CI / build + unit + e2e (pull_request) Failing after 5m28s
Part A: r(F) = k(We + rho*F) L/km, k calibrated so a full tank burns exactly
the old constant; closed forms in lib/sim/movement.ts (fuelAtWeighted,
requiredFuelFor, burnRateConstant, FUEL_DENSITY_KG_PER_L). Catalog gains
emptyWeightKg (ATR72 OEW 9300 kg), exposed in the snapshot. All three burn
call sites (engine landing, state live fuel, fly validation) moved to the
weighted model; old constant-rate fuelAt removed.
Part B: POST /api/planes/{planeId}/refuel takes an optional body
{mode:'full'|'forFlight', packageId} — no body keeps today's fill-to-full
behavior exactly (fuel lands bit-exact on maxFuel); forFlight adds just
enough for the loaded package's destination leg (409 not-loaded/expired/
doesn't-fit, 400 bad body). UI: per-cargo-row 'Refuel' button
(refuel-for-{pkg.id}), market button renamed 'Refuel to full'.
OpenAPI: RefuelRequest schema + optional requestBody + 400 on refuel, fly's
409 description updated, CatalogEntry.emptyWeightKg. Tests: new weighted-burn
suite, forFlight action/route cases (incl. ~7670 km UNNT<->LPPD doesn't-fit),
openapi guards; landing-fuel assertions in engine.test.ts + e2e/actions.spec.ts
use the closed form; the e2e 'return leg unfuelable' scenario reworked — a full
tank now covers the EDDF<->LEZL round trip, so the 409 moves to the second
outbound. New e2e: row Refuel deducts exactly L*price / raises fuel by exactly
L via the endpoint's own response snapshot.
48 lines
2.1 KiB
TypeScript
48 lines
2.1 KiB
TypeScript
import { performAction, refuelPlane } from '@/lib/game/actions';
|
|
|
|
// POST /api/planes/:planeId/refuel (v0.8.0; optional body v0.16.0) — fills a grounded
|
|
// plane at the fuel price per added liter and deducts the cost from cash. The body is
|
|
// OPTIONAL: no body, {} or {mode:'full'} fill to maxFuel (the original behavior);
|
|
// {mode:'forFlight', packageId} adds just enough for that loaded package's destination
|
|
// leg. 400 bad body (unknown field, unknown mode, forFlight without packageId); 404
|
|
// unknown plane/package id; 409 if the plane is flying, the package isn't loaded on it
|
|
// (or is expired), the leg doesn't fit a full tank, or cash is insufficient. On success
|
|
// returns the full state snapshot (fuel raised by exactly the added liters).
|
|
|
|
export async function POST(req: Request, ctx: { params: Promise<{ planeId: string }> }) {
|
|
const { planeId } = await ctx.params;
|
|
|
|
let body: unknown;
|
|
try {
|
|
const text = await req.text();
|
|
body = text ? JSON.parse(text) : {};
|
|
} catch {
|
|
return Response.json({ error: 'invalid JSON body' }, { status: 400 });
|
|
}
|
|
if (typeof body !== 'object' || body === null || Array.isArray(body)) {
|
|
return Response.json({ error: 'body must be a JSON object' }, { status: 400 });
|
|
}
|
|
const input = body as Record<string, unknown>;
|
|
for (const key of Object.keys(input)) {
|
|
if (key !== 'mode' && key !== 'packageId') {
|
|
return Response.json({ error: `unknown field "${key}"` }, { status: 400 });
|
|
}
|
|
}
|
|
const mode = input.mode ?? 'full';
|
|
if (mode !== 'full' && mode !== 'forFlight') {
|
|
return Response.json({ error: 'mode must be "full" or "forFlight"' }, { status: 400 });
|
|
}
|
|
let packageId: string | undefined;
|
|
if (input.packageId !== undefined) {
|
|
if (typeof input.packageId !== 'string' || input.packageId.length === 0) {
|
|
return Response.json({ error: 'packageId must be a non-empty string' }, { status: 400 });
|
|
}
|
|
packageId = input.packageId;
|
|
}
|
|
if (mode === 'forFlight' && !packageId) {
|
|
return Response.json({ error: 'packageId is required when mode is "forFlight"' }, { status: 400 });
|
|
}
|
|
|
|
return performAction((inst) => refuelPlane(inst, planeId, { mode, packageId }));
|
|
}
|