diff --git a/apps/api/src/database/seeds/vertical-slice.seed.spec.ts b/apps/api/src/database/seeds/vertical-slice.seed.spec.ts index c24015f..df32221 100644 --- a/apps/api/src/database/seeds/vertical-slice.seed.spec.ts +++ b/apps/api/src/database/seeds/vertical-slice.seed.spec.ts @@ -1,5 +1,7 @@ import { DataSource } from 'typeorm'; import { Character } from '../../characters/entities/character.entity'; +import { CharacterEquipment } from '../../equipment/entities/character-equipment.entity'; +import { CharacterItem } from '../../items/entities/character-item.entity'; import { ItemDefinition } from '../../items/entities/item-definition.entity'; import { LootTableEntry } from '../../loot/entities/loot-table-entry.entity'; import { LootTable } from '../../loot/entities/loot-table.entity'; @@ -7,6 +9,7 @@ import { LocationMonster } from '../../monsters/entities/location-monster.entity import { MonsterDefinition } from '../../monsters/entities/monster-definition.entity'; import { LocationConnection } from '../../world/entities/location-connection.entity'; import { LocationDefinition } from '../../world/entities/location-definition.entity'; +import { DEMO_CHARACTER_STARTING_WEAPON_ITEM_ID } from '../../demo/demo-character.constants'; import { ASH_RAT_LOOT_TABLE_ID, ITEM_IDS, ROAD_BANDIT_LOOT_TABLE_ID } from './item.constants'; import { seedVisibleVerticalSlice } from './vertical-slice.seed'; @@ -74,6 +77,8 @@ function createDataSource( itemRepository: InMemoryRepository = new InMemoryRepository(), lootTableRepository: InMemoryRepository = new InMemoryRepository(), lootEntryRepository: InMemoryRepository = new InMemoryRepository(), + characterItemRepository: InMemoryRepository = new InMemoryRepository(), + characterEquipmentRepository: InMemoryRepository = new InMemoryRepository(), ): DataSource { return { getRepository: jest.fn((entity: unknown) => { @@ -85,6 +90,8 @@ function createDataSource( if (entity === ItemDefinition) return itemRepository; if (entity === LootTable) return lootTableRepository; if (entity === LootTableEntry) return lootEntryRepository; + if (entity === CharacterItem) return characterItemRepository; + if (entity === CharacterEquipment) return characterEquipmentRepository; throw new Error('Unexpected repository'); }), @@ -336,4 +343,80 @@ describe('seedVisibleVerticalSlice', () => { ]), ); }); + + it('seeds the starting sword as a real, equipped CharacterItem idempotently', async () => { + const locationRepository = new InMemoryRepository(); + const connectionRepository = new InMemoryRepository(); + const characterRepository = new InMemoryRepository(); + const monsterRepository = new InMemoryRepository(); + const locationMonsterRepository = new InMemoryRepository(); + const characterItemRepository = new InMemoryRepository(); + const characterEquipmentRepository = new InMemoryRepository(); + const dataSource = createDataSource( + locationRepository, + connectionRepository, + characterRepository, + monsterRepository, + locationMonsterRepository, + new InMemoryRepository(), + new InMemoryRepository(), + new InMemoryRepository(), + characterItemRepository, + characterEquipmentRepository, + ); + + await seedVisibleVerticalSlice(dataSource); + await seedVisibleVerticalSlice(dataSource); + + expect(characterItemRepository.rows).toHaveLength(1); + expect(characterItemRepository.rows[0]).toEqual( + expect.objectContaining({ + characterId: DEMO_CHARACTER_ID, + itemDefinitionId: ITEM_IDS['worn-short-sword'], + quantity: 1, + }), + ); + expect(characterEquipmentRepository.rows).toHaveLength(1); + expect(characterEquipmentRepository.rows[0]).toEqual( + expect.objectContaining({ + characterId: DEMO_CHARACTER_ID, + slot: 'WEAPON', + characterItemId: DEMO_CHARACTER_STARTING_WEAPON_ITEM_ID, + }), + ); + }); + + it('never re-equips the starting sword once the player has equipped different gear', async () => { + const locationRepository = new InMemoryRepository(); + const connectionRepository = new InMemoryRepository(); + const characterRepository = new InMemoryRepository(); + const monsterRepository = new InMemoryRepository(); + const locationMonsterRepository = new InMemoryRepository(); + const characterItemRepository = new InMemoryRepository(); + const characterEquipmentRepository = new InMemoryRepository(); + const dataSource = createDataSource( + locationRepository, + connectionRepository, + characterRepository, + monsterRepository, + locationMonsterRepository, + new InMemoryRepository(), + new InMemoryRepository(), + new InMemoryRepository(), + characterItemRepository, + characterEquipmentRepository, + ); + + await seedVisibleVerticalSlice(dataSource); + // Simulate the player having equipped earned loot instead. + characterEquipmentRepository.rows[0]['characterItemId'] = 'earned-bandit-blade-item-id'; + + await seedVisibleVerticalSlice(dataSource); + + expect(characterEquipmentRepository.rows).toHaveLength(1); + expect(characterEquipmentRepository.rows[0]['characterItemId']).toBe( + 'earned-bandit-blade-item-id', + ); + expect(characterItemRepository.rows).toHaveLength(1); + }); }); diff --git a/apps/api/src/database/seeds/vertical-slice.seed.ts b/apps/api/src/database/seeds/vertical-slice.seed.ts index 92add28..8af98bc 100644 --- a/apps/api/src/database/seeds/vertical-slice.seed.ts +++ b/apps/api/src/database/seeds/vertical-slice.seed.ts @@ -1,6 +1,13 @@ import { DataSource } from 'typeorm'; -import { DEMO_CHARACTER_ID } from '../../demo/demo-character.constants'; +import { + DEMO_CHARACTER_ID, + DEMO_CHARACTER_STARTING_WEAPON_EQUIPMENT_ID, + DEMO_CHARACTER_STARTING_WEAPON_ITEM_ID, +} from '../../demo/demo-character.constants'; import { Character } from '../../characters/entities/character.entity'; +import { CharacterEquipment } from '../../equipment/entities/character-equipment.entity'; +import { CharacterItem } from '../../items/entities/character-item.entity'; +import { EquipmentSlot } from '../../items/equipment-slot.enum'; import { ItemDefinition } from '../../items/entities/item-definition.entity'; import { LootTableEntry } from '../../loot/entities/loot-table-entry.entity'; import { LootTable } from '../../loot/entities/loot-table.entity'; @@ -11,6 +18,7 @@ import { LocationConnection } from '../../world/entities/location-connection.ent import { LocationDefinition } from '../../world/entities/location-definition.entity'; import { ASH_RAT_LOOT_TABLE_ID, + ITEM_IDS, ROAD_BANDIT_LOOT_TABLE_ID, } from './item.constants'; import { ITEM_DEFINITIONS, LOOT_TABLES, LOOT_TABLE_ENTRIES } from './item-content'; @@ -205,4 +213,32 @@ export async function seedVisibleVerticalSlice( currentLocationId: southGateId, }); } + + const characterItemRepository = dataSource.getRepository(CharacterItem); + const characterEquipmentRepository = dataSource.getRepository(CharacterEquipment); + + const existingStartingSword = await characterItemRepository.findOneBy({ + id: DEMO_CHARACTER_STARTING_WEAPON_ITEM_ID, + }); + if (!existingStartingSword) { + await characterItemRepository.insert({ + id: DEMO_CHARACTER_STARTING_WEAPON_ITEM_ID, + characterId: DEMO_CHARACTER_ID, + itemDefinitionId: ITEM_IDS['worn-short-sword'], + quantity: 1, + }); + } + + const existingWeaponEquipment = await characterEquipmentRepository.findOneBy({ + characterId: DEMO_CHARACTER_ID, + slot: EquipmentSlot.WEAPON, + }); + if (!existingWeaponEquipment) { + await characterEquipmentRepository.insert({ + id: DEMO_CHARACTER_STARTING_WEAPON_EQUIPMENT_ID, + characterId: DEMO_CHARACTER_ID, + slot: EquipmentSlot.WEAPON, + characterItemId: DEMO_CHARACTER_STARTING_WEAPON_ITEM_ID, + }); + } } diff --git a/apps/api/src/demo/demo-character.constants.ts b/apps/api/src/demo/demo-character.constants.ts index e79635b..f09bf6e 100644 --- a/apps/api/src/demo/demo-character.constants.ts +++ b/apps/api/src/demo/demo-character.constants.ts @@ -1 +1,5 @@ export const DEMO_CHARACTER_ID = '10000000-0000-4000-8000-000000000001'; +export const DEMO_CHARACTER_STARTING_WEAPON_ITEM_ID = + '10000000-0000-4000-8000-000000000002'; +export const DEMO_CHARACTER_STARTING_WEAPON_EQUIPMENT_ID = + '10000000-0000-4000-8000-000000000003';