fix: preserve seeded location identities
This commit is contained in:
@@ -1,4 +1,7 @@
|
|||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
|
import { Character } from '../../characters/entities/character.entity';
|
||||||
|
import { LocationConnection } from '../../world/entities/location-connection.entity';
|
||||||
|
import { LocationDefinition } from '../../world/entities/location-definition.entity';
|
||||||
import { seedVisibleVerticalSlice } from './vertical-slice.seed';
|
import { seedVisibleVerticalSlice } from './vertical-slice.seed';
|
||||||
|
|
||||||
type Row = Record<string, unknown>;
|
type Row = Record<string, unknown>;
|
||||||
@@ -39,6 +42,41 @@ class InMemoryRepository {
|
|||||||
readonly insert = jest.fn(async (value: Row) => {
|
readonly insert = jest.fn(async (value: Row) => {
|
||||||
this.rows.push({ ...value });
|
this.rows.push({ ...value });
|
||||||
});
|
});
|
||||||
|
readonly update = jest.fn(async (criteria: string | Row, value: Row) => {
|
||||||
|
const row = this.rows.find((candidate) =>
|
||||||
|
typeof criteria === 'string'
|
||||||
|
? candidate.id === criteria
|
||||||
|
: Object.entries(criteria).every(
|
||||||
|
([key, expected]) => candidate[key] === expected,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (row) {
|
||||||
|
Object.assign(row, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDataSource(
|
||||||
|
locationRepository: InMemoryRepository,
|
||||||
|
connectionRepository: InMemoryRepository,
|
||||||
|
characterRepository: InMemoryRepository,
|
||||||
|
): DataSource {
|
||||||
|
return {
|
||||||
|
getRepository: jest.fn((entity: unknown) => {
|
||||||
|
if (entity === LocationDefinition) {
|
||||||
|
return locationRepository;
|
||||||
|
}
|
||||||
|
if (entity === LocationConnection) {
|
||||||
|
return connectionRepository;
|
||||||
|
}
|
||||||
|
if (entity === Character) {
|
||||||
|
return characterRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Unexpected repository');
|
||||||
|
}),
|
||||||
|
} as unknown as DataSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('seedVisibleVerticalSlice', () => {
|
describe('seedVisibleVerticalSlice', () => {
|
||||||
@@ -46,27 +84,20 @@ describe('seedVisibleVerticalSlice', () => {
|
|||||||
const locationRepository = new InMemoryRepository();
|
const locationRepository = new InMemoryRepository();
|
||||||
const connectionRepository = new InMemoryRepository();
|
const connectionRepository = new InMemoryRepository();
|
||||||
const characterRepository = new InMemoryRepository();
|
const characterRepository = new InMemoryRepository();
|
||||||
const dataSource = {
|
const dataSource = createDataSource(
|
||||||
getRepository: jest
|
locationRepository,
|
||||||
.fn()
|
connectionRepository,
|
||||||
.mockReturnValueOnce(locationRepository)
|
characterRepository,
|
||||||
.mockReturnValueOnce(connectionRepository)
|
|
||||||
.mockReturnValueOnce(characterRepository)
|
|
||||||
.mockReturnValueOnce(locationRepository)
|
|
||||||
.mockReturnValueOnce(connectionRepository)
|
|
||||||
.mockReturnValueOnce(characterRepository),
|
|
||||||
} as unknown as DataSource;
|
|
||||||
|
|
||||||
await seedVisibleVerticalSlice(dataSource);
|
|
||||||
await seedVisibleVerticalSlice(dataSource);
|
|
||||||
|
|
||||||
expect(locationRepository.upsert).toHaveBeenCalledWith(
|
|
||||||
expect.arrayContaining([
|
|
||||||
expect.objectContaining({ id: SOUTH_GATE_ID, key: 'south-gate' }),
|
|
||||||
expect.objectContaining({ id: BURNED_ROAD_ID, key: 'burned-road' }),
|
|
||||||
]),
|
|
||||||
['key'],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await seedVisibleVerticalSlice(dataSource);
|
||||||
|
Object.assign(characterRepository.rows[0], {
|
||||||
|
currentLocationId: BURNED_ROAD_ID,
|
||||||
|
currentHp: 57,
|
||||||
|
experience: 39,
|
||||||
|
});
|
||||||
|
await seedVisibleVerticalSlice(dataSource);
|
||||||
|
|
||||||
expect(connectionRepository.upsert).toHaveBeenCalledWith(
|
expect(connectionRepository.upsert).toHaveBeenCalledWith(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@@ -86,5 +117,57 @@ describe('seedVisibleVerticalSlice', () => {
|
|||||||
expect(locationRepository.rows).toHaveLength(2);
|
expect(locationRepository.rows).toHaveLength(2);
|
||||||
expect(connectionRepository.rows).toHaveLength(2);
|
expect(connectionRepository.rows).toHaveLength(2);
|
||||||
expect(characterRepository.rows).toHaveLength(1);
|
expect(characterRepository.rows).toHaveLength(1);
|
||||||
|
expect(characterRepository.rows[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
currentLocationId: BURNED_ROAD_ID,
|
||||||
|
currentHp: 57,
|
||||||
|
experience: 39,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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';
|
||||||
|
locationRepository.rows.push({
|
||||||
|
id: persistedSouthGateId,
|
||||||
|
key: 'south-gate',
|
||||||
|
name: 'Veraltetes Südtor',
|
||||||
|
});
|
||||||
|
const dataSource = createDataSource(
|
||||||
|
locationRepository,
|
||||||
|
connectionRepository,
|
||||||
|
characterRepository,
|
||||||
|
);
|
||||||
|
|
||||||
|
await seedVisibleVerticalSlice(dataSource);
|
||||||
|
|
||||||
|
expect(locationRepository.rows).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: persistedSouthGateId,
|
||||||
|
key: 'south-gate',
|
||||||
|
name: 'Südtor von Graufurt',
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: BURNED_ROAD_ID,
|
||||||
|
key: 'burned-road',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
expect(connectionRepository.rows).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
fromLocationId: persistedSouthGateId,
|
||||||
|
toLocationId: BURNED_ROAD_ID,
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
fromLocationId: BURNED_ROAD_ID,
|
||||||
|
toLocationId: persistedSouthGateId,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,52 +12,71 @@ export async function seedVisibleVerticalSlice(
|
|||||||
const connectionRepository = dataSource.getRepository(LocationConnection);
|
const connectionRepository = dataSource.getRepository(LocationConnection);
|
||||||
const characterRepository = dataSource.getRepository(Character);
|
const characterRepository = dataSource.getRepository(Character);
|
||||||
|
|
||||||
await locationRepository.upsert(
|
const locations = [
|
||||||
[
|
{
|
||||||
{
|
id: SOUTH_GATE_ID,
|
||||||
id: SOUTH_GATE_ID,
|
key: 'south-gate',
|
||||||
key: 'south-gate',
|
name: 'Südtor von Graufurt',
|
||||||
name: 'Südtor von Graufurt',
|
description:
|
||||||
description:
|
'Am schwarzen Südtor endet der Schutz Graufurts. Hinter den Wachtfeuern beginnt die stille Weite der Aschenfelder.',
|
||||||
'Am schwarzen Südtor endet der Schutz Graufurts. Hinter den Wachtfeuern beginnt die stille Weite der Aschenfelder.',
|
regionKey: 'ashen-fields',
|
||||||
regionKey: 'ashen-fields',
|
minRecommendedLevel: 1,
|
||||||
minRecommendedLevel: 1,
|
maxRecommendedLevel: 1,
|
||||||
maxRecommendedLevel: 1,
|
dangerLevel: 0,
|
||||||
dangerLevel: 0,
|
isSafe: true,
|
||||||
isSafe: true,
|
huntingEnabled: false,
|
||||||
huntingEnabled: false,
|
artworkPath: '/assets/locations/south-gate.webp',
|
||||||
artworkPath: '/assets/locations/south-gate.webp',
|
},
|
||||||
},
|
{
|
||||||
{
|
id: BURNED_ROAD_ID,
|
||||||
id: BURNED_ROAD_ID,
|
key: 'burned-road',
|
||||||
key: 'burned-road',
|
name: 'Verbrannte Straße',
|
||||||
name: 'Verbrannte Straße',
|
description:
|
||||||
description:
|
'Die alte Handelsstraße führt durch verkohlte Felder. Zwischen Asche und zerbrochenen Wagen warten die ersten Gefahren.',
|
||||||
'Die alte Handelsstraße führt durch verkohlte Felder. Zwischen Asche und zerbrochenen Wagen warten die ersten Gefahren.',
|
regionKey: 'ashen-fields',
|
||||||
regionKey: 'ashen-fields',
|
minRecommendedLevel: 1,
|
||||||
minRecommendedLevel: 1,
|
maxRecommendedLevel: 2,
|
||||||
maxRecommendedLevel: 2,
|
dangerLevel: 1,
|
||||||
dangerLevel: 1,
|
isSafe: false,
|
||||||
isSafe: false,
|
huntingEnabled: true,
|
||||||
huntingEnabled: true,
|
artworkPath: '/assets/locations/burned-road.webp',
|
||||||
artworkPath: '/assets/locations/burned-road.webp',
|
},
|
||||||
},
|
];
|
||||||
],
|
let southGateId = SOUTH_GATE_ID;
|
||||||
['key'],
|
let burnedRoadId = BURNED_ROAD_ID;
|
||||||
);
|
|
||||||
|
for (const location of locations) {
|
||||||
|
const existing = await locationRepository.findOneBy({
|
||||||
|
key: location.key,
|
||||||
|
});
|
||||||
|
const { id, key, ...definition } = location;
|
||||||
|
const persistedId = existing?.id ?? id;
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
await locationRepository.update(existing.id, definition);
|
||||||
|
} else {
|
||||||
|
await locationRepository.insert(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'south-gate') {
|
||||||
|
southGateId = persistedId;
|
||||||
|
} else {
|
||||||
|
burnedRoadId = persistedId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await connectionRepository.upsert(
|
await connectionRepository.upsert(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
fromLocationId: SOUTH_GATE_ID,
|
fromLocationId: southGateId,
|
||||||
toLocationId: BURNED_ROAD_ID,
|
toLocationId: burnedRoadId,
|
||||||
travelDurationSeconds: 10,
|
travelDurationSeconds: 10,
|
||||||
ambushChance: '0.0500',
|
ambushChance: '0.0500',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fromLocationId: BURNED_ROAD_ID,
|
fromLocationId: burnedRoadId,
|
||||||
toLocationId: SOUTH_GATE_ID,
|
toLocationId: southGateId,
|
||||||
travelDurationSeconds: 10,
|
travelDurationSeconds: 10,
|
||||||
ambushChance: '0.0500',
|
ambushChance: '0.0500',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user