77 lines
3.0 KiB
TypeScript
77 lines
3.0 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 { getLocalPlayer, updateLocalPlayerEconomy } from '@/lib/db/localPlayer';
|
|
import { getGameDb } from '@/lib/db/client';
|
|
import { createSession } from '@/lib/auth';
|
|
import { PLANE_TYPES } from '@/lib/game/catalog';
|
|
import { POST } from './route';
|
|
|
|
let dir: string;
|
|
let sessionId: 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-buy-api-'));
|
|
process.env.FLIGHT_DB_PATH = path.join(dir, 'buy.db');
|
|
// v1.1.0 (ISS-107): action routes are session-guarded — mint a session for the seeded admin user.
|
|
sessionId = await createSession(await getGameDb(), 'admin-kamilkrzywda');
|
|
});
|
|
|
|
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/market/buy-plane', {
|
|
method: 'POST',
|
|
headers: { 'X-Flight-Session': sessionId },
|
|
body: JSON.stringify(body),
|
|
}),
|
|
);
|
|
|
|
describe('POST /api/market/buy-plane', () => {
|
|
it('rejects a missing model, unknown fields and unknown models with 400', async () => {
|
|
for (const body of [{}, { model: 7 }, { bogus: 1 }, { model: 'boeing747' }]) {
|
|
const res = await post(body);
|
|
expect(res.status).toBe(400);
|
|
expect(((await res.json()) as { error: string }).error).toBeTruthy();
|
|
}
|
|
});
|
|
|
|
it('rejects the purchase with 409 when cash is below the price (seeded €20k < €200k)', async () => {
|
|
const res = await post({ model: 'atr72' });
|
|
expect(res.status).toBe(409);
|
|
expect(((await res.json()) as { error: string }).error).toMatch(/insufficient funds/i);
|
|
|
|
// Cash untouched by the failed attempt.
|
|
expect((await getLocalPlayer(await getGameDb())).cash).toBe(20_000);
|
|
});
|
|
|
|
it('deducts the full price and adds a full-tank plane at EDDF to the snapshot', async () => {
|
|
const inst = await getGameDb();
|
|
// Local player row — top up cash so the purchase can succeed.
|
|
await updateLocalPlayerEconomy(inst, { cash: 500_000 });
|
|
|
|
const res = await post({ model: 'atr72' });
|
|
expect(res.status).toBe(200);
|
|
|
|
const body = (await res.json()) as {
|
|
account: { cash: number };
|
|
planes: Array<{ id: string; model: string; status: string; fuel: number; locationIcao: string }>;
|
|
};
|
|
expect(body.account.cash).toBe(500_000 - PLANE_TYPES.atr72.price);
|
|
|
|
const bought = body.planes.filter((p) => p.model === 'atr72');
|
|
expect(bought.length).toBe(2); // starter + the new one
|
|
const fresh = bought.find((p) => p.id !== 'plane-starter')!;
|
|
expect(fresh.status).toBe('available');
|
|
expect(fresh.fuel).toBe(PLANE_TYPES.atr72.maxFuel);
|
|
expect(fresh.locationIcao).toBe('EDDF'); // home base delivery
|
|
});
|
|
});
|