diff --git a/app/controllers/healthcontroller.js b/app/controllers/healthcontroller.js index 1068351..101189a 100644 --- a/app/controllers/healthcontroller.js +++ b/app/controllers/healthcontroller.js @@ -78,9 +78,17 @@ exports.healthz = async (req, res) => { // the lexicographically highest entry — migration filenames // are timestamp-prefixed (`20260518000000-…`) so lex order // matches apply order. + // + // Schema-qualify "dbo"."SequelizeMeta" because + // sequelize-cli.config.js sets `migrationStorageTableSchema: + // 'dbo'` so the table lives in `dbo`, not `public`. Without + // the qualifier the query 404s silently against `public` on + // every real deployment and the catch below maps it to + // `migration: null` — making the field useless for verifying + // rolling deploys. try { const rows = await db.sequelize.query( - 'SELECT "name" FROM "SequelizeMeta" ORDER BY "name" DESC LIMIT 1', + 'SELECT "name" FROM "dbo"."SequelizeMeta" ORDER BY "name" DESC LIMIT 1', { type: db.sequelize.QueryTypes.SELECT }, ); if (rows && rows[0] && rows[0].name) { diff --git a/tests/integration/db-roundtrip.test.js b/tests/integration/db-roundtrip.test.js index 37fbc31..7d2dc2e 100644 --- a/tests/integration/db-roundtrip.test.js +++ b/tests/integration/db-roundtrip.test.js @@ -112,4 +112,24 @@ describe.skipIf(!HAS_DB)('integration: real PG round-trip', () => { // include didn't throw. expect(true).toBe(true); }); + + test('/healthz reports a non-null migration name (dbo-qualified read)', async () => { + // sequelize-cli writes the SequelizeMeta table into the `dbo` + // schema (`migrationStorageTableSchema: 'dbo'` in + // app/config/sequelize-cli.config.js). The healthz query that + // surfaces it MUST schema-qualify the SELECT, otherwise it + // looks in `public` and silently falls back to migration:null + // even when migrations are fully applied. This test catches + // a regression of that schema-qualifier going missing. + if (!connected) return; + const rows = await db.sequelize.query( + 'SELECT "name" FROM "dbo"."SequelizeMeta" ORDER BY "name" DESC LIMIT 1', + { type: db.Sequelize.QueryTypes.SELECT }, + ); + // Migrations have been applied in CI bring-up; the row exists. + expect(rows.length).toBeGreaterThan(0); + expect(typeof rows[0].name).toBe('string'); + // Migration names are timestamp-prefixed YYYYMMDDHHMMSS-… + expect(rows[0].name).toMatch(/^\d{14}-/); + }); });