77 lines
3.3 KiB
TypeScript
77 lines
3.3 KiB
TypeScript
import fs from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { eq } from 'drizzle-orm';
|
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
import * as schema from '@/lib/db/schema';
|
|
import { getGameDb } from '@/lib/db/client';
|
|
import { PLANE_TYPES, SELL_FRACTION } from '@/lib/game/catalog';
|
|
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-sell-api-'));
|
|
process.env.FLIGHT_DB_PATH = path.join(dir, 'sell.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/market/sell-plane', { method: 'POST', body: JSON.stringify(body) }));
|
|
|
|
describe('POST /api/market/sell-plane', () => {
|
|
it('rejects a missing planeId and unknown fields with 400', async () => {
|
|
for (const body of [{}, { planeId: 7 }, { bogus: 1 }]) {
|
|
const res = await post(body);
|
|
expect(res.status).toBe(400);
|
|
expect(((await res.json()) as { error: string }).error).toBeTruthy();
|
|
}
|
|
});
|
|
|
|
it('rejects an unknown id with 404', async () => {
|
|
const res = await post({ planeId: 'ghost' });
|
|
expect(res.status).toBe(404);
|
|
});
|
|
|
|
it('rejects selling the last remaining plane with 409 (seeded DB has only the starter)', async () => {
|
|
const res = await post({ planeId: 'plane-starter' });
|
|
expect(res.status).toBe(409);
|
|
expect(((await res.json()) as { error: string }).error).toMatch(/last remaining/);
|
|
});
|
|
|
|
it('rejects selling a flying plane with 409', async () => {
|
|
const { db } = await getGameDb();
|
|
// A second, grounded plane so "flying" is the reason for rejection.
|
|
await db.insert(schema.planes).values({
|
|
id: 'plane-extra', model: 'atr72', status: 'available', fuel: 100, maxFuel: PLANE_TYPES.atr72.maxFuel,
|
|
locationIcao: 'EDDF', destIcao: null, lat: 50.03, lon: 8.57, departureSimTime: null,
|
|
});
|
|
await db.update(schema.planes).set({ status: 'flying' }).where(eq(schema.planes.id, 'plane-extra'));
|
|
|
|
const res = await post({ planeId: 'plane-extra' });
|
|
expect(res.status).toBe(409);
|
|
expect(((await res.json()) as { error: string }).error).toMatch(/flying/);
|
|
});
|
|
|
|
it('sells a grounded second plane for SELL_FRACTION of the catalog price', async () => {
|
|
const { db } = await getGameDb();
|
|
// The previous test left 'plane-extra' in the DB (now flying) — ground it.
|
|
await db.update(schema.planes).set({ status: 'available' }).where(eq(schema.planes.id, 'plane-extra'));
|
|
const cashBefore = (await db.select().from(schema.account))[0].cash;
|
|
|
|
const res = await post({ planeId: 'plane-extra' });
|
|
expect(res.status).toBe(200);
|
|
|
|
const body = (await res.json()) as { account: { cash: number }; planes: Array<{ id: string }> };
|
|
const payout = Math.round(PLANE_TYPES.atr72.price * SELL_FRACTION * 100) / 100;
|
|
expect(body.account.cash).toBeCloseTo(cashBefore + payout, 6);
|
|
expect(body.planes.map((p) => p.id)).not.toContain('plane-extra'); // row deleted
|
|
expect(body.planes.map((p) => p.id)).toContain('plane-starter');
|
|
});
|
|
});
|