This commit is contained in:
+1
-2
@@ -13,7 +13,7 @@ test('GET /api/version returns the app version from package.json', async ({ page
|
||||
expect(body.version).toBe(pkg.version);
|
||||
});
|
||||
|
||||
test('GET /api/openapi serves the spec with all thirteen endpoints and the package.json version', async ({ page }) => {
|
||||
test('GET /api/openapi serves the spec with all twelve endpoints and the package.json version', async ({ page }) => {
|
||||
const res = await page.request.get('/api/openapi');
|
||||
expect(res.status()).toBe(200);
|
||||
expect(res.headers()['content-type']).toContain('application/json');
|
||||
@@ -27,7 +27,6 @@ test('GET /api/openapi serves the spec with all thirteen endpoints and the packa
|
||||
'/api/market/buy-plane',
|
||||
'/api/market/sell-plane',
|
||||
'/api/packages/{id}/abandon',
|
||||
'/api/packages/{id}/ignore', // v0.29.0 (ISS-047 part B)
|
||||
'/api/packages/{id}/load',
|
||||
'/api/planes/{planeId}/fly',
|
||||
'/api/planes/{planeId}/queue',
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ interface StateBody {
|
||||
}
|
||||
|
||||
// ISS-133 (v0.59.0): frames carry only the package statuses the client renders — terminal history
|
||||
// (delivered/abandoned/ignored) is unbounded in the DB and must never leak into a pushed frame.
|
||||
// (delivered/abandoned) is unbounded in the DB and must never leak into a pushed frame.
|
||||
const ALLOWED_PACKAGE_STATUSES = new Set(['open', 'loaded', 'expired']);
|
||||
|
||||
test('GET /api/events streams state snapshots over SSE', async () => {
|
||||
|
||||
+3
-3
@@ -63,10 +63,10 @@ test('long-leg priority packages always expire after travel time at max speed +
|
||||
created.push(pkg);
|
||||
}
|
||||
} finally {
|
||||
// Cleanup: dismiss every package this test created so it leaves no trace on the shared board.
|
||||
// Cleanup: age out every package this test created so it leaves no trace on the shared board.
|
||||
for (const pkg of created) {
|
||||
const r = await page.request.post(`/api/packages/${pkg.id}/ignore`);
|
||||
expect(r.ok(), `ignoring ${pkg.id}`).toBeTruthy();
|
||||
const r = await page.request.post('/api/test/expire-package', { data: { packageId: pkg.id } });
|
||||
expect(r.ok(), `expiring ${pkg.id}`).toBeTruthy();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+1
-33
@@ -40,7 +40,7 @@ interface PackageState {
|
||||
originName: string;
|
||||
destName: string;
|
||||
reward: number;
|
||||
status: 'open' | 'loaded' | 'delivered' | 'expired' | 'abandoned' | 'ignored'; // v0.29.0 (ISS-047 part B)
|
||||
status: 'open' | 'loaded' | 'delivered' | 'expired' | 'abandoned';
|
||||
planeId: string | null;
|
||||
createdSimTime: number;
|
||||
deadlineSimTime: number;
|
||||
@@ -447,38 +447,6 @@ test('dispatch auto-refuels: a low tank tops up to 150% of the leg requirement a
|
||||
expect((await page.request.post('/api/test/set-fuel', { data: { planeId: 'plane-starter', fuel: entry.maxFuel } })).status()).toBe(200);
|
||||
});
|
||||
|
||||
test('Ignore removes an open job from the list permanently (ISS-047 part B)', async ({ page }) => {
|
||||
test.setTimeout(300_000); // self-heal can take minutes after an interrupted run
|
||||
await flyHomeViaApi(page); // deterministic: the starter sits at EDDF
|
||||
|
||||
const nearest = await nearestToHub(page);
|
||||
const pkg = await createPackage(page, HUB, nearest.icao, 24 * 3_600_000);
|
||||
|
||||
await page.goto('/');
|
||||
const row = page.locator(`li[data-testid="open-package-${pkg.id}"]`);
|
||||
await expect(row, 'the hook-created package appears in the list').toBeVisible({ timeout: 15_000 });
|
||||
|
||||
// Ignore through the UI — the post-action snapshot lands immediately (no poll wait).
|
||||
const ignoreBtn = page.getByTestId(`ignore-${pkg.id}`);
|
||||
await expect(ignoreBtn, 'the row has an Ignore button').toBeEnabled();
|
||||
await ignoreBtn.click();
|
||||
|
||||
// Server truth: the package is 'ignored'…
|
||||
await expect
|
||||
.poll(async () => (await getState(page)).packages.find((p) => p.id === pkg.id)?.status, {
|
||||
timeout: 15_000,
|
||||
message: 'waiting for the ignored status in /api/state',
|
||||
})
|
||||
.toBe('ignored');
|
||||
|
||||
// …and the row is gone from the open list (the section filters status==='open').
|
||||
await expect(row).not.toBeVisible({ timeout: 15_000 });
|
||||
|
||||
// A second ignore attempt on the same id is a 409 — 'ignored' is terminal.
|
||||
const again = await page.request.post(`/api/packages/${pkg.id}/ignore`);
|
||||
expect(again.status()).toBe(409);
|
||||
});
|
||||
|
||||
test('Planes market tab lists all plane types with tech specs', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
const body = await getState(page); // authoritative catalog — v0.28.0 ships six types
|
||||
|
||||
+11
-10
@@ -84,7 +84,7 @@ test('a fully grounded fleet freezes the world clock (#135)', async ({ page }) =
|
||||
expect(after.simState.clockMs - c0, 'the grounded clock must stay frozen').toBe(0);
|
||||
});
|
||||
|
||||
test('a grounded fleet never starves: ignoring every open package cannot empty the market (#135 follow-up)', async ({ page }) => {
|
||||
test('a grounded fleet never starves: draining every open package cannot empty the market (#135 follow-up)', async ({ page }) => {
|
||||
// Self-heal to a fully grounded fleet first — that is what freezes the world clock (#135), and
|
||||
// the freeze is exactly what used to let the drain stick (no frozen tick spawned replacements).
|
||||
await expect
|
||||
@@ -100,23 +100,24 @@ test('a grounded fleet never starves: ignoring every open package cannot empty t
|
||||
expect(before.planes.every((p) => p.status === 'available'), 'fleet should be grounded at spec start').toBe(true);
|
||||
const clockBefore = before.simState.clockMs;
|
||||
|
||||
// Drain: ignore every open package, pass after pass. Pre-fix (v0.62.0) a frozen tick never
|
||||
// spawned replacements, so this reached zero and stayed there — the soft-lock. Post-fix every
|
||||
// request's tick re-tops-up BEFORE its removal, so the market can never empty; cap the passes to
|
||||
// bound runtime either way (a zero-open snapshot would mean the bug is back). v0.65.0: ignoring
|
||||
// starts each slot's refill cooldown (seconds in CI via FLIGHT_JOB_*_REFILL_MS), so the board
|
||||
// comes back a few seconds after the drain, not instantly.
|
||||
// Drain: expire every open package via the test hook (FLIGHT_TEST_HOOKS=1), pass after pass —
|
||||
// ignore was removed in v0.69.0 and loading can't drain a pool either (the starter's payload fits
|
||||
// only the lightest band). Pre-fix (v0.62.0) a frozen tick never spawned replacements, so this
|
||||
// reached zero and stayed there — the soft-lock. Post-fix every request's tick re-tops-up around
|
||||
// its removal, so the market can never empty; cap the passes to bound runtime either way (a
|
||||
// zero-open snapshot would mean the bug is back). Expired slots carry no refill cooldown, so the
|
||||
// board comes back on the very next tick.
|
||||
for (let pass = 0; pass < 3; pass++) {
|
||||
const s = await getState(page);
|
||||
const open = s.packages.filter((p) => p.status === 'open');
|
||||
if (open.length === 0) break; // drained — the pre-fix soft-lock state
|
||||
for (const p of open) {
|
||||
expect((await page.request.post(`/api/packages/${p.id}/ignore`)).status()).toBe(200);
|
||||
expect((await page.request.post('/api/test/expire-package', { data: { packageId: p.id } })).status()).toBe(200);
|
||||
}
|
||||
}
|
||||
|
||||
// The ~1 s background tick must refill the pool once the cooldowns lapse — within ~15 s of real
|
||||
// time (CI's heavy-job refill delay is 5 s, so allow headroom for dind slowness)…
|
||||
// Expired slots respawn immediately (no cooldown), so the ~1 s background tick must refill the
|
||||
// pool right away — within ~15 s of real time, with headroom for dind slowness…
|
||||
let topped: StateBody | undefined;
|
||||
await expect
|
||||
.poll(async () => {
|
||||
|
||||
@@ -38,7 +38,7 @@ interface PackageState {
|
||||
id: string;
|
||||
originIcao: string;
|
||||
destIcao: string;
|
||||
status: 'open' | 'loaded' | 'delivered' | 'expired' | 'abandoned' | 'ignored';
|
||||
status: 'open' | 'loaded' | 'delivered' | 'expired' | 'abandoned';
|
||||
planeId: string | null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user