Queue consumed on dispatch; engine chain hardened; queued planes unsellable (ISS-088)
CI / build + unit + e2e (pull_request) Failing after 12m59s
CI / build + unit + e2e (pull_request) Failing after 12m59s
This commit is contained in:
@@ -331,6 +331,34 @@ describe('flyPlane', () => {
|
||||
|
||||
await expectActionError(flyPlane(inst, 'plane-starter', 'LPPD'), 409, /insufficient fuel/i);
|
||||
});
|
||||
|
||||
// --- Queue consumption on dispatch (v0.46.0, ISS-088) ---
|
||||
|
||||
it('consumes queue[0] when flying to the first queued leg — no phantom self-hop on landing', async () => {
|
||||
const inst = await freshDb('fly-consume.db');
|
||||
await queueLeg(inst, 'plane-starter', 'LEMD');
|
||||
await queueLeg(inst, 'plane-starter', 'EHAM');
|
||||
|
||||
await flyPlane(inst, 'plane-starter', 'LEMD'); // the first queued leg
|
||||
|
||||
const plane = (await inst.db.select().from(schema.planes))[0];
|
||||
expect(plane.status).toBe('flying');
|
||||
expect(plane.destIcao).toBe('LEMD');
|
||||
expect(JSON.parse(plane.queue)).toEqual(['EHAM']); // queue[0] consumed, the rest kept in order
|
||||
});
|
||||
|
||||
it('keeps the queue intact when skipping ahead to a non-first queued leg (explicit override)', async () => {
|
||||
const inst = await freshDb('fly-skipahead.db');
|
||||
await queueLeg(inst, 'plane-starter', 'LEMD');
|
||||
await queueLeg(inst, 'plane-starter', 'EHAM');
|
||||
|
||||
await flyPlane(inst, 'plane-starter', 'EHAM'); // not the first queued leg — an override
|
||||
|
||||
const plane = (await inst.db.select().from(schema.planes))[0];
|
||||
expect(plane.status).toBe('flying');
|
||||
expect(plane.destIcao).toBe('EHAM');
|
||||
expect(JSON.parse(plane.queue)).toEqual(['LEMD', 'EHAM']); // untouched — FIFO order preserved
|
||||
});
|
||||
});
|
||||
|
||||
describe('queueLeg (v0.25.0, ISS-043)', () => {
|
||||
@@ -671,6 +699,19 @@ describe('sellPlane', () => {
|
||||
expect(ids).toContain('plane-extra'); // row untouched — the cargo stays bound to a real plane
|
||||
});
|
||||
|
||||
it('rejects selling a grounded plane with queued legs with 409 (v0.46.0, ISS-088)', async () => {
|
||||
const inst = await freshDb('sell-queue.db');
|
||||
await inst.db.insert(schema.planes).values({
|
||||
id: 'plane-extra', model: 'atr72', status: 'available', fuel: 100, maxFuel: PLANE_TYPES.atr72.maxFuel,
|
||||
locationIcao: HUB_ICAO, destIcao: null, lat: 50, lon: 8, departureSimTime: null,
|
||||
});
|
||||
await queueLeg(inst, 'plane-extra', 'LEMD');
|
||||
|
||||
await expectActionError(sellPlane(inst, 'plane-extra'), 409, /cancel queued legs first/);
|
||||
const plane = (await inst.db.select().from(schema.planes).where(eq(schema.planes.id, 'plane-extra')))[0];
|
||||
expect(JSON.parse(plane.queue)).toEqual(['LEMD']); // row untouched — the queue stays with a real plane
|
||||
});
|
||||
|
||||
it('rejects selling the last remaining plane with 409', async () => {
|
||||
const inst = await freshDb('sell-last.db'); // only the starter exists
|
||||
await expectActionError(sellPlane(inst, 'plane-starter'), 409, /last remaining/);
|
||||
|
||||
@@ -153,6 +153,12 @@ export async function flyPlane(inst: GameDb, planeId: string, destIcao: string):
|
||||
const sim = (await inst.db.select().from(schema.simState))[0];
|
||||
if (!sim) throw new Error('sim_state row missing');
|
||||
|
||||
// v0.46.0 (ISS-088): flying to the FIRST queued leg consumes it — otherwise the engine would
|
||||
// chain a zero-distance self-hop on landing (queue[0] === the airport just reached). Skip-ahead
|
||||
// to a non-first queued dest is an explicit override: the queue stays intact, FIFO preserved.
|
||||
const queue = parseIcaoQueue(plane.queue);
|
||||
if (queue.length > 0 && queue[0] === destIcao) queue.shift();
|
||||
|
||||
await inst.db.update(schema.planes)
|
||||
.set({
|
||||
status: 'flying',
|
||||
@@ -161,6 +167,7 @@ export async function flyPlane(inst: GameDb, planeId: string, destIcao: string):
|
||||
lat: origin.lat,
|
||||
lon: origin.lon,
|
||||
fuel: target, // ISS-082: the post-top-up level (unchanged when no top-up was needed)
|
||||
queue: JSON.stringify(queue),
|
||||
})
|
||||
.where(eq(schema.planes.id, planeId));
|
||||
if (cost > 0) {
|
||||
@@ -340,6 +347,11 @@ export async function sellPlane(inst: GameDb, planeId: string): Promise<{ payout
|
||||
throw new GameActionError(409, `cannot sell a plane carrying cargo`);
|
||||
}
|
||||
|
||||
// v0.46.0 (ISS-088): selling a plane with queued legs would silently drop them — force an explicit cancel first.
|
||||
if (parseIcaoQueue(plane.queue).length > 0) {
|
||||
throw new GameActionError(409, 'the plane has queued legs — cancel queued legs first');
|
||||
}
|
||||
|
||||
const all = await inst.db.select().from(schema.planes);
|
||||
if (all.length <= 1) throw new GameActionError(409, 'cannot sell the last remaining plane');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user