deadhead-space-freight-tycoon/apps/server/test/buy-ship.test.ts

91 lines
3.5 KiB
TypeScript

import { content } from "@deadhead/content";
import { errorResponseSchema, shipResponseSchema } from "@deadhead/schema";
import { describe, expect, it } from "vitest";
import { shipName } from "../src/game/names";
import { setCredits, useTestDatabase } from "./db";
import { call, getState, signUp, signUpOwner, useTestApp } from "./game";
const database = useTestDatabase();
const testApp = useTestApp(database);
const errorCode = (response: { json(): unknown }) =>
errorResponseSchema.parse(response.json()).error.code;
describe("buy a ship", () => {
it("deducts the price and adds an idle, uncrewed ship at Earth", async () => {
const { app } = testApp();
const owner = await signUpOwner(app, database().db);
const price = content.shipModel("courier")!.price;
await setCredits(database().db, owner.playerId, price + 500);
const response = await call(app, owner, "POST", "/api/ships", { modelId: "courier" });
expect(response.statusCode).toBe(200);
const { ship } = shipResponseSchema.parse(response.json());
expect(ship).toMatchObject({
modelId: "courier",
status: "idle",
locationId: "earth",
pilotId: null,
dispatchId: null,
});
expect(ship.name.length).toBeGreaterThan(0);
const state = await getState(app, owner);
expect(state.company.credits).toBe(500);
expect(state.ships.map((s) => s.id)).toEqual([owner.shipId, ship.id]);
});
it("returns insufficient-funds and changes nothing without enough credits", async () => {
const { app } = testApp();
const owner = await signUpOwner(app, database().db);
const price = content.shipModel("freighter")!.price;
await setCredits(database().db, owner.playerId, price - 1);
const response = await call(app, owner, "POST", "/api/ships", { modelId: "freighter" });
expect(response.statusCode).toBe(409);
expect(errorCode(response)).toBe("insufficient-funds");
const state = await getState(app, owner);
expect(state.company.credits).toBe(price - 1);
expect(state.ships).toHaveLength(1);
});
it("returns tutorial-active during the tutorial and changes nothing", async () => {
const { app } = testApp();
const guest = await signUp(app);
await setCredits(database().db, guest.playerId, 1_000_000);
const response = await call(app, guest, "POST", "/api/ships", { modelId: "courier" });
expect(response.statusCode).toBe(409);
expect(errorCode(response)).toBe("tutorial-active");
const state = await getState(app, guest);
expect(state.company.credits).toBe(1_000_000);
expect(state.ships).toHaveLength(0);
});
it("refuses to sell the company loaner", async () => {
const { app } = testApp();
const owner = await signUpOwner(app, database().db);
await setCredits(database().db, owner.playerId, 1_000_000);
const response = await call(app, owner, "POST", "/api/ships", { modelId: "company-courier" });
expect(response.statusCode).toBe(404);
});
it("rejects an unknown model", async () => {
const { app } = testApp();
const owner = await signUpOwner(app, database().db);
const response = await call(app, owner, "POST", "/api/ships", { modelId: "x-wing" });
expect(response.statusCode).toBe(404);
expect(errorCode(response)).toBe("not-found");
});
});
describe("ship names", () => {
it("are stable for a world seed and differ between ships", () => {
expect(shipName(42, 0)).toBe(shipName(42, 0));
const names = new Set(Array.from({ length: 10 }, (_, i) => shipName(42, i)));
expect(names.size).toBeGreaterThan(1);
});
});