feat: seed Aschenratte and Straßenräuber for Verbrannte Straße

Adds the two starter monster definitions and their location-monster
mapping (weights 70/30) to the vertical-slice seed, following the
same findOneBy/update-or-insert pattern used for locations, plus an
upsert on (locationId, monsterId) for the mapping. Copies the source
artwork into the served images/monsters directory and extends the
seed spec harness to cover both idempotent inserts.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Bastian Wagner
2026-08-19 12:08:26 +02:00
parent e04827cd5a
commit 9423c28bd8
5 changed files with 154 additions and 2 deletions

View File

@@ -1,5 +1,7 @@
import { DataSource } from 'typeorm';
import { Character } from '../../characters/entities/character.entity';
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 { seedVisibleVerticalSlice } from './vertical-slice.seed';
@@ -9,6 +11,8 @@ type Row = Record<string, unknown>;
const DEMO_CHARACTER_ID = '10000000-0000-4000-8000-000000000001';
const SOUTH_GATE_ID = '20000000-0000-4000-8000-000000000001';
const BURNED_ROAD_ID = '20000000-0000-4000-8000-000000000002';
const ASH_RAT_MONSTER_ID = '30000000-0000-4000-8000-000000000001';
const ROAD_BANDIT_MONSTER_ID = '30000000-0000-4000-8000-000000000002';
class InMemoryRepository {
readonly rows: Row[] = [];
@@ -61,6 +65,8 @@ function createDataSource(
locationRepository: InMemoryRepository,
connectionRepository: InMemoryRepository,
characterRepository: InMemoryRepository,
monsterRepository: InMemoryRepository,
locationMonsterRepository: InMemoryRepository,
): DataSource {
return {
getRepository: jest.fn((entity: unknown) => {
@@ -73,6 +79,12 @@ function createDataSource(
if (entity === Character) {
return characterRepository;
}
if (entity === MonsterDefinition) {
return monsterRepository;
}
if (entity === LocationMonster) {
return locationMonsterRepository;
}
throw new Error('Unexpected repository');
}),
@@ -84,10 +96,14 @@ describe('seedVisibleVerticalSlice', () => {
const locationRepository = new InMemoryRepository();
const connectionRepository = new InMemoryRepository();
const characterRepository = new InMemoryRepository();
const monsterRepository = new InMemoryRepository();
const locationMonsterRepository = new InMemoryRepository();
const dataSource = createDataSource(
locationRepository,
connectionRepository,
characterRepository,
monsterRepository,
locationMonsterRepository,
);
await seedVisibleVerticalSlice(dataSource);
@@ -136,13 +152,63 @@ describe('seedVisibleVerticalSlice', () => {
experience: 39,
}),
);
expect(monsterRepository.insert).toHaveBeenCalledTimes(2);
expect(monsterRepository.rows).toHaveLength(2);
expect(monsterRepository.rows).toEqual(
expect.arrayContaining([
expect.objectContaining({
key: 'ash-rat',
name: 'Aschenratte',
level: 1,
maxHp: 45,
attack: 5,
armor: 0,
experienceReward: 8,
silverMin: 4,
silverMax: 7,
artworkPath: '/images/monsters/ash-rat.png',
}),
expect.objectContaining({
key: 'road-bandit',
name: 'Straßenräuber',
level: 2,
maxHp: 75,
attack: 9,
armor: 5,
experienceReward: 16,
silverMin: 9,
silverMax: 15,
artworkPath: '/images/monsters/road-bandit.png',
}),
]),
);
expect(locationMonsterRepository.upsert).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({
locationId: BURNED_ROAD_ID,
monsterId: ASH_RAT_MONSTER_ID,
weight: 70,
}),
expect.objectContaining({
locationId: BURNED_ROAD_ID,
monsterId: ROAD_BANDIT_MONSTER_ID,
weight: 30,
}),
]),
['locationId', 'monsterId'],
);
expect(locationMonsterRepository.rows).toHaveLength(2);
});
it('preserves existing location IDs and uses them for the directed connections', async () => {
const locationRepository = new InMemoryRepository();
const connectionRepository = new InMemoryRepository();
const characterRepository = new InMemoryRepository();
const persistedSouthGateId = '30000000-0000-4000-8000-000000000001';
const monsterRepository = new InMemoryRepository();
const locationMonsterRepository = new InMemoryRepository();
const persistedSouthGateId = '40000000-0000-4000-8000-000000000001';
locationRepository.rows.push({
id: persistedSouthGateId,
key: 'south-gate',
@@ -152,6 +218,8 @@ describe('seedVisibleVerticalSlice', () => {
locationRepository,
connectionRepository,
characterRepository,
monsterRepository,
locationMonsterRepository,
);
await seedVisibleVerticalSlice(dataSource);