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:
@@ -1,5 +1,6 @@
|
|||||||
import 'reflect-metadata';
|
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 { Character } from '../../characters/entities/character.entity';
|
||||||
import { CharacterItem } from '../../items/entities/character-item.entity';
|
import { CharacterItem } from '../../items/entities/character-item.entity';
|
||||||
import { ItemDefinition } from '../../items/entities/item-definition.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.precision).toBe(5);
|
||||||
expect(dropChance?.options.scale).toBe(4);
|
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"',
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ describe('App', () => {
|
|||||||
);
|
);
|
||||||
expect(fixture.nativeElement.querySelector('app-top-bar')?.textContent).toContain('Stufe 7');
|
expect(fixture.nativeElement.querySelector('app-top-bar')?.textContent).toContain('Stufe 7');
|
||||||
expect(fixture.nativeElement.querySelector('app-top-bar')?.textContent).toContain('52 / 80');
|
expect(fixture.nativeElement.querySelector('app-top-bar')?.textContent).toContain('52 / 80');
|
||||||
|
expect(fixture.nativeElement.querySelector('app-top-bar')?.textContent).toContain('150');
|
||||||
|
expect(fixture.nativeElement.querySelector('app-top-bar')?.textContent).toContain('320');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('redirects root and unknown routes to the world shell', () => {
|
it('redirects root and unknown routes to the world shell', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user