feat(api): reveal the ash pit route from the watchpost hotspot

This commit is contained in:
Bastian Wagner
2026-08-23 11:18:20 +02:00
parent aaa7146c0c
commit 37d025f401
5 changed files with 309 additions and 11 deletions

View File

@@ -6,6 +6,7 @@ import {
} from '../database/seeds/vertical-slice.constants';
import { LocationMonster } from '../monsters/entities/location-monster.entity';
import { TravelService } from '../travel/travel.service';
import { WorldDiscoveryService } from './discovery/world-discovery.service';
import { LocationConnection } from './entities/location-connection.entity';
import { LocationDefinition } from './entities/location-definition.entity';
import type { LocationPointOfInterestContent } from './local-location.types';
@@ -92,9 +93,51 @@ function createService(
} as unknown as Repository<Character>,
{ find: jest.fn() } as unknown as Repository<LocationConnection>,
{ find: jest.fn() } as unknown as Repository<LocationMonster>,
{
isTravelAllowed: () => Promise.resolve(true),
discover: jest.fn(),
} as unknown as WorldDiscoveryService,
);
}
/**
* Builds a `WorldService` sitting at the Burned Road with the given points of
* interest, and a stub `WorldDiscoveryService` whose `discover` mirrors the
* real one: it returns the ash pit the first time and `null` once
* `alreadyDiscovered` says the character already knows it.
*/
function buildService(options: {
pointsOfInterest: LocationPointOfInterestContent[];
alreadyDiscovered?: boolean;
}) {
const currentLocation = location(BURNED_ROAD_ID, options.pointsOfInterest);
const discover = jest.fn().mockResolvedValue(
options.alreadyDiscovered ? null : { key: 'ash-pit', name: 'Ash Pit' },
);
const worldDiscovery = {
discover,
isTravelAllowed: () => Promise.resolve(true),
} as unknown as WorldDiscoveryService;
const service = new WorldService(
{
completeTravelIfDue: jest.fn().mockResolvedValue({ status: 'IDLE' }),
} as unknown as TravelService,
{
findOne: jest.fn().mockResolvedValue({
id: CHARACTER_ID,
currentLocationId: currentLocation.id,
currentLocation,
}),
} as unknown as Repository<Character>,
{ find: jest.fn() } as unknown as Repository<LocationConnection>,
{ find: jest.fn() } as unknown as Repository<LocationMonster>,
worldDiscovery,
);
return { service, discover };
}
async function expectRejected(promise: Promise<unknown>): Promise<void> {
await expect(promise).rejects.toBeInstanceOf(WorldDomainError);
await expect(promise).rejects.toMatchObject({
@@ -112,6 +155,7 @@ describe('WorldService.runLocalInteraction', () => {
interactionKey: 'inspect-tracks',
title: 'Suspicious Tracks',
text: 'Between the ash and broken stones you make out several fresh bootprints.',
discoveredLocation: null,
});
});
@@ -125,6 +169,7 @@ describe('WorldService.runLocalInteraction', () => {
title: 'Gate Watch',
text: 'Only heard at the South Gate.',
img: '/images/npcs/graufurt-gate-watch.png',
discoveredLocation: null,
});
});
@@ -171,4 +216,87 @@ describe('WorldService.runLocalInteraction', () => {
service.runLocalInteraction(CHARACTER_ID, 'hunt-area'),
);
});
it('discovers the route the hotspot points at', async () => {
const { service, discover } = buildService({
pointsOfInterest: [
{
key: 'inspect-watchpost',
title: 'The Watchpost',
actionLabel: 'Inspect',
type: 'INVESTIGATE',
iconKey: 'investigate',
xPercent: 50,
yPercent: 50,
enabled: true,
resultTitle: 'The Watchpost',
resultText: 'Fresh tracks lead east.',
discoversLocationKey: 'ash-pit',
},
],
});
const result = await service.runLocalInteraction(
CHARACTER_ID,
'inspect-watchpost',
);
expect(discover).toHaveBeenCalledWith(CHARACTER_ID, 'ash-pit');
expect(result.discoveredLocation).toEqual({
key: 'ash-pit',
name: 'Ash Pit',
});
});
it('reports no discovery the second time the hotspot is used', async () => {
const { service } = buildService({
alreadyDiscovered: true,
pointsOfInterest: [
{
key: 'inspect-watchpost',
title: 'The Watchpost',
type: 'INVESTIGATE',
iconKey: 'investigate',
xPercent: 50,
yPercent: 50,
enabled: true,
resultText: 'Fresh tracks lead east.',
discoversLocationKey: 'ash-pit',
},
],
});
const result = await service.runLocalInteraction(
CHARACTER_ID,
'inspect-watchpost',
);
expect(result.discoveredLocation).toBeNull();
expect(result.text).toBe('Fresh tracks lead east.');
});
it('reports no discovery for a hotspot that reveals nothing', async () => {
const { service, discover } = buildService({
pointsOfInterest: [
{
key: 'search-quarters',
title: 'Guard Quarters',
type: 'SEARCH',
iconKey: 'search',
xPercent: 20,
yPercent: 60,
enabled: true,
resultText: 'Nothing but ash.',
},
],
});
const result = await service.runLocalInteraction(
CHARACTER_ID,
'search-quarters',
);
expect(discover).not.toHaveBeenCalled();
expect(result.discoveredLocation).toBeNull();
});
});