import { pool } from '../../src/db'; import { assertSchemaPresent, migrate, resetDb, testPool, closeDb } from './setup/testDb'; /** * The diagnostic for #154, exercised against a database that has actually lost * its schema. * * The failure this guards is not hypothetical and not cheap: the integration run * lost its schema partway through, and it presented as 36 assertion errors about * categories, price filters and favourite notifications. The real message — * `relation "items" does not exist` — was further down the same log. * * This suite destroys and rebuilds the schema, so it restores in afterAll and * `migrate()` is what puts it back: dropping the schema takes `pgmigrations` * with it, so node-pg-migrate re-runs everything rather than believing it has * already done so. */ async function dropEverything(): Promise { await testPool.query('DROP SCHEMA public CASCADE; CREATE SCHEMA public;'); } afterAll(async () => { // Put it back before anything else in the run touches it. Ordering matters: // jest runs these in band, so a suite after this one would otherwise find // nothing at all. await migrate(); await pool.end(); await closeDb(); }); describe('when the database loses its schema', () => { it('assertSchemaPresent says so, rather than letting the suite guess', async () => { await dropEverything(); await expect(assertSchemaPresent('in a test')).rejects.toThrow(/has no schema \(in a test\)/); await migrate(); }); it('names the tables that are missing', async () => { await dropEverything(); // The count and a few names, not the whole list: the point is that a reader // can tell at a glance this is a missing schema rather than a logic bug. await expect(assertSchemaPresent('in a test')).rejects.toThrow(/Missing 20 of 20 tables/); await expect(assertSchemaPresent('in a test')).rejects.toThrow(/items/); await migrate(); }); it('points at the cause rather than leaving it to be rediscovered', async () => { await dropEverything(); await expect(assertSchemaPresent('in a test')).rejects.toThrow(/TRUNCATE does not/); await expect(assertSchemaPresent('in a test')).rejects.toThrow(/#154/); await migrate(); }); // The path a real run takes: resetDb's TRUNCATE is what fails first, and it // has to turn that into the schema message rather than passing on a bare // "relation does not exist". it('resetDb turns a failed truncate into the schema message', async () => { await dropEverything(); await expect(resetDb()).rejects.toThrow(/has no schema \(while resetting between tests\)/); await migrate(); }); // And it must not swallow unrelated failures behind a schema message. it('says nothing about the schema when the schema is fine', async () => { await expect(assertSchemaPresent('in a test')).resolves.toBeUndefined(); await expect(resetDb()).resolves.toBeUndefined(); }); });