97 lines
3.9 KiB
TypeScript
97 lines
3.9 KiB
TypeScript
import fs from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
import { FUEL_PRICE_PER_L, PLANE_TYPES, SELL_FRACTION } from '@/lib/game/catalog';
|
|
import { GET } from './route';
|
|
|
|
let dir: string;
|
|
|
|
beforeAll(async () => {
|
|
// Point the app's DB singleton at a throwaway file so this test never
|
|
// touches ./db/flight.db. getGameDb() reads the env var on first call, and
|
|
// vitest gives each test file its own module registry (fresh singleton).
|
|
dir = await fs.mkdtemp(path.join(os.tmpdir(), 'flight-state-'));
|
|
process.env.FLIGHT_DB_PATH = path.join(dir, 'state.db');
|
|
});
|
|
|
|
afterAll(async () => {
|
|
delete process.env.FLIGHT_DB_PATH;
|
|
await fs.rm(dir, { recursive: true, force: true });
|
|
});
|
|
|
|
describe('GET /api/state', () => {
|
|
it('returns 200 with the full snapshot shape', async () => {
|
|
const res = await GET();
|
|
expect(res.status).toBe(200);
|
|
expect(res.headers.get('content-type')).toContain('application/json');
|
|
|
|
const body = (await res.json()) as Record<string, unknown>;
|
|
expect(Object.keys(body).sort()).toEqual([
|
|
'account',
|
|
'catalog',
|
|
'fuelPricePerL',
|
|
'packages',
|
|
'planes',
|
|
'sellFraction',
|
|
'simState',
|
|
]);
|
|
|
|
const account = body.account as { cash: number; reputation: number };
|
|
expect(typeof account.cash).toBe('number');
|
|
expect(typeof account.reputation).toBe('number');
|
|
|
|
const sim = body.simState as { clockMs: number; timeScale: number; paused: boolean };
|
|
expect(typeof sim.clockMs).toBe('number');
|
|
expect(typeof sim.timeScale).toBe('number');
|
|
expect(sim.paused).toBe(false);
|
|
|
|
const planes = body.planes as Array<Record<string, unknown>>;
|
|
expect(planes.length).toBeGreaterThanOrEqual(1);
|
|
for (const key of ['id', 'model', 'status', 'fuel', 'maxFuel', 'locationIcao', 'locationName', 'destIcao', 'destName', 'lat', 'lon']) {
|
|
expect(planes[0], `plane.${key}`).toHaveProperty(key);
|
|
}
|
|
expect(typeof planes[0].locationName).toBe('string');
|
|
// The seeded starter is grounded — no flight destination yet.
|
|
expect(planes[0].destIcao).toBeNull();
|
|
expect(planes[0].destName).toBeNull();
|
|
|
|
const packages = body.packages as Array<Record<string, unknown>>;
|
|
expect(packages.length).toBeGreaterThan(0);
|
|
for (const key of ['id', 'originIcao', 'destIcao', 'reward', 'cargoType', 'deadlineSimTime', 'status']) {
|
|
expect(packages[0], `package.${key}`).toHaveProperty(key);
|
|
}
|
|
});
|
|
|
|
it('is stable across repeated calls (same seeded DB)', async () => {
|
|
const a = ((await GET()).json()) as Promise<{ account: { cash: number } }>;
|
|
const b = ((await GET()).json()) as Promise<{ account: { cash: number } }>;
|
|
expect((await b).account.cash).toBe((await a).account.cash);
|
|
});
|
|
|
|
it('exposes the catalog + economy constants matching lib/game/catalog.ts (v0.10.0)', async () => {
|
|
const body = ((await GET()).json()) as Promise<{
|
|
catalog: Array<Record<string, unknown>>;
|
|
fuelPricePerL: number;
|
|
sellFraction: number;
|
|
}>;
|
|
const snap = await body;
|
|
|
|
// One entry per PLANE_TYPES model, values copied straight from the catalog.
|
|
expect(snap.catalog.map((e) => e.model).sort()).toEqual(Object.keys(PLANE_TYPES).sort());
|
|
for (const [model, type] of Object.entries(PLANE_TYPES)) {
|
|
const entry = snap.catalog.find((e) => e.model === model)!;
|
|
expect(entry.label).toBe(type.name);
|
|
expect(entry.price).toBe(type.price);
|
|
expect(entry.maxFuel).toBe(type.maxFuel);
|
|
expect(entry.burnPerKm).toBe(type.burnPerKm);
|
|
expect(entry.speedKmh).toBe(type.speedKmh);
|
|
expect(entry.capacity).toBe(type.capacity); // v0.12.0 slot capacity (ATR 72-600: 6)
|
|
}
|
|
|
|
// Economy constants — the single source of truth stays lib/game/catalog.ts.
|
|
expect(snap.fuelPricePerL).toBe(FUEL_PRICE_PER_L);
|
|
expect(snap.sellFraction).toBe(SELL_FRACTION);
|
|
});
|
|
});
|