import 'reflect-metadata'; import { QueryRunner } from 'typeorm'; import { SellableLootBags1796000000000 } from './1796000000000-SellableLootBags'; async function runUp(): Promise { const query = jest.fn().mockResolvedValue(undefined); const queryRunner = { query } as unknown as QueryRunner; await new SellableLootBags1796000000000().up(queryRunner); return query.mock.calls.map(([sql]) => sql as string); } async function runDown(): Promise { const query = jest.fn().mockResolvedValue(undefined); const queryRunner = { query } as unknown as QueryRunner; const migration = new SellableLootBags1796000000000(); await migration.up(queryRunner); const upCount = query.mock.calls.length; await migration.down(queryRunner); return query.mock.calls.slice(upCount).map(([sql]) => sql as string); } describe('SellableLootBags1796000000000', () => { it('adds the bag target and bypass columns', async () => { const joined = (await runUp()).join('\n'); expect(joined).toContain('"loot_bag_definition_id" uuid'); expect(joined).toContain('"bypass_conditions" jsonb'); expect(joined).toContain('FK_shop_offers_loot_bag'); }); it('makes the item target nullable so a bag offer can exist', async () => { const joined = (await runUp()).join('\n'); expect(joined).toContain('ALTER COLUMN "item_definition_id" DROP NOT NULL'); }); it('requires exactly one target per offer', async () => { const joined = (await runUp()).join('\n'); // An offer that sells nothing, or sells both an item and a bag, is a // content bug the database must not store. expect(joined).toContain('CHK_shop_offers_single_target'); expect(joined).toContain('num_nonnulls'); }); it('keeps both target kinds unique per shop via partial indexes', async () => { const joined = (await runUp()).join('\n'); // A plain unique index over a nullable column would let a shop hold // unlimited bag offers, because Postgres treats NULLs as distinct. expect(joined).toContain( 'CREATE UNIQUE INDEX "IDX_shop_offers_shop_item" ON "shop_offers" ("shop_id", "item_definition_id") WHERE "item_definition_id" IS NOT NULL', ); expect(joined).toContain( 'CREATE UNIQUE INDEX "IDX_shop_offers_shop_bag" ON "shop_offers" ("shop_id", "loot_bag_definition_id") WHERE "loot_bag_definition_id" IS NOT NULL', ); }); it('clears content offers so the seed can own stable ids', async () => { const joined = (await runUp()).join('\n'); expect(joined).toContain('DELETE FROM "shop_offers"'); }); it('drops what it added and restores the original index on rollback', async () => { const joined = (await runDown()).join('\n'); expect(joined).toContain('DROP COLUMN "loot_bag_definition_id"'); expect(joined).toContain('DROP COLUMN "bypass_conditions"'); expect(joined).toContain('ALTER COLUMN "item_definition_id" SET NOT NULL'); expect(joined).toContain( 'CREATE UNIQUE INDEX "IDX_shop_offers_shop_item" ON "shop_offers" ("shop_id", "item_definition_id")', ); }); });