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);
|
.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