// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 // `/api/ready` (spec ยง13): the verdict, the migration level it compares against, and what the answer // does not say. The probes are functions, so the verdict is tested without a database; the migration // list is the real one, read from this repository's folders. import { readdirSync } from "node:fs" import { appRoot, expectedMigrations, GRANT_MIGRATIONS, missingMigrations } from "@app/config" import { appModules } from "@app/modules" import { buildRegistry } from "@conusai-kit/core" import { describe, expect, it } from "vitest" import { checkReadiness, type ReadinessProbes } from "../apps/web/src/server/readiness.js" const ALL = [ "0001_kernel", "0004_files", "0005_proposals", "0006_app-user-identity", "notes/0001_notes", ...GRANT_MIGRATIONS, ] const probes = (overrides: Partial = {}, warnings: string[] = []): ReadinessProbes => ({ store: async () => 1, reader: async () => 1, expected: async () => ALL, applied: async () => new Set(ALL), platform: async () => true, warn: (message) => warnings.push(message), ...overrides, }) describe("the readiness verdict", () => { it("is ready when the store, the reader and the migration level are there", async () => { expect(await checkReadiness(probes())).toEqual({ ready: true, checks: { store: true, reader: true, migrations: true, platform: true }, }) }) it("is not ready when the runtime role cannot query, or the reader cannot", async () => { const down = async () => { throw new Error("connect ECONNREFUSED postgresql://app_app:secret@db:5432/app") } expect((await checkReadiness(probes({ store: down }))).checks).toMatchObject({ store: false, reader: true }) expect((await checkReadiness(probes({ reader: down }))).checks).toMatchObject({ store: true, reader: false }) expect((await checkReadiness(probes({ store: down }))).ready).toBe(false) expect((await checkReadiness(probes({ reader: down }))).ready).toBe(false) }) it("is not ready while a migration this build expects has not run", async () => { const behind = new Set(ALL.slice(0, -1)) const report = await checkReadiness(probes({ applied: async () => behind })) expect(report.ready).toBe(false) expect(report.checks.migrations).toBe(false) }) it("is ready when the database is ahead of this build: a newer release has already migrated it", async () => { const ahead = new Set([...ALL, "9999_from_the_future"]) expect((await checkReadiness(probes({ applied: async () => ahead }))).ready).toBe(true) }) it("reports the platform without requiring it", async () => { const report = await checkReadiness(probes({ platform: async () => false })) expect(report.ready).toBe(true) expect(report.checks.platform).toBe(false) const thrown = await checkReadiness( probes({ platform: async () => { throw new Error("boom") }, }), ) expect(thrown.checks.platform).toBe(false) }) it("answers with booleans and nothing else, and logs the reason where an operator reads it", async () => { const warnings: string[] = [] const report = await checkReadiness( probes( { store: async () => { throw new Error("password authentication failed for user app_app\nHINT: pg_hba.conf") }, applied: async () => new Set(["0001_kernel"]), }, warnings, ), ) const answer = JSON.stringify(report) expect(answer).not.toContain("password") expect(answer).not.toContain("0004_files") expect(Object.values(report.checks).every((value) => typeof value === "boolean")).toBe(true) expect(warnings).toEqual([ "ready: store failed: password authentication failed for user app_app", expect.stringMatching(/^ready: migrations failed: the migrate job has not applied 0004_files/), ]) }) }) describe("the migration level", () => { it("lists the app's files, then each module's, then the grants the migrate job adds", async () => { const expected = await expectedMigrations(buildRegistry([...appModules])) const files = readdirSync(`${appRoot()}/migrations/postgres`) .filter((name) => name.endsWith(".sql")) .map((name) => name.replace(/\.sql$/, "")) .sort() expect(expected.slice(0, files.length)).toEqual(files) expect(expected).toContain("notes/0001_notes") expect(expected.slice(-2)).toEqual([...GRANT_MIGRATIONS]) }) it("names what a database lacks", () => { expect(missingMigrations(["a", "b", "c"], new Set(["a", "c", "z"]))).toEqual(["b"]) }) })