fix(monsters): finish deleting experienceReward, column included
Design ruling R7 abolishes XP as a concept and says the column goes with it, but no task in the plan actually dropped it -- the plan only dropped characters.experience and combat_rewards.experience_granted. Task 9 removed experienceReward from the seed literals, leaving monster_definitions.experience_reward as a NOT NULL column with no default that nothing supplies. The first monster insert against a real database would have failed on a constraint violation. No suite here could have caught it: none of them connect to Postgres. Drops the column in the slice migration (which has never been run, so amending it in place is correct rather than stacking a second one), removes the entity field, and clears the three test fixtures that still set it. silver_min/silver_max deliberately stay -- spec 15 keeps a direct currency drop available as a lore-valid exception, and XP has no such carve-out. Also retargets the seed idempotency test off renown: 1, which is the seed's own default and so could not distinguish "preserved" from "reset to default". NOTE ON SCOPE: this commit also absorbs a Prettier reformatting pass that was already sitting uncommitted in the working tree, which is why it touches ~59 files. That churn is purely cosmetic line-rewrapping -- verified by inspection, and the suite is green at 267/267 with the build at exactly the 3 expected errors owned by Tasks 10 and 11. The repo is not Prettier-clean at baseline (119 files still flagged), so this was a partial run by an earlier step, not a deliberate repo-wide format. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -160,7 +160,9 @@ export class CreateLootAndRewards1788600000000 implements MigrationInterface {
|
||||
'ALTER TABLE "monster_definitions" DROP COLUMN "loot_table_id"',
|
||||
);
|
||||
await queryRunner.query('DROP INDEX "IDX_loot_table_entries_table_item"');
|
||||
await queryRunner.query('DROP INDEX "IDX_loot_table_entries_table_position"');
|
||||
await queryRunner.query(
|
||||
'DROP INDEX "IDX_loot_table_entries_table_position"',
|
||||
);
|
||||
await queryRunner.query('DROP TABLE "loot_table_entries"');
|
||||
await queryRunner.query('DROP INDEX "IDX_loot_tables_key"');
|
||||
await queryRunner.query('DROP TABLE "loot_tables"');
|
||||
|
||||
@@ -7,9 +7,7 @@ import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
* alone — the map and hunt screens still render them. Backfill defaults keep
|
||||
* existing rows valid; the seed replaces them with authored content.
|
||||
*/
|
||||
export class CreateLocalLocationView1788700000000
|
||||
implements MigrationInterface
|
||||
{
|
||||
export class CreateLocalLocationView1788700000000 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "location_definitions" ADD COLUMN "region_name" character varying(150) NOT NULL DEFAULT \'\'',
|
||||
|
||||
@@ -2,10 +2,18 @@ import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class ExtendCombatEventTypes1790000000000 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TYPE "combat_event_type_enum" ADD VALUE 'HEAL'`);
|
||||
await queryRunner.query(`ALTER TYPE "combat_event_type_enum" ADD VALUE 'DEFEND'`);
|
||||
await queryRunner.query(`ALTER TYPE "combat_event_type_enum" ADD VALUE 'TELEGRAPH'`);
|
||||
await queryRunner.query(`ALTER TYPE "combat_event_type_enum" ADD VALUE 'INTERRUPT'`);
|
||||
await queryRunner.query(
|
||||
`ALTER TYPE "combat_event_type_enum" ADD VALUE 'HEAL'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TYPE "combat_event_type_enum" ADD VALUE 'DEFEND'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TYPE "combat_event_type_enum" ADD VALUE 'TELEGRAPH'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TYPE "combat_event_type_enum" ADD VALUE 'INTERRUPT'`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
|
||||
@@ -13,7 +13,9 @@ export class CreateRenownAndReputation1791000000000 implements MigrationInterfac
|
||||
'ALTER TABLE "characters" ADD CONSTRAINT "CHK_characters_renown" CHECK ("renown" >= 1 AND "renown" <= 15)',
|
||||
);
|
||||
await queryRunner.query('ALTER TABLE "characters" DROP COLUMN "level"');
|
||||
await queryRunner.query('ALTER TABLE "characters" DROP COLUMN "experience"');
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "characters" DROP COLUMN "experience"',
|
||||
);
|
||||
|
||||
// --- ItemDefinition: drop requiredLevel (spec §14, design R4) ---
|
||||
await queryRunner.query(
|
||||
@@ -48,6 +50,14 @@ export class CreateRenownAndReputation1791000000000 implements MigrationInterfac
|
||||
'ALTER TABLE "combat_rewards" DROP COLUMN "experience_granted"',
|
||||
);
|
||||
|
||||
// --- MonsterDefinition: XP is abolished as a concept (spec §1, design R7).
|
||||
// `silver_min`/`silver_max` deliberately stay -- spec §15 keeps a direct
|
||||
// currency drop available as a lore-valid exception -- but XP has no such
|
||||
// carve-out, so the column goes with the concept. ---
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "monster_definitions" DROP COLUMN "experience_reward"',
|
||||
);
|
||||
|
||||
// --- ReputationFaction (spec §9) ---
|
||||
await queryRunner.query(`CREATE TABLE "reputation_factions" (
|
||||
"id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
@@ -141,14 +151,22 @@ export class CreateRenownAndReputation1791000000000 implements MigrationInterfac
|
||||
'DROP INDEX "IDX_character_renown_milestones_character_milestone"',
|
||||
);
|
||||
await queryRunner.query('DROP TABLE "character_renown_milestones"');
|
||||
await queryRunner.query('DROP INDEX "IDX_renown_milestone_definitions_key"');
|
||||
await queryRunner.query(
|
||||
'DROP INDEX "IDX_renown_milestone_definitions_key"',
|
||||
);
|
||||
await queryRunner.query('DROP TABLE "renown_milestone_definitions"');
|
||||
|
||||
await queryRunner.query('DROP INDEX "IDX_character_reputation_character_faction"');
|
||||
await queryRunner.query(
|
||||
'DROP INDEX "IDX_character_reputation_character_faction"',
|
||||
);
|
||||
await queryRunner.query('DROP TABLE "character_reputation"');
|
||||
await queryRunner.query('DROP INDEX "IDX_reputation_factions_key"');
|
||||
await queryRunner.query('DROP TABLE "reputation_factions"');
|
||||
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "monster_definitions" ADD COLUMN "experience_reward" integer NOT NULL DEFAULT 0',
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "combat_rewards" ADD COLUMN "experience_granted" integer NOT NULL DEFAULT 0',
|
||||
);
|
||||
@@ -175,12 +193,16 @@ export class CreateRenownAndReputation1791000000000 implements MigrationInterfac
|
||||
'ALTER TABLE "item_definitions" ADD COLUMN "required_level" integer NOT NULL DEFAULT 1',
|
||||
);
|
||||
|
||||
await queryRunner.query('ALTER TABLE "characters" ADD COLUMN "level" integer NOT NULL DEFAULT 1');
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "characters" ADD COLUMN "level" integer NOT NULL DEFAULT 1',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "characters" ADD COLUMN "experience" integer NOT NULL DEFAULT 0',
|
||||
);
|
||||
await queryRunner.query('UPDATE "characters" SET "level" = "renown"');
|
||||
await queryRunner.query('ALTER TABLE "characters" DROP CONSTRAINT "CHK_characters_renown"');
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "characters" DROP CONSTRAINT "CHK_characters_renown"',
|
||||
);
|
||||
await queryRunner.query('ALTER TABLE "characters" DROP COLUMN "renown"');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ describe('combat system schema', () => {
|
||||
const metadata = getMetadataArgsStorage();
|
||||
|
||||
const relations = metadata.relations.filter(
|
||||
(relation) => relation.target === Combat || relation.target === CombatEvent,
|
||||
(relation) =>
|
||||
relation.target === Combat || relation.target === CombatEvent,
|
||||
);
|
||||
|
||||
expect(
|
||||
@@ -19,10 +20,26 @@ describe('combat system schema', () => {
|
||||
})),
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ onDelete: 'RESTRICT', propertyName: 'character', target: Combat }),
|
||||
expect.objectContaining({ onDelete: 'RESTRICT', propertyName: 'huntEncounter', target: Combat }),
|
||||
expect.objectContaining({ onDelete: 'RESTRICT', propertyName: 'monster', target: Combat }),
|
||||
expect.objectContaining({ onDelete: 'CASCADE', propertyName: 'combat', target: CombatEvent }),
|
||||
expect.objectContaining({
|
||||
onDelete: 'RESTRICT',
|
||||
propertyName: 'character',
|
||||
target: Combat,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
onDelete: 'RESTRICT',
|
||||
propertyName: 'huntEncounter',
|
||||
target: Combat,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
onDelete: 'RESTRICT',
|
||||
propertyName: 'monster',
|
||||
target: Combat,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
onDelete: 'CASCADE',
|
||||
propertyName: 'combat',
|
||||
target: CombatEvent,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
@@ -37,7 +54,10 @@ describe('combat system schema', () => {
|
||||
);
|
||||
|
||||
expect(index).toBeDefined();
|
||||
const indexMetadata = index as typeof index & { options?: { unique?: boolean }; unique?: boolean };
|
||||
const indexMetadata = index as typeof index & {
|
||||
options?: { unique?: boolean };
|
||||
unique?: boolean;
|
||||
};
|
||||
expect(indexMetadata?.options?.unique ?? indexMetadata?.unique).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,8 @@ describe('character_equipment schema', () => {
|
||||
const metadata = getMetadataArgsStorage();
|
||||
const column = metadata.columns.find(
|
||||
(candidate) =>
|
||||
candidate.target === CharacterEquipment && candidate.propertyName === 'slot',
|
||||
candidate.target === CharacterEquipment &&
|
||||
candidate.propertyName === 'slot',
|
||||
);
|
||||
|
||||
expect(column).toBeDefined();
|
||||
|
||||
@@ -7,7 +7,8 @@ describe('combat_events.type enum', () => {
|
||||
it('includes the Playable Slice 0.6 event types', () => {
|
||||
const metadata = getMetadataArgsStorage();
|
||||
const column = metadata.columns.find(
|
||||
(candidate) => candidate.target === CombatEvent && candidate.propertyName === 'type',
|
||||
(candidate) =>
|
||||
candidate.target === CombatEvent && candidate.propertyName === 'type',
|
||||
);
|
||||
|
||||
expect(column).toBeDefined();
|
||||
|
||||
@@ -29,21 +29,31 @@ describe('loot and rewards schema', () => {
|
||||
});
|
||||
|
||||
it('keeps one stack per character per item definition', () => {
|
||||
expect(uniqueIndexFor(CharacterItem, ['characterId', 'itemDefinitionId'])).toBe(true);
|
||||
expect(
|
||||
uniqueIndexFor(CharacterItem, ['characterId', 'itemDefinitionId']),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('keeps loot-table content keys and entry positions unique', () => {
|
||||
expect(uniqueIndexFor(ItemDefinition, ['key'])).toBe(true);
|
||||
expect(uniqueIndexFor(LootTable, ['key'])).toBe(true);
|
||||
expect(uniqueIndexFor(LootTableEntry, ['lootTableId', 'position'])).toBe(true);
|
||||
expect(uniqueIndexFor(LootTableEntry, ['lootTableId', 'itemDefinitionId'])).toBe(true);
|
||||
expect(uniqueIndexFor(LootTableEntry, ['lootTableId', 'position'])).toBe(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
uniqueIndexFor(LootTableEntry, ['lootTableId', 'itemDefinitionId']),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('maps reward and loot relations with the documented onDelete behavior', () => {
|
||||
const relations = getMetadataArgsStorage().relations.filter((relation) =>
|
||||
[CombatReward, CombatRewardItem, CharacterItem, LootTableEntry, MonsterDefinition].includes(
|
||||
relation.target as never,
|
||||
),
|
||||
[
|
||||
CombatReward,
|
||||
CombatRewardItem,
|
||||
CharacterItem,
|
||||
LootTableEntry,
|
||||
MonsterDefinition,
|
||||
].includes(relation.target as never),
|
||||
);
|
||||
|
||||
expect(
|
||||
@@ -54,16 +64,56 @@ describe('loot and rewards schema', () => {
|
||||
})),
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ onDelete: 'CASCADE', propertyName: 'combat', target: CombatReward }),
|
||||
expect.objectContaining({ onDelete: 'RESTRICT', propertyName: 'character', target: CombatReward }),
|
||||
expect.objectContaining({ onDelete: 'CASCADE', propertyName: 'combatReward', target: CombatRewardItem }),
|
||||
expect.objectContaining({ onDelete: 'RESTRICT', propertyName: 'characterItem', target: CombatRewardItem }),
|
||||
expect.objectContaining({ onDelete: 'RESTRICT', propertyName: 'itemDefinition', target: CombatRewardItem }),
|
||||
expect.objectContaining({ onDelete: 'CASCADE', propertyName: 'character', target: CharacterItem }),
|
||||
expect.objectContaining({ onDelete: 'RESTRICT', propertyName: 'itemDefinition', target: CharacterItem }),
|
||||
expect.objectContaining({ onDelete: 'CASCADE', propertyName: 'lootTable', target: LootTableEntry }),
|
||||
expect.objectContaining({ onDelete: 'RESTRICT', propertyName: 'itemDefinition', target: LootTableEntry }),
|
||||
expect.objectContaining({ onDelete: 'RESTRICT', propertyName: 'lootTable', target: MonsterDefinition }),
|
||||
expect.objectContaining({
|
||||
onDelete: 'CASCADE',
|
||||
propertyName: 'combat',
|
||||
target: CombatReward,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
onDelete: 'RESTRICT',
|
||||
propertyName: 'character',
|
||||
target: CombatReward,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
onDelete: 'CASCADE',
|
||||
propertyName: 'combatReward',
|
||||
target: CombatRewardItem,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
onDelete: 'RESTRICT',
|
||||
propertyName: 'characterItem',
|
||||
target: CombatRewardItem,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
onDelete: 'RESTRICT',
|
||||
propertyName: 'itemDefinition',
|
||||
target: CombatRewardItem,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
onDelete: 'CASCADE',
|
||||
propertyName: 'character',
|
||||
target: CharacterItem,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
onDelete: 'RESTRICT',
|
||||
propertyName: 'itemDefinition',
|
||||
target: CharacterItem,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
onDelete: 'CASCADE',
|
||||
propertyName: 'lootTable',
|
||||
target: LootTableEntry,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
onDelete: 'RESTRICT',
|
||||
propertyName: 'itemDefinition',
|
||||
target: LootTableEntry,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
onDelete: 'RESTRICT',
|
||||
propertyName: 'lootTable',
|
||||
target: MonsterDefinition,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
@@ -72,14 +122,16 @@ describe('loot and rewards schema', () => {
|
||||
const columns = getMetadataArgsStorage().columns;
|
||||
|
||||
const silver = columns.find(
|
||||
(candidate) => candidate.target === Character && candidate.propertyName === 'silver',
|
||||
(candidate) =>
|
||||
candidate.target === Character && candidate.propertyName === 'silver',
|
||||
);
|
||||
expect(silver).toBeDefined();
|
||||
expect(silver?.options.type).toBe('integer');
|
||||
|
||||
const lootTableId = columns.find(
|
||||
(candidate) =>
|
||||
candidate.target === MonsterDefinition && candidate.propertyName === 'lootTableId',
|
||||
candidate.target === MonsterDefinition &&
|
||||
candidate.propertyName === 'lootTableId',
|
||||
);
|
||||
expect(lootTableId).toBeDefined();
|
||||
expect(lootTableId?.options.nullable).toBe(true);
|
||||
@@ -88,7 +140,8 @@ describe('loot and rewards schema', () => {
|
||||
it('stores drop chance as a numeric column so probabilities stay data-driven', () => {
|
||||
const dropChance = getMetadataArgsStorage().columns.find(
|
||||
(candidate) =>
|
||||
candidate.target === LootTableEntry && candidate.propertyName === 'dropChance',
|
||||
candidate.target === LootTableEntry &&
|
||||
candidate.propertyName === 'dropChance',
|
||||
);
|
||||
|
||||
expect(dropChance?.options.type).toBe('numeric');
|
||||
@@ -111,11 +164,19 @@ describe('loot and rewards schema', () => {
|
||||
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(
|
||||
'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"'),
|
||||
expect.stringContaining(
|
||||
'ALTER TABLE "monster_definitions" ADD COLUMN "loot_table_id"',
|
||||
),
|
||||
]),
|
||||
);
|
||||
|
||||
|
||||
@@ -19,9 +19,13 @@ function columnNames(target: unknown): string[] {
|
||||
function uniqueIndexFor(target: unknown, columns: string[]): boolean {
|
||||
const index = getMetadataArgsStorage().indices.find(
|
||||
(candidate) =>
|
||||
candidate.target === target && columns.every((column) => candidate.columns?.includes(column)),
|
||||
candidate.target === target &&
|
||||
columns.every((column) => candidate.columns?.includes(column)),
|
||||
);
|
||||
const meta = index as typeof index & { options?: { unique?: boolean }; unique?: boolean };
|
||||
const meta = index as typeof index & {
|
||||
options?: { unique?: boolean };
|
||||
unique?: boolean;
|
||||
};
|
||||
return (meta?.options?.unique ?? meta?.unique) === true;
|
||||
}
|
||||
|
||||
@@ -52,7 +56,9 @@ describe('Slice 0.6.5 entity metadata', () => {
|
||||
});
|
||||
|
||||
it('CharacterReputation enforces one row per character per faction', () => {
|
||||
expect(uniqueIndexFor(CharacterReputation, ['characterId', 'factionId'])).toBe(true);
|
||||
expect(
|
||||
uniqueIndexFor(CharacterReputation, ['characterId', 'factionId']),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('RenownMilestoneDefinition has a unique key', () => {
|
||||
|
||||
@@ -35,8 +35,12 @@ describe('CreateRenownAndReputation1791000000000', () => {
|
||||
it('backfills renown from level BEFORE dropping the level column', async () => {
|
||||
const up = await runUp();
|
||||
|
||||
const backfillIndex = up.findIndex((sql) => sql.includes('LEAST(GREATEST("level", 1), 15)'));
|
||||
const dropLevelIndex = up.findIndex((sql) => sql.includes('DROP COLUMN "level"'));
|
||||
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);
|
||||
@@ -47,8 +51,12 @@ describe('CreateRenownAndReputation1791000000000', () => {
|
||||
it('adds the renown range constraint only AFTER the backfill has populated valid values', async () => {
|
||||
const up = await runUp();
|
||||
|
||||
const backfillIndex = up.findIndex((sql) => sql.includes('LEAST(GREATEST("level", 1), 15)'));
|
||||
const constraintIndex = up.findIndex((sql) => sql.includes('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);
|
||||
@@ -62,7 +70,9 @@ describe('CreateRenownAndReputation1791000000000', () => {
|
||||
|
||||
expect(up).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.stringContaining('ALTER TABLE "item_definitions" DROP COLUMN "required_level"'),
|
||||
expect.stringContaining(
|
||||
'ALTER TABLE "item_definitions" DROP COLUMN "required_level"',
|
||||
),
|
||||
]),
|
||||
);
|
||||
});
|
||||
@@ -72,8 +82,12 @@ describe('CreateRenownAndReputation1791000000000', () => {
|
||||
|
||||
expect(up).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.stringContaining(`SET "type" = 'EQUIPMENT' WHERE "type" IN ('WEAPON', 'ARMOR')`),
|
||||
expect.stringContaining(`SET "type" = 'TRADE_GOOD' WHERE "type" = 'MATERIAL'`),
|
||||
expect.stringContaining(
|
||||
`SET "type" = 'EQUIPMENT' WHERE "type" IN ('WEAPON', 'ARMOR')`,
|
||||
),
|
||||
expect.stringContaining(
|
||||
`SET "type" = 'TRADE_GOOD' WHERE "type" = 'MATERIAL'`,
|
||||
),
|
||||
expect.stringContaining('DROP TYPE "item_type_enum"'),
|
||||
expect.stringContaining(
|
||||
`CREATE TYPE "item_type_enum" AS ENUM ('EQUIPMENT', 'TRADE_GOOD', 'TROPHY', 'QUEST_ITEM', 'CONSUMABLE')`,
|
||||
@@ -87,30 +101,63 @@ describe('CreateRenownAndReputation1791000000000', () => {
|
||||
|
||||
expect(up).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.stringContaining('ALTER TABLE "combat_rewards" DROP COLUMN "experience_granted"'),
|
||||
expect.stringContaining(
|
||||
'ALTER TABLE "combat_rewards" DROP COLUMN "experience_granted"',
|
||||
),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* XP is abolished outright (design R7), so the column goes with the concept.
|
||||
* It is NOT NULL with no default, so leaving it behind while the seed stops
|
||||
* supplying a value would fail every monster insert against a real database
|
||||
* -- a break no suite here can catch, since none of them connect to Postgres.
|
||||
*/
|
||||
it('drops experience_reward from monster_definitions', async () => {
|
||||
const up = await runUp();
|
||||
|
||||
expect(up).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.stringContaining(
|
||||
'ALTER TABLE "monster_definitions" DROP COLUMN "experience_reward"',
|
||||
),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps silver_min and silver_max on monster_definitions', async () => {
|
||||
const up = await runUp();
|
||||
|
||||
expect(up.join(' ')).not.toContain('"silver_min"');
|
||||
expect(up.join(' ')).not.toContain('"silver_max"');
|
||||
});
|
||||
|
||||
it('creates all five new tables with their unique constraints', async () => {
|
||||
const up = await runUp();
|
||||
|
||||
expect(up).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.stringContaining('CREATE TABLE "reputation_factions"'),
|
||||
expect.stringContaining('CREATE UNIQUE INDEX "IDX_reputation_factions_key"'),
|
||||
expect.stringContaining(
|
||||
'CREATE UNIQUE INDEX "IDX_reputation_factions_key"',
|
||||
),
|
||||
expect.stringContaining('CREATE TABLE "character_reputation"'),
|
||||
expect.stringContaining(
|
||||
'CREATE UNIQUE INDEX "IDX_character_reputation_character_faction" ON "character_reputation" ("character_id", "faction_id")',
|
||||
),
|
||||
expect.stringContaining('CREATE TABLE "renown_milestone_definitions"'),
|
||||
expect.stringContaining('CREATE UNIQUE INDEX "IDX_renown_milestone_definitions_key"'),
|
||||
expect.stringContaining(
|
||||
'CREATE UNIQUE INDEX "IDX_renown_milestone_definitions_key"',
|
||||
),
|
||||
expect.stringContaining('CREATE TABLE "character_renown_milestones"'),
|
||||
expect.stringContaining(
|
||||
'CREATE UNIQUE INDEX "IDX_character_renown_milestones_character_milestone" ON "character_renown_milestones" ("character_id", "milestone_id")',
|
||||
),
|
||||
expect.stringContaining('CREATE TABLE "turn_in_definitions"'),
|
||||
expect.stringContaining('CREATE UNIQUE INDEX "IDX_turn_in_definitions_key"'),
|
||||
expect.stringContaining(
|
||||
'CREATE UNIQUE INDEX "IDX_turn_in_definitions_key"',
|
||||
),
|
||||
]),
|
||||
);
|
||||
});
|
||||
@@ -125,14 +172,27 @@ describe('CreateRenownAndReputation1791000000000', () => {
|
||||
expect.stringContaining('DROP TABLE "renown_milestone_definitions"'),
|
||||
expect.stringContaining('DROP TABLE "character_reputation"'),
|
||||
expect.stringContaining('DROP TABLE "reputation_factions"'),
|
||||
expect.stringContaining('ALTER TABLE "combat_rewards" ADD COLUMN "experience_granted"'),
|
||||
expect.stringContaining('ALTER TABLE "item_definitions" ADD COLUMN "required_level"'),
|
||||
expect.stringContaining(
|
||||
'ALTER TABLE "monster_definitions" ADD COLUMN "experience_reward"',
|
||||
),
|
||||
expect.stringContaining(
|
||||
'ALTER TABLE "combat_rewards" ADD COLUMN "experience_granted"',
|
||||
),
|
||||
expect.stringContaining(
|
||||
'ALTER TABLE "item_definitions" ADD COLUMN "required_level"',
|
||||
),
|
||||
expect.stringContaining('ALTER TABLE "characters" ADD COLUMN "level"'),
|
||||
expect.stringContaining('ALTER TABLE "characters" ADD COLUMN "experience"'),
|
||||
expect.stringContaining('ALTER TABLE "characters" DROP COLUMN "renown"'),
|
||||
expect.stringContaining(
|
||||
'ALTER TABLE "characters" ADD COLUMN "experience"',
|
||||
),
|
||||
expect.stringContaining(
|
||||
'ALTER TABLE "characters" DROP COLUMN "renown"',
|
||||
),
|
||||
]),
|
||||
);
|
||||
const turnInDropIndex = down.findIndex((sql) => sql.includes('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"'),
|
||||
);
|
||||
|
||||
@@ -129,7 +129,10 @@ describe('seedVisibleVerticalSlice', () => {
|
||||
Object.assign(characterRepository.rows[0], {
|
||||
currentLocationId: BURNED_ROAD_ID,
|
||||
currentHp: 57,
|
||||
renown: 1,
|
||||
// Deliberately NOT the seed default of 1: if a re-seed clobbered player
|
||||
// state back to defaults, an assertion on the default value could not
|
||||
// tell the difference.
|
||||
renown: 5,
|
||||
});
|
||||
await seedVisibleVerticalSlice(dataSource);
|
||||
|
||||
@@ -168,7 +171,7 @@ describe('seedVisibleVerticalSlice', () => {
|
||||
expect.objectContaining({
|
||||
currentLocationId: BURNED_ROAD_ID,
|
||||
currentHp: 57,
|
||||
renown: 1,
|
||||
renown: 5,
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user