import { describe, expect, it } from "vitest"; import { z } from "zod"; import { routes, type RouteDef } from "../src/index"; const entries = Object.entries(routes) as [string, RouteDef][]; describe("route catalog", () => { it("lists every base-game endpoint", () => { expect(Object.keys(routes).sort()).toEqual( [ "getServerTime", "createGuest", "getPlayerState", "getContractBoard", "getTutorialBoard", "getLogbook", "answerRadioCall", "repairShip", "createDispatch", "buyShip", "leaseBay", "hirePilot", "assignPilot", "unassignPilot", "restPilot", "startUpgrade", "refuelShip", "setRefuelPolicy", "setSleepThreshold", "orderEmergencyFuel", "spendSkillPoint", "buyDispatchOffice", "setPilotMode", "setPilotOrders", "setSpendingFloor", "acknowledgeSummaries", "acceptDeal", "backToWork", "markWelcomeSeen", "exportSave", "importSave", "devAdvanceClock", "devAddCredits", "devPilot", "devShip", "devDiscoverAll", "newCompany", "renameCompany", "repayLoan", "claimGoal", ].sort(), ); }); it.each(entries)("%s has request and response schemas", (_name, route) => { expect(route.request).toBeInstanceOf(z.ZodType); expect(route.response).toBeInstanceOf(z.ZodType); }); it("never gives two endpoints the same method and path", () => { const keys = entries.map(([, r]) => `${r.method} ${r.path}`); expect(new Set(keys).size).toBe(keys.length); }); it("keeps every path under /api", () => { for (const [, route] of entries) { expect(route.path.startsWith("/api/")).toBe(true); } }); it.each(entries)("%s declares each path parameter in its request schema", (_name, route) => { const params = [...route.path.matchAll(/:(\w+)/g)].map((m) => m[1]!); if (params.length === 0) return; expect(route.request).toBeInstanceOf(z.ZodObject); const shape = (route.request as z.ZodObject).shape; for (const param of params) { expect(shape).toHaveProperty(param); } }); it("needs a company everywhere except server time, starting a company and import", () => { const open = entries.filter(([, r]) => !r.auth).map(([name]) => name); // Importing works on a device with no company yet (CR-014). expect(open.sort()).toEqual(["createGuest", "getServerTime", "importSave"]); }); }); describe("manual decisions in the catalog (Step 24.2, decision 0.5.136)", () => { it("has every command declare whether it is a manual decision", () => { for (const [name, route] of Object.entries(routes) as [string, RouteDef][]) { expect(typeof route.manualDecision, name).toBe("boolean"); } }); it("counts the player's game commands, not viewing, dismissing, saves or dev tools", () => { const manual = Object.entries(routes) .filter(([, route]) => route.manualDecision) .map(([name]) => name) .sort(); expect(manual).toEqual( [ "acceptDeal", "answerRadioCall", "backToWork", "assignPilot", "buyDispatchOffice", "buyShip", "claimGoal", "createDispatch", "hirePilot", "leaseBay", "orderEmergencyFuel", "refuelShip", "renameCompany", "repairShip", "repayLoan", "restPilot", "setPilotMode", "setPilotOrders", "setRefuelPolicy", "setSleepThreshold", "setSpendingFloor", "spendSkillPoint", "startUpgrade", "unassignPilot", ].sort(), ); }); });