From 3662029ec9086249f75c7ec43e8d1b4c4181fc27 Mon Sep 17 00:00:00 2001 From: Bastian Wagner Date: Fri, 21 Aug 2026 08:56:48 +0200 Subject: [PATCH] test(renown): pin the migration's backfill and constraint statement ordering Co-Authored-By: Claude Opus 5 --- ...1791000000000-CreateRenownAndReputation.ts | 1 + .../renown-and-reputation.migration.spec.ts | 42 ++++++++++++------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/apps/api/src/database/migrations/1791000000000-CreateRenownAndReputation.ts b/apps/api/src/database/migrations/1791000000000-CreateRenownAndReputation.ts index dd7661a..bb49c42 100644 --- a/apps/api/src/database/migrations/1791000000000-CreateRenownAndReputation.ts +++ b/apps/api/src/database/migrations/1791000000000-CreateRenownAndReputation.ts @@ -134,6 +134,7 @@ export class CreateRenownAndReputation1791000000000 implements MigrationInterfac } public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query('DROP INDEX "IDX_turn_in_definitions_key"'); await queryRunner.query('DROP TABLE "turn_in_definitions"'); await queryRunner.query( diff --git a/apps/api/src/database/migrations/renown-and-reputation.migration.spec.ts b/apps/api/src/database/migrations/renown-and-reputation.migration.spec.ts index 31179cf..f873446 100644 --- a/apps/api/src/database/migrations/renown-and-reputation.migration.spec.ts +++ b/apps/api/src/database/migrations/renown-and-reputation.migration.spec.ts @@ -32,22 +32,29 @@ describe('CreateRenownAndReputation1791000000000', () => { ); }); - it('clamps existing level into the 1-15 Renown range before dropping it', async () => { + it('backfills renown from level BEFORE dropping the level column', async () => { const up = await runUp(); - expect(up).toEqual( - expect.arrayContaining([expect.stringContaining('LEAST(GREATEST("level", 1), 15)')]), - ); + const backfillIndex = up.findIndex((sql) => sql.includes('LEAST(GREATEST("level", 1), 15)')); + const dropLevelIndex = up.findIndex((sql) => sql.includes('DROP COLUMN "level"')); + + expect(backfillIndex).toBeGreaterThanOrEqual(0); + expect(dropLevelIndex).toBeGreaterThanOrEqual(0); + // Reversing these two would silently discard every character's progression. + expect(backfillIndex).toBeLessThan(dropLevelIndex); }); - it('enforces the renown range at the database level', async () => { + it('adds the renown range constraint only AFTER the backfill has populated valid values', async () => { const up = await runUp(); - expect(up).toEqual( - expect.arrayContaining([ - expect.stringContaining('CHK_characters_renown'), - ]), - ); + const backfillIndex = up.findIndex((sql) => sql.includes('LEAST(GREATEST("level", 1), 15)')); + const constraintIndex = up.findIndex((sql) => sql.includes('CHK_characters_renown')); + + expect(backfillIndex).toBeGreaterThanOrEqual(0); + expect(constraintIndex).toBeGreaterThanOrEqual(0); + // ADD CONSTRAINT validates the whole table; running it before the backfill + // would only pass by coincidence of the column DEFAULT being in range. + expect(backfillIndex).toBeLessThan(constraintIndex); }); it('drops required_level from item_definitions', async () => { @@ -125,10 +132,15 @@ describe('CreateRenownAndReputation1791000000000', () => { expect.stringContaining('ALTER TABLE "characters" DROP COLUMN "renown"'), ]), ); - // The dropped turn_in_definitions table must be the very first statement - // in down() -- it's the last thing up() created, and it has FK - // dependencies on reputation_factions/item_definitions that must be - // gone before those tables can be touched. - expect(down[0]).toContain('DROP TABLE "turn_in_definitions"'); + const turnInDropIndex = down.findIndex((sql) => sql.includes('DROP TABLE "turn_in_definitions"')); + const reputationFactionsDropIndex = down.findIndex((sql) => + sql.includes('DROP TABLE "reputation_factions"'), + ); + + expect(turnInDropIndex).toBeGreaterThanOrEqual(0); + expect(reputationFactionsDropIndex).toBeGreaterThanOrEqual(0); + // turn_in_definitions has FK dependencies on reputation_factions and + // item_definitions that must be gone before those tables are touched. + expect(turnInDropIndex).toBeLessThan(reputationFactionsDropIndex); }); });