Auto-refuel on dispatch; remove manual refueling UI/API/action (ISS-082, v0.42.0)
CI / build + unit + e2e (pull_request) Successful in 17m25s
CI / build + unit + e2e (pull_request) Successful in 17m25s
This commit is contained in:
+96
-214
@@ -32,7 +32,6 @@ import {
|
||||
ignorePackage,
|
||||
loadPackage,
|
||||
queueLeg,
|
||||
refuelPlane,
|
||||
repairPlane,
|
||||
sellPlane,
|
||||
} from './actions';
|
||||
@@ -231,14 +230,7 @@ describe('flyPlane', () => {
|
||||
await expectActionError(flyPlane(inst, 'plane-starter', 'ZZZZ'), 400, /unknown airport/);
|
||||
});
|
||||
|
||||
it('rejects a leg the plane cannot fuel with 409 (burn > fuel)', async () => {
|
||||
const inst = await freshDb('fly-nofuel.db'); // EDDF → LEMD ≈ 1950 km × 1.75 L/km ≈ 3400 L
|
||||
await inst.db.update(schema.planes).set({ fuel: 1 }).where(eq(schema.planes.id, 'plane-starter'));
|
||||
|
||||
await expectActionError(flyPlane(inst, 'plane-starter', 'LEMD'), 409, /insufficient fuel/i);
|
||||
});
|
||||
|
||||
it('uses the ×1.5 broken burn for fuel feasibility when health is 0 (v0.39.0, ISS-069)', async () => {
|
||||
it('uses the ×1.5 broken burn for the dispatch auto-top-up when health is 0 (v0.39.0, ISS-069)', async () => {
|
||||
const inst = await freshDb('fly-broken.db'); // EDDF → LEMD
|
||||
const fc = await loadAirportsGeoJson();
|
||||
const from = fc.features.find((f) => f.properties.icao === HUB_ICAO)!.properties;
|
||||
@@ -247,19 +239,106 @@ describe('flyPlane', () => {
|
||||
const type = PLANE_TYPES.atr72;
|
||||
const w = weatherFor(HUB_ICAO);
|
||||
const f = windBurnFactor(w.windKt, w.windDirDeg, initialBearingDeg(from.lat, from.lon, to.lat, to.lon));
|
||||
// The tank holds EXACTLY the healthy requirement — a broken plane needs more than that.
|
||||
// The tank holds EXACTLY the healthy requirement — a broken plane's 150% top-up target is higher.
|
||||
const healthyRequired = requiredFuelFor(type.burnPerKm * f, type.emptyWeightKg, type.maxFuel, km);
|
||||
await inst.db.update(schema.planes)
|
||||
.set({ health: 0, fuel: requiredFuelFor(type.burnPerKm * f, type.emptyWeightKg, type.maxFuel, km) })
|
||||
.set({ health: 0, fuel: healthyRequired })
|
||||
.where(eq(schema.planes.id, 'plane-starter'));
|
||||
|
||||
await expectActionError(flyPlane(inst, 'plane-starter', 'LEMD'), 409, /insufficient fuel/i);
|
||||
await flyPlane(inst, 'plane-starter', 'LEMD'); // auto-refuel covers the extra burn — no 409
|
||||
|
||||
// Same tank at full health is flyable — the broken multiplier is what blocked it.
|
||||
const brokenRequired = requiredFuelFor(type.burnPerKm * f * BROKEN_BURN_MULTIPLIER, type.emptyWeightKg, type.maxFuel, km);
|
||||
expect(brokenRequired).toBeGreaterThan(healthyRequired); // sanity: the multiplier genuinely raises the requirement
|
||||
const plane = (await inst.db.select().from(schema.planes))[0];
|
||||
expect(plane.status).toBe('flying');
|
||||
expect(plane.fuel).toBeCloseTo(Math.min(type.maxFuel, Math.max(healthyRequired, brokenRequired * 1.5)), 6); // topped up for the BROKEN burn…
|
||||
|
||||
// …while the same tank at full health tops up to the LOWER healthy target — the multiplier is what raised it.
|
||||
const inst2 = await freshDb('fly-broken-healthy.db');
|
||||
await inst2.db.update(schema.planes)
|
||||
.set({ fuel: requiredFuelFor(type.burnPerKm * f, type.emptyWeightKg, type.maxFuel, km) })
|
||||
.where(eq(schema.planes.id, 'plane-starter'));
|
||||
await inst2.db.update(schema.planes).set({ fuel: healthyRequired }).where(eq(schema.planes.id, 'plane-starter'));
|
||||
await flyPlane(inst2, 'plane-starter', 'LEMD'); // must not throw
|
||||
const plane2 = (await inst2.db.select().from(schema.planes))[0];
|
||||
expect(plane2.fuel).toBeCloseTo(Math.min(type.maxFuel, Math.max(healthyRequired, healthyRequired * 1.5)), 6);
|
||||
expect(plane.fuel).toBeGreaterThan(plane2.fuel); // the broken plane genuinely got more fuel
|
||||
});
|
||||
|
||||
// --- Auto-refuel on dispatch (ISS-082, v0.42.0) ---
|
||||
|
||||
// Wind-adjusted requirement for the hub → LEMD leg — computed with the SAME helpers flyPlane uses,
|
||||
// so the expectations stay exact without hardcoded literals.
|
||||
async function adjustedRequiredForHubToLemd() {
|
||||
const fc = await loadAirportsGeoJson();
|
||||
const from = fc.features.find((f) => f.properties.icao === HUB_ICAO)!.properties;
|
||||
const to = fc.features.find((f) => f.properties.icao === 'LEMD')!.properties;
|
||||
const km = haversineKm(from.lat, from.lon, to.lat, to.lon);
|
||||
const type = PLANE_TYPES.atr72;
|
||||
const w = weatherFor(HUB_ICAO);
|
||||
return requiredFuelFor(
|
||||
type.burnPerKm * windBurnFactor(w.windKt, w.windDirDeg, initialBearingDeg(from.lat, from.lon, to.lat, to.lon)),
|
||||
type.emptyWeightKg,
|
||||
type.maxFuel,
|
||||
km,
|
||||
);
|
||||
}
|
||||
|
||||
it('auto-refuels on dispatch: tops up to min(maxFuel, max(fuel, 1.5 × required)) and deducts the rep-discounted cost', async () => {
|
||||
const inst = await freshDb('fly-autorefuel.db'); // EDDF → LEMD
|
||||
const required = await adjustedRequiredForHubToLemd();
|
||||
const startFuel = Math.round(required / 2); // partial tank below the requirement
|
||||
await inst.db.update(schema.planes).set({ fuel: startFuel }).where(eq(schema.planes.id, 'plane-starter'));
|
||||
// Elevated rep → non-zero discount rate; expectations computed with the SAME helpers flyPlane uses.
|
||||
await inst.db.update(schema.account).set({ reputation: 30 });
|
||||
const cashBefore = (await inst.db.select().from(schema.account))[0].cash;
|
||||
|
||||
await flyPlane(inst, 'plane-starter', 'LEMD');
|
||||
|
||||
const rate = repRate(30, FUEL_DISCOUNT_PER_REP, MAX_FUEL_DISCOUNT);
|
||||
expect(rate).toBeGreaterThan(0); // sanity pin on the formula input
|
||||
const target = Math.min(PLANE_TYPES.atr72.maxFuel, Math.max(startFuel, required * 1.5));
|
||||
const expectedCost = Math.round((target - startFuel) * FUEL_PRICE_PER_L * (1 - rate) * 100) / 100;
|
||||
expect(expectedCost).toBeGreaterThan(0); // the top-up genuinely costs something
|
||||
|
||||
const plane = (await inst.db.select().from(schema.planes))[0];
|
||||
expect(plane.status).toBe('flying');
|
||||
expect(plane.fuel).toBeCloseTo(target, 6); // topped up to exactly the 150% target…
|
||||
const account = (await inst.db.select().from(schema.account))[0];
|
||||
expect(account.cash).toBeCloseTo(cashBefore - expectedCost, 6); // …at exactly the rep-discounted cost
|
||||
});
|
||||
|
||||
it('is a free no-op when the tank already covers the 150% target (cash unchanged)', async () => {
|
||||
const inst = await freshDb('fly-autorefuel-full.db'); // seeded starter is full at EDDF
|
||||
const required = await adjustedRequiredForHubToLemd();
|
||||
expect(required * 1.5).toBeLessThan(PLANE_TYPES.atr72.maxFuel); // sanity: the target stays below a full tank
|
||||
const cashBefore = (await inst.db.select().from(schema.account))[0].cash;
|
||||
|
||||
await flyPlane(inst, 'plane-starter', 'LEMD');
|
||||
|
||||
const plane = (await inst.db.select().from(schema.planes))[0];
|
||||
expect(plane.status).toBe('flying');
|
||||
expect(plane.fuel).toBe(PLANE_TYPES.atr72.maxFuel); // untouched…
|
||||
expect((await inst.db.select().from(schema.account))[0].cash).toBe(cashBefore); // …and free
|
||||
});
|
||||
|
||||
it('rejects dispatch with 409 insufficient funds when cash cannot cover the top-up — nothing mutated', async () => {
|
||||
const inst = await freshDb('fly-autorefuel-poor.db'); // EDDF → LEMD
|
||||
const required = await adjustedRequiredForHubToLemd();
|
||||
const startFuel = Math.round(required / 2);
|
||||
await inst.db.update(schema.planes).set({ fuel: startFuel }).where(eq(schema.planes.id, 'plane-starter'));
|
||||
await inst.db.update(schema.account).set({ cash: 0 });
|
||||
|
||||
await expectActionError(flyPlane(inst, 'plane-starter', 'LEMD'), 409, /insufficient funds/i);
|
||||
|
||||
const plane = (await inst.db.select().from(schema.planes))[0];
|
||||
expect(plane.status).toBe('available'); // still grounded…
|
||||
expect(plane.fuel).toBe(startFuel); // …with the fuel UNCHANGED — nothing was mutated
|
||||
});
|
||||
|
||||
it('still rejects a leg that does not fit even a full tank with 409 (the auto-refuel backstop)', async () => {
|
||||
const inst = await freshDb('fly-autorefuel-nofit.db');
|
||||
// UNNT ↔ LPPD are ~7670 km apart — far beyond the ATR's range, so no top-up can make it flyable.
|
||||
await inst.db.update(schema.planes).set({ locationIcao: 'UNNT' }).where(eq(schema.planes.id, 'plane-starter'));
|
||||
|
||||
await expectActionError(flyPlane(inst, 'plane-starter', 'LPPD'), 409, /insufficient fuel/i);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -612,202 +691,6 @@ describe('sellPlane', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('refuelPlane', () => {
|
||||
it('fills to maxFuel and deducts (maxFuel − fuel) × FUEL_PRICE_PER_L from cash', async () => {
|
||||
const inst = await freshDb('refuel-ok.db');
|
||||
await inst.db.update(schema.planes).set({ fuel: 1_000 }).where(eq(schema.planes.id, 'plane-starter'));
|
||||
const cashBefore = (await inst.db.select().from(schema.account))[0].cash;
|
||||
|
||||
const { cost } = await refuelPlane(inst, 'plane-starter');
|
||||
|
||||
expect(cost).toBe(Math.round((PLANE_TYPES.atr72.maxFuel - 1_000) * FUEL_PRICE_PER_L * 100) / 100);
|
||||
const plane = (await inst.db.select().from(schema.planes))[0];
|
||||
expect(plane.fuel).toBe(PLANE_TYPES.atr72.maxFuel);
|
||||
const account = (await inst.db.select().from(schema.account))[0];
|
||||
expect(account.cash).toBeCloseTo(cashBefore - cost, 6);
|
||||
});
|
||||
|
||||
it('is a free no-op when the tank is already full', async () => {
|
||||
const inst = await freshDb('refuel-full.db'); // seeded starter is full
|
||||
const cashBefore = (await inst.db.select().from(schema.account))[0].cash;
|
||||
|
||||
const { cost } = await refuelPlane(inst, 'plane-starter');
|
||||
|
||||
expect(cost).toBe(0);
|
||||
expect((await inst.db.select().from(schema.account))[0].cash).toBe(cashBefore);
|
||||
});
|
||||
|
||||
it('rejects a flying plane with 409', async () => {
|
||||
const inst = await freshDb('refuel-flying.db');
|
||||
await inst.db.update(schema.planes)
|
||||
.set({ status: 'flying', departureSimTime: T0, destIcao: 'LEMD' })
|
||||
.where(eq(schema.planes.id, 'plane-starter'));
|
||||
|
||||
await expectActionError(refuelPlane(inst, 'plane-starter'), 409, /flying/);
|
||||
});
|
||||
|
||||
it('rejects the fill-up when cash is below the cost with 409', async () => {
|
||||
const inst = await freshDb('refuel-poor.db');
|
||||
await inst.db.update(schema.planes).set({ fuel: 1_000 }).where(eq(schema.planes.id, 'plane-starter'));
|
||||
await inst.db.update(schema.account).set({ cash: 10 }); // cost is ~4960
|
||||
|
||||
await expectActionError(refuelPlane(inst, 'plane-starter'), 409, /insufficient funds/i);
|
||||
});
|
||||
|
||||
it('applies the reputation-scaled discount to the price term only, in both modes (v0.38.0)', async () => {
|
||||
const inst = await freshDb('refuel-rep.db');
|
||||
// Elevated rep → non-zero rate; expectations computed with the SAME helpers refuelPlane uses.
|
||||
await inst.db.update(schema.account).set({ reputation: 30 });
|
||||
const rate = repRate(30, FUEL_DISCOUNT_PER_REP, MAX_FUEL_DISCOUNT);
|
||||
expect(rate).toBeGreaterThan(0);
|
||||
|
||||
// 'full': exact discounted cost, tank math untouched.
|
||||
await inst.db.update(schema.planes).set({ fuel: 1_000 }).where(eq(schema.planes.id, 'plane-starter'));
|
||||
const cashBefore = (await inst.db.select().from(schema.account))[0].cash;
|
||||
const { cost } = await refuelPlane(inst, 'plane-starter');
|
||||
expect(cost).toBe(Math.round((PLANE_TYPES.atr72.maxFuel - 1_000) * FUEL_PRICE_PER_L * (1 - rate) * 100) / 100);
|
||||
const plane = (await inst.db.select().from(schema.planes))[0];
|
||||
expect(plane.fuel).toBe(PLANE_TYPES.atr72.maxFuel); // target unchanged by the discount
|
||||
expect((await inst.db.select().from(schema.account))[0].cash).toBeCloseTo(cashBefore - cost, 6);
|
||||
|
||||
// 'forFlight': same discount on the price term; burn/target math (wind factor, ×1.5 margin) untouched.
|
||||
await insertOpenPackage(inst, 'p-ff-r', { destIcao: 'LEMD' }); // EDDF → LEMD
|
||||
await loadPackage(inst, 'p-ff-r', 'plane-starter');
|
||||
const required = await adjustedRequiredForHubToLemd();
|
||||
const startFuel = Math.round(required / 2);
|
||||
await inst.db.update(schema.planes).set({ fuel: startFuel }).where(eq(schema.planes.id, 'plane-starter'));
|
||||
const cashBefore2 = (await inst.db.select().from(schema.account))[0].cash;
|
||||
|
||||
const { cost: ffCost } = await refuelPlane(inst, 'plane-starter', { mode: 'forFlight', packageId: 'p-ff-r' });
|
||||
|
||||
const targetFuel = Math.min(PLANE_TYPES.atr72.maxFuel, Math.max(startFuel, required * 1.5));
|
||||
expect(ffCost).toBe(Math.round((targetFuel - startFuel) * FUEL_PRICE_PER_L * (1 - rate) * 100) / 100);
|
||||
const plane2 = (await inst.db.select().from(schema.planes))[0];
|
||||
expect(plane2.fuel).toBeCloseTo(targetFuel, 6); // topped up to exactly the 150% target
|
||||
expect((await inst.db.select().from(schema.account))[0].cash).toBeCloseTo(cashBefore2 - ffCost, 6);
|
||||
});
|
||||
|
||||
it('rejects an unknown id with 404', async () => {
|
||||
const inst = await freshDb('refuel-unknown.db');
|
||||
await expectActionError(refuelPlane(inst, 'ghost'), 404);
|
||||
});
|
||||
|
||||
// --- forFlight mode (v0.16.0) ---
|
||||
|
||||
// Wind-adjusted requirement for the hub → LEMD leg (v0.36.0) — computed with the SAME helpers
|
||||
// refuelPlane uses, so the expectations stay exact without hardcoded literals.
|
||||
async function adjustedRequiredForHubToLemd() {
|
||||
const fc = await loadAirportsGeoJson();
|
||||
const from = fc.features.find((f) => f.properties.icao === HUB_ICAO)!.properties;
|
||||
const to = fc.features.find((f) => f.properties.icao === 'LEMD')!.properties;
|
||||
const km = haversineKm(from.lat, from.lon, to.lat, to.lon);
|
||||
const type = PLANE_TYPES.atr72;
|
||||
const w = weatherFor(HUB_ICAO);
|
||||
return requiredFuelFor(
|
||||
type.burnPerKm * windBurnFactor(w.windKt, w.windDirDeg, initialBearingDeg(from.lat, from.lon, to.lat, to.lon)),
|
||||
type.emptyWeightKg,
|
||||
type.maxFuel,
|
||||
km,
|
||||
);
|
||||
}
|
||||
|
||||
it('forFlight buys 150% of the weather-adjusted leg requirement and deducts L × price', async () => {
|
||||
const inst = await freshDb('refuel-forflight-ok.db');
|
||||
await insertOpenPackage(inst, 'p-ff', { destIcao: 'LEMD' }); // EDDF → LEMD
|
||||
await loadPackage(inst, 'p-ff', 'plane-starter');
|
||||
|
||||
const required = await adjustedRequiredForHubToLemd();
|
||||
|
||||
// Partial tank below the requirement so L > 0.
|
||||
const startFuel = Math.round(required / 2);
|
||||
await inst.db.update(schema.planes).set({ fuel: startFuel }).where(eq(schema.planes.id, 'plane-starter'));
|
||||
const cashBefore = (await inst.db.select().from(schema.account))[0].cash;
|
||||
|
||||
const { cost } = await refuelPlane(inst, 'plane-starter', { mode: 'forFlight', packageId: 'p-ff' });
|
||||
|
||||
// ISS-071: target is 150% of the requirement (below tank capacity for this leg).
|
||||
const targetFuel = Math.min(PLANE_TYPES.atr72.maxFuel, Math.max(startFuel, required * 1.5));
|
||||
expect(cost).toBe(Math.round((targetFuel - startFuel) * FUEL_PRICE_PER_L * 100) / 100);
|
||||
const plane = (await inst.db.select().from(schema.planes))[0];
|
||||
expect(plane.fuel).toBeCloseTo(targetFuel, 6); // topped up to exactly the 150% target
|
||||
const account = (await inst.db.select().from(schema.account))[0];
|
||||
expect(account.cash).toBeCloseTo(cashBefore - cost, 6);
|
||||
});
|
||||
|
||||
it('forFlight leaves a tank with a 50% margin — flyPlane to the package dest succeeds right after', async () => {
|
||||
const inst = await freshDb('refuel-ff-flyafter.db');
|
||||
await insertOpenPackage(inst, 'p-fa', { destIcao: 'LEMD' }); // EDDF → LEMD
|
||||
await loadPackage(inst, 'p-fa', 'plane-starter');
|
||||
|
||||
const required = await adjustedRequiredForHubToLemd();
|
||||
|
||||
// Empty-ish tank: well below the leg requirement.
|
||||
await inst.db.update(schema.planes).set({ fuel: Math.round(required / 4) }).where(eq(schema.planes.id, 'plane-starter'));
|
||||
|
||||
await refuelPlane(inst, 'plane-starter', { mode: 'forFlight', packageId: 'p-fa' });
|
||||
|
||||
// The leg must now be flyable — the 150% margin covers flyPlane's strict `>` check.
|
||||
await flyPlane(inst, 'plane-starter', 'LEMD');
|
||||
const plane = (await inst.db.select().from(schema.planes))[0];
|
||||
expect(plane.status).toBe('flying');
|
||||
expect(plane.destIcao).toBe('LEMD');
|
||||
});
|
||||
|
||||
it('forFlight is a free no-op when the tank already covers the leg', async () => {
|
||||
const inst = await freshDb('refuel-forflight-enough.db'); // seeded starter is full at EDDF
|
||||
await insertOpenPackage(inst, 'p-ff2', { destIcao: 'LEMD' });
|
||||
await loadPackage(inst, 'p-ff2', 'plane-starter');
|
||||
const cashBefore = (await inst.db.select().from(schema.account))[0].cash;
|
||||
|
||||
const { cost } = await refuelPlane(inst, 'plane-starter', { mode: 'forFlight', packageId: 'p-ff2' });
|
||||
|
||||
expect(cost).toBe(0);
|
||||
const plane = (await inst.db.select().from(schema.planes))[0];
|
||||
expect(plane.fuel).toBe(PLANE_TYPES.atr72.maxFuel); // untouched
|
||||
expect((await inst.db.select().from(schema.account))[0].cash).toBe(cashBefore);
|
||||
});
|
||||
|
||||
it('forFlight rejects a package that is not loaded on this plane with 409', async () => {
|
||||
const inst = await freshDb('refuel-ff-open.db'); // open — never loaded
|
||||
await insertOpenPackage(inst, 'p-x');
|
||||
await expectActionError(refuelPlane(inst, 'plane-starter', { mode: 'forFlight', packageId: 'p-x' }), 409);
|
||||
|
||||
const inst2 = await freshDb('refuel-ff-other.db'); // loaded on ANOTHER plane
|
||||
await insertOpenPackage(inst2, 'p-y', { status: 'loaded', planeId: 'plane-someone-else' });
|
||||
await expectActionError(refuelPlane(inst2, 'plane-starter', { mode: 'forFlight', packageId: 'p-y' }), 409);
|
||||
});
|
||||
|
||||
it('forFlight rejects an expired package with 409', async () => {
|
||||
const inst = await freshDb('refuel-ff-expired.db');
|
||||
await insertOpenPackage(inst, 'p-z', { status: 'expired', planeId: 'plane-starter' });
|
||||
await expectActionError(refuelPlane(inst, 'plane-starter', { mode: 'forFlight', packageId: 'p-z' }), 409);
|
||||
});
|
||||
|
||||
it('forFlight rejects an unknown package id with 404', async () => {
|
||||
const inst = await freshDb('refuel-ff-unknown.db');
|
||||
await expectActionError(refuelPlane(inst, 'plane-starter', { mode: 'forFlight', packageId: 'nope' }), 404);
|
||||
});
|
||||
|
||||
it('forFlight rejects a missing packageId with 400', async () => {
|
||||
const inst = await freshDb('refuel-ff-nopkg.db');
|
||||
await expectActionError(refuelPlane(inst, 'plane-starter', { mode: 'forFlight' }), 400);
|
||||
});
|
||||
|
||||
it('forFlight rejects a leg that does not fit even a full tank with 409', async () => {
|
||||
const inst = await freshDb('refuel-ff-nofit.db');
|
||||
// UNNT (Narsarsuaq, Greenland) ↔ LPPD are ~7670 km apart — far beyond the ATR's range.
|
||||
await insertOpenPackage(inst, 'p-far', { destIcao: 'LPPD' });
|
||||
await loadPackage(inst, 'p-far', 'plane-starter');
|
||||
await inst.db.update(schema.planes).set({ locationIcao: 'UNNT' }).where(eq(schema.planes.id, 'plane-starter'));
|
||||
|
||||
await expectActionError(
|
||||
refuelPlane(inst, 'plane-starter', { mode: 'forFlight', packageId: 'p-far' }),
|
||||
409,
|
||||
/does not fit/i,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('repairPlane (v0.39.0, ISS-069)', () => {
|
||||
it('charges exactly REPAIR_COST, restores health to 100 and grounds the plane for REPAIR_DURATION_MS', async () => {
|
||||
const inst = await freshDb('repair-ok.db');
|
||||
@@ -857,7 +740,7 @@ describe('repairPlane (v0.39.0, ISS-069)', () => {
|
||||
await expectActionError(repairPlane(inst, 'plane-starter'), 409, /only grounded planes can be repaired/);
|
||||
});
|
||||
|
||||
it('blocks load/fly/refuel/sell while repairing (the existing status guards)', async () => {
|
||||
it('blocks load/fly/sell while repairing (the existing status guards)', async () => {
|
||||
const inst = await freshDb('repair-blocks.db');
|
||||
await inst.db.update(schema.planes).set({ health: 50 }).where(eq(schema.planes.id, 'plane-starter'));
|
||||
await repairPlane(inst, 'plane-starter');
|
||||
@@ -865,7 +748,6 @@ describe('repairPlane (v0.39.0, ISS-069)', () => {
|
||||
await insertOpenPackage(inst); // open package at the hub — loadable if the plane were grounded
|
||||
await expectActionError(loadPackage(inst, 'p-open', 'plane-starter'), 409, /repairing/);
|
||||
await expectActionError(flyPlane(inst, 'plane-starter', 'LEMD'), 409, /repairing/);
|
||||
await expectActionError(refuelPlane(inst, 'plane-starter'), 409, /repairing/);
|
||||
// A second plane so the "last remaining" guard cannot fire before the status one.
|
||||
await inst.db.insert(schema.planes).values({
|
||||
id: 'plane-extra', model: 'atr72', status: 'available', fuel: 100, maxFuel: PLANE_TYPES.atr72.maxFuel,
|
||||
|
||||
Reference in New Issue
Block a user