diff --git a/src/db/schema.ts b/src/db/schema.ts index 326a4f028d..4b57195135 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -1,4 +1,9 @@ import { index, integer, real, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core"; +// Timestamp columns use a drizzle $defaultFn so an insert that omits the column gets a real ISO-8601 +// timestamp. A static `.default("CURRENT_TIMESTAMP")` would make drizzle inject the literal STRING +// "CURRENT_TIMESTAMP" (it applies static defaults client-side, never reaching SQLite's CURRENT_TIMESTAMP), +// which previously corrupted timestamp columns on omit (e.g. webhook_events.received_at). +import { nowIso } from "../utils/json"; export const installations = sqliteTable("installations", { id: integer("id").primaryKey(), @@ -9,8 +14,8 @@ export const installations = sqliteTable("installations", { permissionsJson: text("permissions_json").notNull().default("{}"), eventsJson: text("events_json").notNull().default("[]"), suspendedAt: text("suspended_at"), - createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + createdAt: text("created_at").notNull().$defaultFn(() => nowIso()), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), }); export const repositories = sqliteTable("repositories", { @@ -29,8 +34,8 @@ export const repositories = sqliteTable("repositories", { maintainerCut: real("maintainer_cut").notNull().default(0), labelMultipliersJson: text("label_multipliers_json").notNull().default("{}"), lastRegistrySnapshotId: text("last_registry_snapshot_id"), - createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + createdAt: text("created_at").notNull().$defaultFn(() => nowIso()), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), }); export const repositorySettings = sqliteTable("repository_settings", { @@ -54,8 +59,8 @@ export const repositorySettings = sqliteTable("repository_settings", { backfillEnabled: integer("backfill_enabled", { mode: "boolean" }).notNull().default(true), privateTrustEnabled: integer("private_trust_enabled", { mode: "boolean" }).notNull().default(true), commandAuthorizationJson: text("command_authorization_json").notNull().default("{}"), - createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + createdAt: text("created_at").notNull().$defaultFn(() => nowIso()), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), }); export const repoSyncState = sqliteTable("repo_sync_state", { @@ -76,7 +81,7 @@ export const repoSyncState = sqliteTable("repo_sync_state", { lastCompletedAt: text("last_completed_at"), errorSummary: text("error_summary"), warningsJson: text("warnings_json").notNull().default("[]"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), }); export const repoSyncSegments = sqliteTable( @@ -101,7 +106,7 @@ export const repoSyncSegments = sqliteTable( lastModified: text("last_modified"), warningsJson: text("warnings_json").notNull().default("[]"), errorSummary: text("error_summary"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), }, (table) => ({ repoSegment: uniqueIndex("repo_sync_segments_repo_segment_unique").on(table.repoFullName, table.segment), @@ -120,7 +125,7 @@ export const githubRateLimitObservations = sqliteTable( limitValue: integer("limit_value"), remaining: integer("remaining"), resetAt: text("reset_at"), - observedAt: text("observed_at").notNull().default("CURRENT_TIMESTAMP"), + observedAt: text("observed_at").notNull().$defaultFn(() => nowIso()), }, (table) => ({ repoObserved: index("github_rate_limit_observations_repo_observed_idx").on(table.repoFullName, table.observedAt), @@ -161,7 +166,7 @@ export const pullRequestDetailSyncState = sqliteTable( checksSyncedAt: text("checks_synced_at"), lastSyncedAt: text("last_synced_at"), errorSummary: text("error_summary"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), }, (table) => ({ repoPull: uniqueIndex("pull_request_detail_sync_repo_pull_unique").on(table.repoFullName, table.pullNumber), @@ -180,7 +185,7 @@ export const repoLabels = sqliteTable( isConfigured: integer("is_configured", { mode: "boolean" }).notNull().default(false), observedCount: integer("observed_count").notNull().default(0), payloadJson: text("payload_json").notNull().default("{}"), - lastSeenAt: text("last_seen_at").notNull().default("CURRENT_TIMESTAMP"), + lastSeenAt: text("last_seen_at").notNull().$defaultFn(() => nowIso()), }, (table) => ({ repoLabel: uniqueIndex("repo_labels_repo_name_unique").on(table.repoFullName, table.name), @@ -232,8 +237,8 @@ export const pullRequests = sqliteTable( linkedIssuesJson: text("linked_issues_json").notNull().default("[]"), lastSeenOpenAt: text("last_seen_open_at"), payloadJson: text("payload_json").notNull().default("{}"), - createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + createdAt: text("created_at").notNull().$defaultFn(() => nowIso()), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), }, (table) => ({ repoNumber: uniqueIndex("pull_requests_repo_number_unique").on(table.repoFullName, table.number), @@ -253,7 +258,7 @@ export const pullRequestFiles = sqliteTable( changes: integer("changes").notNull().default(0), previousFilename: text("previous_filename"), payloadJson: text("payload_json").notNull().default("{}"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), }, (table) => ({ repoPullPath: uniqueIndex("pull_request_files_repo_pull_path_unique").on(table.repoFullName, table.pullNumber, table.path), @@ -269,7 +274,7 @@ export const pullRequestReviews = sqliteTable("pull_request_reviews", { authorAssociation: text("author_association"), submittedAt: text("submitted_at"), payloadJson: text("payload_json").notNull().default("{}"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), }); export const checkSummaries = sqliteTable( @@ -286,7 +291,7 @@ export const checkSummaries = sqliteTable( completedAt: text("completed_at"), detailsUrl: text("details_url"), payloadJson: text("payload_json").notNull().default("{}"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), }, (table) => ({ repoShaName: uniqueIndex("check_summaries_repo_sha_name_unique").on(table.repoFullName, table.headSha, table.name), @@ -307,7 +312,7 @@ export const recentMergedPullRequests = sqliteTable( linkedIssuesJson: text("linked_issues_json").notNull().default("[]"), changedFilesJson: text("changed_files_json").notNull().default("[]"), payloadJson: text("payload_json").notNull().default("{}"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), }, (table) => ({ repoNumber: uniqueIndex("recent_merged_pull_requests_repo_number_unique").on(table.repoFullName, table.number), @@ -329,8 +334,8 @@ export const issues = sqliteTable( linkedPrsJson: text("linked_prs_json").notNull().default("[]"), lastSeenOpenAt: text("last_seen_open_at"), payloadJson: text("payload_json").notNull().default("{}"), - createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + createdAt: text("created_at").notNull().$defaultFn(() => nowIso()), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), }, (table) => ({ repoNumber: uniqueIndex("issues_repo_number_unique").on(table.repoFullName, table.number), @@ -347,8 +352,8 @@ export const bounties = sqliteTable( amountText: text("amount_text"), sourceUrl: text("source_url"), payloadJson: text("payload_json").notNull().default("{}"), - discoveredAt: text("discovered_at").notNull().default("CURRENT_TIMESTAMP"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + discoveredAt: text("discovered_at").notNull().$defaultFn(() => nowIso()), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), }, (table) => ({ repoIssue: uniqueIndex("bounties_repo_issue_unique").on(table.repoFullName, table.issueNumber), @@ -362,9 +367,9 @@ export const contributors = sqliteTable("contributors", { publicRepos: integer("public_repos"), followers: integer("followers"), source: text("source").notNull().default("github"), - firstSeenAt: text("first_seen_at").notNull().default("CURRENT_TIMESTAMP"), - lastSeenAt: text("last_seen_at").notNull().default("CURRENT_TIMESTAMP"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + firstSeenAt: text("first_seen_at").notNull().$defaultFn(() => nowIso()), + lastSeenAt: text("last_seen_at").notNull().$defaultFn(() => nowIso()), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), }); export const contributorRepoStats = sqliteTable( @@ -381,7 +386,7 @@ export const contributorRepoStats = sqliteTable( unlinkedPullRequests: integer("unlinked_pull_requests").notNull().default(0), dominantLabelsJson: text("dominant_labels_json").notNull().default("[]"), lastActivityAt: text("last_activity_at"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), }, (table) => ({ loginRepo: uniqueIndex("contributor_repo_stats_login_repo_unique").on(table.login, table.repoFullName), @@ -400,7 +405,7 @@ export const collisionEdges = sqliteTable("collision_edges", { risk: text("risk").notNull(), reason: text("reason").notNull(), sharedTermsJson: text("shared_terms_json").notNull().default("[]"), - generatedAt: text("generated_at").notNull().default("CURRENT_TIMESTAMP"), + generatedAt: text("generated_at").notNull().$defaultFn(() => nowIso()), }); export const signalSnapshots = sqliteTable("signal_snapshots", { @@ -409,7 +414,7 @@ export const signalSnapshots = sqliteTable("signal_snapshots", { targetKey: text("target_key").notNull(), repoFullName: text("repo_full_name"), payloadJson: text("payload_json").notNull().default("{}"), - generatedAt: text("generated_at").notNull().default("CURRENT_TIMESTAMP"), + generatedAt: text("generated_at").notNull().$defaultFn(() => nowIso()), }); export const agentRuns = sqliteTable( @@ -424,8 +429,8 @@ export const agentRuns = sqliteTable( dataQualityStatus: text("data_quality_status").notNull().default("unknown"), errorSummary: text("error_summary"), payloadJson: text("payload_json").notNull().default("{}"), - createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + createdAt: text("created_at").notNull().$defaultFn(() => nowIso()), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), }, (table) => ({ actorUpdated: index("agent_runs_actor_updated_idx").on(table.actorLogin, table.updatedAt), @@ -455,7 +460,7 @@ export const agentActions = sqliteTable( approvalRequired: integer("approval_required", { mode: "boolean" }).notNull().default(true), safetyClass: text("safety_class").notNull(), payloadJson: text("payload_json").notNull().default("{}"), - createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"), + createdAt: text("created_at").notNull().$defaultFn(() => nowIso()), }, (table) => ({ runAction: index("agent_actions_run_action_idx").on(table.runId, table.actionType), @@ -473,7 +478,7 @@ export const agentContextSnapshots = sqliteTable( scoringModelId: text("scoring_model_id"), freshnessWarningsJson: text("freshness_warnings_json").notNull().default("[]"), payloadJson: text("payload_json").notNull().default("{}"), - createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"), + createdAt: text("created_at").notNull().$defaultFn(() => nowIso()), }, (table) => ({ runCreated: index("agent_context_snapshots_run_created_idx").on(table.runId, table.createdAt), @@ -503,10 +508,10 @@ export const agentRecommendationOutcomes = sqliteTable( confidence: text("confidence").notNull(), reason: text("reason").notNull(), sourceUpdatedAt: text("source_updated_at"), - detectedAt: text("detected_at").notNull().default("CURRENT_TIMESTAMP"), + detectedAt: text("detected_at").notNull().$defaultFn(() => nowIso()), metadataJson: text("metadata_json").notNull().default("{}"), - createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + createdAt: text("created_at").notNull().$defaultFn(() => nowIso()), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), }, (table) => ({ action: uniqueIndex("agent_recommendation_outcomes_action_unique").on(table.actionId), @@ -547,8 +552,8 @@ export const advisories = sqliteTable("advisories", { findingsJson: text("findings_json").notNull().default("[]"), checkRunId: integer("check_run_id"), checkRunUrl: text("check_run_url"), - createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + createdAt: text("created_at").notNull().$defaultFn(() => nowIso()), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), }); export const webhookEvents = sqliteTable("webhook_events", { @@ -560,7 +565,7 @@ export const webhookEvents = sqliteTable("webhook_events", { payloadHash: text("payload_hash").notNull(), status: text("status").notNull(), errorSummary: text("error_summary"), - receivedAt: text("received_at").notNull().default("CURRENT_TIMESTAMP"), + receivedAt: text("received_at").notNull().$defaultFn(() => nowIso()), processedAt: text("processed_at"), }); @@ -572,7 +577,7 @@ export const syncRuns = sqliteTable("sync_runs", { sourceUrl: text("source_url"), warningsJson: text("warnings_json").notNull().default("[]"), errorSummary: text("error_summary"), - startedAt: text("started_at").notNull().default("CURRENT_TIMESTAMP"), + startedAt: text("started_at").notNull().$defaultFn(() => nowIso()), completedAt: text("completed_at"), }); @@ -598,27 +603,27 @@ export const scorePreviews = sqliteTable("score_previews", { contributorLogin: text("contributor_login"), inputJson: text("input_json").notNull().default("{}"), resultJson: text("result_json").notNull().default("{}"), - generatedAt: text("generated_at").notNull().default("CURRENT_TIMESTAMP"), + generatedAt: text("generated_at").notNull().$defaultFn(() => nowIso()), }); export const contributorEvidence = sqliteTable("contributor_evidence", { login: text("login").primaryKey(), payloadJson: text("payload_json").notNull().default("{}"), - generatedAt: text("generated_at").notNull().default("CURRENT_TIMESTAMP"), + generatedAt: text("generated_at").notNull().$defaultFn(() => nowIso()), }); export const contributorScoringProfiles = sqliteTable("contributor_scoring_profiles", { login: text("login").primaryKey(), scoringModelSnapshotId: text("scoring_model_snapshot_id").notNull(), payloadJson: text("payload_json").notNull().default("{}"), - generatedAt: text("generated_at").notNull().default("CURRENT_TIMESTAMP"), + generatedAt: text("generated_at").notNull().$defaultFn(() => nowIso()), }); export const officialMinerDetections = sqliteTable("official_miner_detections", { login: text("login").primaryKey(), status: text("status").notNull(), snapshotJson: text("snapshot_json").notNull().default("{}"), error: text("error"), fetchedAt: text("fetched_at").notNull(), expiresAt: text("expires_at").notNull(), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), }); export const issueQualityReports = sqliteTable( @@ -628,7 +633,7 @@ export const issueQualityReports = sqliteTable( repoFullName: text("repo_full_name").notNull(), issueNumber: integer("issue_number").notNull(), payloadJson: text("payload_json").notNull().default("{}"), - generatedAt: text("generated_at").notNull().default("CURRENT_TIMESTAMP"), + generatedAt: text("generated_at").notNull().$defaultFn(() => nowIso()), }, (table) => ({ repoIssue: uniqueIndex("issue_quality_reports_repo_issue_unique").on(table.repoFullName, table.issueNumber), @@ -638,13 +643,13 @@ export const issueQualityReports = sqliteTable( export const burdenForecasts = sqliteTable("burden_forecasts", { repoFullName: text("repo_full_name").primaryKey(), payloadJson: text("payload_json").notNull().default("{}"), - generatedAt: text("generated_at").notNull().default("CURRENT_TIMESTAMP"), + generatedAt: text("generated_at").notNull().$defaultFn(() => nowIso()), }); export const repoQueueTrendSnapshots = sqliteTable("repo_queue_trend_snapshots", { repoFullName: text("repo_full_name").primaryKey(), payloadJson: text("payload_json").notNull().default("{}"), - generatedAt: text("generated_at").notNull().default("CURRENT_TIMESTAMP"), + generatedAt: text("generated_at").notNull().$defaultFn(() => nowIso()), }); export const registryDriftEvents = sqliteTable("registry_drift_events", { @@ -655,7 +660,7 @@ export const registryDriftEvents = sqliteTable("registry_drift_events", { previousSnapshotId: text("previous_snapshot_id"), currentSnapshotId: text("current_snapshot_id"), payloadJson: text("payload_json").notNull().default("{}"), - generatedAt: text("generated_at").notNull().default("CURRENT_TIMESTAMP"), + generatedAt: text("generated_at").notNull().$defaultFn(() => nowIso()), }); export const upstreamSourceSnapshots = sqliteTable( @@ -736,7 +741,7 @@ export const bountyLifecycleEvents = sqliteTable("bounty_lifecycle_events", { issueNumber: integer("issue_number").notNull(), status: text("status").notNull(), payloadJson: text("payload_json").notNull().default("{}"), - generatedAt: text("generated_at").notNull().default("CURRENT_TIMESTAMP"), + generatedAt: text("generated_at").notNull().$defaultFn(() => nowIso()), }); export const authSessions = sqliteTable( @@ -749,7 +754,7 @@ export const authSessions = sqliteTable( scopesJson: text("scopes_json").notNull().default("[]"), expiresAt: text("expires_at").notNull(), revokedAt: text("revoked_at"), - createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"), + createdAt: text("created_at").notNull().$defaultFn(() => nowIso()), lastSeenAt: text("last_seen_at"), metadataJson: text("metadata_json").notNull().default("{}"), }, @@ -769,8 +774,8 @@ export const digestSubscriptions = sqliteTable( email: text("email").notNull(), status: text("status").notNull().default("active"), source: text("source").notNull().default("app"), - createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + createdAt: text("created_at").notNull().$defaultFn(() => nowIso()), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), }, (table) => ({ loginEmail: uniqueIndex("digest_subscriptions_login_email_unique").on(table.login, table.email), @@ -790,8 +795,8 @@ export const githubAgentCommandAnswers = sqliteTable( responseCommentId: integer("response_comment_id"), responseUrl: text("response_url"), actorKind: text("actor_kind").notNull(), - createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + createdAt: text("created_at").notNull().$defaultFn(() => nowIso()), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), metadataJson: text("metadata_json").notNull().default("{}"), }, (table) => ({ @@ -814,8 +819,8 @@ export const githubAgentCommandFeedback = sqliteTable( vote: text("vote").notNull(), source: text("source").notNull(), actorKind: text("actor_kind").notNull(), - createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + createdAt: text("created_at").notNull().$defaultFn(() => nowIso()), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), metadataJson: text("metadata_json").notNull().default("{}"), }, (table) => ({ @@ -836,7 +841,7 @@ export const auditEvents = sqliteTable( outcome: text("outcome").notNull(), detail: text("detail"), metadataJson: text("metadata_json").notNull().default("{}"), - createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"), + createdAt: text("created_at").notNull().$defaultFn(() => nowIso()), }, (table) => ({ typeCreated: index("audit_events_type_created_idx").on(table.eventType, table.createdAt), @@ -862,7 +867,7 @@ export const productUsageEvents = sqliteTable( clientName: text("client_name"), clientVersion: text("client_version"), metadataJson: text("metadata_json").notNull().default("{}"), - occurredAt: text("occurred_at").notNull().default("CURRENT_TIMESTAMP"), + occurredAt: text("occurred_at").notNull().$defaultFn(() => nowIso()), }, (table) => ({ surfaceOccurred: index("product_usage_events_surface_occurred_idx").on(table.surface, table.occurredAt), @@ -898,8 +903,8 @@ export const productUsageDailyRollups = sqliteTable( activationByRoleJson: text("activation_by_role_json").notNull().default("[]"), activationBySurfaceJson: text("activation_by_surface_json").notNull().default("[]"), retentionJson: text("retention_json").notNull().default("[]"), - generatedAt: text("generated_at").notNull().default("CURRENT_TIMESTAMP"), - updatedAt: text("updated_at").notNull().default("CURRENT_TIMESTAMP"), + generatedAt: text("generated_at").notNull().$defaultFn(() => nowIso()), + updatedAt: text("updated_at").notNull().$defaultFn(() => nowIso()), }, (table) => ({ statusUpdated: index("product_usage_daily_rollups_status_idx").on(table.status, table.updatedAt), @@ -918,7 +923,7 @@ export const aiUsageEvents = sqliteTable( estimatedNeurons: integer("estimated_neurons").notNull().default(0), detail: text("detail"), metadataJson: text("metadata_json").notNull().default("{}"), - createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"), + createdAt: text("created_at").notNull().$defaultFn(() => nowIso()), }, (table) => ({ featureCreated: index("ai_usage_events_feature_created_idx").on(table.feature, table.createdAt), diff --git a/test/unit/schema-timestamp-defaults.test.ts b/test/unit/schema-timestamp-defaults.test.ts new file mode 100644 index 0000000000..2278407a90 --- /dev/null +++ b/test/unit/schema-timestamp-defaults.test.ts @@ -0,0 +1,30 @@ +import { eq } from "drizzle-orm"; +import { describe, expect, it } from "vitest"; +import { getDb } from "../../src/db/client"; +import { repositorySettings, webhookEvents } from "../../src/db/schema"; +import { createTestEnv } from "../helpers/d1"; + +const ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/; + +describe("timestamp column defaults", () => { + it("inserts a real ISO timestamp (not the literal 'CURRENT_TIMESTAMP') when the column is omitted", async () => { + const env = createTestEnv(); + const db = getDb(env.DB); + // Omit receivedAt entirely — drizzle must inject a real timestamp via $defaultFn, not the static + // string "CURRENT_TIMESTAMP" that a `.default("CURRENT_TIMESTAMP")` would have injected client-side. + await db.insert(webhookEvents).values({ deliveryId: "ts-default-1", eventName: "push", payloadHash: "h", status: "processed" }); + const [row] = await db.select().from(webhookEvents).where(eq(webhookEvents.deliveryId, "ts-default-1")).limit(1); + expect(row?.receivedAt).not.toBe("CURRENT_TIMESTAMP"); + expect(row?.receivedAt ?? "").toMatch(ISO); + }); + + it("applies the same default to createdAt/updatedAt on omit", async () => { + const env = createTestEnv(); + const db = getDb(env.DB); + await db.insert(repositorySettings).values({ repoFullName: "acme/widgets" }); + const [row] = await db.select().from(repositorySettings).where(eq(repositorySettings.repoFullName, "acme/widgets")).limit(1); + expect(row?.createdAt).toMatch(ISO); + expect(row?.updatedAt).toMatch(ISO); + expect(row?.createdAt).not.toBe("CURRENT_TIMESTAMP"); + }); +});