feat: v0.7.0 — server-authoritative sim engine: tick-on-read clock (time-scale + pause via POST /api/sim), contract expiry + market spawn, analytic plane movement/finalization
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
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 { POST } from './route';
|
||||
|
||||
let dir: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Same pattern as app/api/state/route.test.ts: point the DB singleton at a
|
||||
// throwaway file (fresh module registry per test file → fresh singleton).
|
||||
dir = await fs.mkdtemp(path.join(os.tmpdir(), 'flight-sim-api-'));
|
||||
process.env.FLIGHT_DB_PATH = path.join(dir, 'sim.db');
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
delete process.env.FLIGHT_DB_PATH;
|
||||
await fs.rm(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
const post = (body: unknown) => POST(new Request('http://localhost/api/sim', { method: 'POST', body: JSON.stringify(body) }));
|
||||
const postRaw = (text: string) => POST(new Request('http://localhost/api/sim', { method: 'POST', body: text }));
|
||||
|
||||
describe('POST /api/sim', () => {
|
||||
it('returns the current simState for an empty body (tick + no-op)', async () => {
|
||||
const res = await post({});
|
||||
expect(res.status).toBe(200);
|
||||
const body = (await res.json()) as { clockMs: number; timeScale: number; paused: boolean };
|
||||
expect(typeof body.clockMs).toBe('number');
|
||||
expect(body.timeScale).toBe(1); // seeded default
|
||||
expect(body.paused).toBe(false);
|
||||
});
|
||||
|
||||
it('sets and clamps timeScale into [1, 60]', async () => {
|
||||
const hi = await post({ timeScale: 500 });
|
||||
expect(hi.status).toBe(200);
|
||||
expect(((await hi.json()) as { timeScale: number }).timeScale).toBe(60);
|
||||
|
||||
const lo = await post({ timeScale: 0.1 });
|
||||
expect(lo.status).toBe(200);
|
||||
expect(((await lo.json()) as { timeScale: number }).timeScale).toBe(1);
|
||||
|
||||
const mid = await post({ timeScale: 37 });
|
||||
expect(mid.status).toBe(200);
|
||||
expect(((await mid.json()) as { timeScale: number }).timeScale).toBe(37);
|
||||
|
||||
// Restore the default for later tests.
|
||||
await post({ timeScale: 1 });
|
||||
});
|
||||
|
||||
it('toggles paused and freezes the clock across ticks', async () => {
|
||||
const pause = await post({ paused: true });
|
||||
expect(pause.status).toBe(200);
|
||||
expect(((await pause.json()) as { paused: boolean }).paused).toBe(true);
|
||||
|
||||
// Two reads while paused → identical clock (tick-on-read advances nothing).
|
||||
const a = await post({});
|
||||
const b = await post({});
|
||||
expect(((await a.json()) as { clockMs: number }).clockMs).toBe(
|
||||
((await b.json()) as { clockMs: number }).clockMs,
|
||||
);
|
||||
|
||||
const resume = await post({ paused: false });
|
||||
expect(resume.status).toBe(200);
|
||||
expect(((await resume.json()) as { paused: boolean }).paused).toBe(false);
|
||||
});
|
||||
|
||||
it('applies both fields in one call', async () => {
|
||||
const res = await post({ timeScale: 60, paused: true });
|
||||
expect(res.status).toBe(200);
|
||||
const body = (await res.json()) as { timeScale: number; paused: boolean };
|
||||
expect(body.timeScale).toBe(60);
|
||||
expect(body.paused).toBe(true);
|
||||
await post({ timeScale: 1, paused: false }); // restore
|
||||
});
|
||||
|
||||
it('rejects invalid input with 400', async () => {
|
||||
for (const body of [{ timeScale: 'fast' }, { timeScale: Infinity }, { paused: 'yes' }, { bogus: 1 }]) {
|
||||
const res = await post(body);
|
||||
expect(res.status).toBe(400);
|
||||
expect(((await res.json()) as { error: string }).error).toBeTruthy();
|
||||
}
|
||||
|
||||
for (const text of ['nope{', '[1,2]', '"str"', 'null']) {
|
||||
const res = await postRaw(text);
|
||||
expect(res.status).toBe(400);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user