test: cover the successful travel start happy path in the e2e smoke suite
The DB-gated e2e suite covered health, seeded reads, and the arrivesAt whitelist rejection, but never asserted that a valid POST /api/travel actually transitions to TRAVELLING, that a concurrent second start is rejected with 409/TRAVEL_ALREADY_ACTIVE, or that a travel completes and moves the character. Added one test covering all three plus GET /api/world/current-location reflecting the move, and restores the demo character to its original location afterward so the suite stays safely re-runnable.
This commit is contained in:
@@ -152,5 +152,104 @@ describe('Visible vertical slice smoke (e2e)', () => {
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it(
|
||||
'POST /api/travel starts a travel, rejects a concurrent start, and completes into the moved character (full happy path)',
|
||||
async () => {
|
||||
// `GET current-location` lazily completes any overdue travel left
|
||||
// behind by a previous run before we read the starting point, so
|
||||
// this test is safe to re-run without a fresh seed.
|
||||
const origin = await request(app.getHttpServer())
|
||||
.get('/api/world/current-location')
|
||||
.expect(200);
|
||||
const originLocationId: string = origin.body.id;
|
||||
const outbound = origin.body.connections[0];
|
||||
const targetLocationId: string = outbound.targetLocation.id;
|
||||
const travelDurationSeconds: number = outbound.travelDurationSeconds;
|
||||
|
||||
const started = await request(app.getHttpServer())
|
||||
.post('/api/travel')
|
||||
.send({ targetLocationId })
|
||||
.expect(201);
|
||||
|
||||
expect(started.body).toMatchObject({
|
||||
status: 'TRAVELLING',
|
||||
targetLocation: { id: targetLocationId },
|
||||
});
|
||||
expect(typeof started.body.arrivesAt).toBe('string');
|
||||
expect(Number.isNaN(Date.parse(started.body.arrivesAt))).toBe(false);
|
||||
|
||||
const concurrentStart = await request(app.getHttpServer())
|
||||
.post('/api/travel')
|
||||
.send({ targetLocationId })
|
||||
.expect(409);
|
||||
expect(concurrentStart.body).toMatchObject({
|
||||
code: 'TRAVEL_ALREADY_ACTIVE',
|
||||
});
|
||||
|
||||
const arrivedTravel = await pollUntilTravelCompletes(
|
||||
app,
|
||||
travelDurationSeconds,
|
||||
);
|
||||
expect(arrivedTravel).toMatchObject({
|
||||
status: 'COMPLETED',
|
||||
targetLocation: { id: targetLocationId },
|
||||
});
|
||||
|
||||
const arrivedLocation = await request(app.getHttpServer())
|
||||
.get('/api/world/current-location')
|
||||
.expect(200);
|
||||
expect(arrivedLocation.body.id).toBe(targetLocationId);
|
||||
|
||||
// Restore the demo character to its original location so the suite
|
||||
// (and this test) stays safely re-runnable.
|
||||
const returnConnection = arrivedLocation.body.connections.find(
|
||||
(connection: { targetLocation: { id: string } }) =>
|
||||
connection.targetLocation.id === originLocationId,
|
||||
);
|
||||
expect(returnConnection).toBeDefined();
|
||||
|
||||
await request(app.getHttpServer())
|
||||
.post('/api/travel')
|
||||
.send({ targetLocationId: originLocationId })
|
||||
.expect(201);
|
||||
|
||||
const returnedTravel = await pollUntilTravelCompletes(
|
||||
app,
|
||||
returnConnection.travelDurationSeconds,
|
||||
);
|
||||
expect(returnedTravel).toMatchObject({
|
||||
status: 'COMPLETED',
|
||||
targetLocation: { id: originLocationId },
|
||||
});
|
||||
|
||||
const restoredLocation = await request(app.getHttpServer())
|
||||
.get('/api/world/current-location')
|
||||
.expect(200);
|
||||
expect(restoredLocation.body.id).toBe(originLocationId);
|
||||
},
|
||||
30_000,
|
||||
);
|
||||
|
||||
async function pollUntilTravelCompletes(
|
||||
application: INestApplication<App>,
|
||||
travelDurationSeconds: number,
|
||||
): Promise<{ status: string; targetLocation?: { id: string } }> {
|
||||
const deadline = Date.now() + travelDurationSeconds * 1000 + 5_000;
|
||||
let body: { status: string; targetLocation?: { id: string } };
|
||||
|
||||
do {
|
||||
const response = await request(application.getHttpServer())
|
||||
.get('/api/travel/current')
|
||||
.expect(200);
|
||||
body = response.body;
|
||||
if (body.status === 'COMPLETED') {
|
||||
return body;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
} while (Date.now() < deadline);
|
||||
|
||||
return body;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user