test(slice-0.4): assert migration SQL and top-bar silver/XP rendering

Adds a real-SQL-inspection test for CreateLootAndRewards1788600000000
(mirroring the visible-vertical-slice pattern) so the one-reward-per-combat
unique index and other DB-level invariants can't be silently deleted
without failing a test, and asserts the TopBar renders character.silver
and character.experience values already present in the app.spec.ts fixture.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Bastian Wagner
2026-08-20 10:25:26 +02:00
parent 1a7a766f2b
commit 048cf80e78
2 changed files with 53 additions and 1 deletions

View File

@@ -1,5 +1,6 @@
import 'reflect-metadata';
import { getMetadataArgsStorage } from 'typeorm';
import { getMetadataArgsStorage, QueryRunner } from 'typeorm';
import { CreateLootAndRewards1788600000000 } from './1788600000000-CreateLootAndRewards';
import { Character } from '../../characters/entities/character.entity';
import { CharacterItem } from '../../items/entities/character-item.entity';
import { ItemDefinition } from '../../items/entities/item-definition.entity';
@@ -94,4 +95,53 @@ describe('loot and rewards schema', () => {
expect(dropChance?.options.precision).toBe(5);
expect(dropChance?.options.scale).toBe(4);
});
it('emits the real SQL that enforces the schema invariants, not just entity decorators', async () => {
// synchronize: false means entity decorators never touch the real database -
// only the raw SQL emitted by the migration itself does. Assert on that SQL
// directly so deleting a constraint here would fail this test.
const query = jest.fn().mockResolvedValue(undefined);
const queryRunner = { query } as unknown as QueryRunner;
const migration = new CreateLootAndRewards1788600000000();
await migration.up(queryRunner);
const upQueries = query.mock.calls.map(([sql]) => sql as string);
expect(upQueries).toEqual(
expect.arrayContaining([
// The database half of the "one reward per combat" invariant (spec §7, §37).
expect.stringContaining('CREATE UNIQUE INDEX "IDX_combat_rewards_combat"'),
expect.stringContaining('CREATE UNIQUE INDEX "IDX_character_items_character_item"'),
expect.stringContaining('CREATE UNIQUE INDEX "IDX_combat_reward_items_reward_item"'),
expect.stringContaining('ALTER TABLE "characters" ADD COLUMN "silver"'),
expect.stringContaining('ALTER TABLE "monster_definitions" ADD COLUMN "loot_table_id"'),
]),
);
const checkConstraints = upQueries.filter((sql) => sql.includes('CHECK ('));
expect(checkConstraints.length).toBeGreaterThan(0);
expect(
checkConstraints.some(
(sql) =>
sql.includes('CHK_loot_table_entries_drop_chance') ||
sql.includes('CHK_character_items_quantity'),
),
).toBe(true);
await migration.down(queryRunner);
const downQueries = query.mock.calls
.slice(upQueries.length)
.map(([sql]) => sql as string);
// Proves down() is real and reverses the up() migration, not a no-op.
expect(downQueries).toEqual(
expect.arrayContaining([
expect.stringContaining('DROP TABLE "combat_rewards"'),
expect.stringContaining('DROP TABLE "character_items"'),
'ALTER TABLE "characters" DROP COLUMN "silver"',
]),
);
});
});