deadhead-space-freight-tycoon/packages/engine/test/ported/dev-tools.test.ts

165 lines
6.1 KiB
TypeScript

// Ported from apps/server/test (CR-014 Step 14.5): now runs on the engine.
import { content, upgradeMaxLevel } from "@deadhead/content";
import { companyResponseSchema, errorResponseSchema, playerStateSchema } from "@deadhead/schema";
import { describe, expect, it } from "vitest";
import {
newTestApp,
useTestApp,
signUp,
call,
getState,
edit,
testContract,
insertDispatch,
signUpOwner,
} from "../support";
const testApp = useTestApp();
describe("developer tools (Step 10K.11)", () => {
it("advances the server clock", async () => {
const guest = await signUp(testApp().app);
const before = (
(await call(testApp().app, guest, "GET", "/api/time")).json() as { realTime: number }
).realTime;
const response = await call(testApp().app, guest, "POST", "/api/test/advance-clock", {
advanceMs: 600_000,
});
expect(response.statusCode).toBe(200);
const after = (
(await call(testApp().app, guest, "GET", "/api/time")).json() as { realTime: number }
).realTime;
expect(after - before).toBeGreaterThanOrEqual(600_000);
});
it("adds credits", async () => {
const guest = await signUp(testApp().app);
const start = (await getState(testApp().app, guest)).company.credits;
const response = await call(testApp().app, guest, "POST", "/api/test/add-credits", {
amount: 10_000,
});
expect(companyResponseSchema.parse(response.json()).company.credits).toBe(start + 10_000);
});
it("lands every flight when advanced to the last arrival", async () => {
const { app, clock } = testApp();
const owner = await signUpOwner(app);
const flight = await insertDispatch(testApp().app, {
shipId: owner.shipId,
pilotId: owner.avatarId,
departAt: clock.now,
contract: testContract(),
});
expect((await getState(app, owner)).activeDispatches).toHaveLength(1);
await call(app, owner, "POST", "/api/test/advance-clock", {
advanceMs: flight.arrivalAt - clock.now + 1000,
});
expect((await getState(app, owner)).activeDispatches).toHaveLength(0);
});
it("does not exist in production", async () => {
// A production build: developer commands are off.
const { app } = newTestApp({ devTools: false });
await signUp(app);
for (const [url, payload] of [
["/api/test/advance-clock", { advanceMs: 1000 }],
["/api/test/add-credits", { amount: 10_000 }],
["/api/test/pilot", { pilotId: "p", action: "level-up" }],
["/api/test/ship", { shipId: "s", action: "max-upgrades" }],
["/api/test/discover-all", {}],
] as const) {
const response = await call(app, null, "POST", url, payload);
expect(response.statusCode, url).toBe(404);
}
});
it("levels a pilot up to the next level's XP, and clears duty and rest", async () => {
const { app, clock } = testApp();
const owner = await signUpOwner(app);
await edit(app, (save) => {
Object.assign(
save.pilots.find((p) => p.id === owner.avatarId)!,
{
xp: 40,
dutyHours: 9,
restEndsAt: clock.now + 3_600_000,
},
);
});
const pilotAfter = async (action: string) => {
const response = await call(app, owner, "POST", "/api/test/pilot", {
pilotId: owner.avatarId,
action,
});
expect(response.statusCode).toBe(200);
return playerStateSchema.parse(response.json()).pilots.find((p) => p.id === owner.avatarId)!;
};
const levelled = await pilotAfter("level-up");
expect(levelled.xp).toBe(content.config.pilotLevelXpThresholds[0]);
expect(levelled.level).toBe(1);
expect(levelled.skillPointsAvailable).toBe(content.config.skillPointsPerLevel);
expect((await pilotAfter("level-up")).level).toBe(2);
const rested = await pilotAfter("clear-rest");
expect(rested).toMatchObject({ dutyHours: 0, resting: false, restEndsAt: null });
});
it("maxes and resets a ship's upgrades, and finishes shop work", async () => {
const { app, clock } = testApp();
const owner = await signUpOwner(app);
const ship = async (input: Record<string, unknown>) => {
const response = await call(app, owner, "POST", "/api/test/ship", {
shipId: owner.shipId,
...input,
});
expect(response.statusCode).toBe(200);
return playerStateSchema.parse(response.json()).ships.find((s) => s.id === owner.shipId)!;
};
const maxed = await ship({ action: "max-upgrades" });
for (const upgrade of content.upgrades) {
expect(maxed.upgrades[upgrade.id], upgrade.id).toBe(upgradeMaxLevel(upgrade, "courier"));
}
expect(maxed.stats.tankCapacityFu).toBeGreaterThan(
content.shipModel("courier")!.tankCapacityFu,
);
expect((await ship({ action: "reset-upgrades" })).upgrades).toEqual({});
await edit(app, (save) => {
Object.assign(
save.ships.find((s) => s.id === owner.shipId)!,
{
inShopUntil: clock.now + 600_000,
shopUpgradeId: "engines",
shopLevel: 1,
},
);
});
expect((await ship({ action: "finish-shop" })).inShopUntil).toBeNull();
});
it("moves an idle ship to a discovered place only", async () => {
const { app } = testApp();
const owner = await signUpOwner(app);
const move = (locationId: string) =>
call(app, owner, "POST", "/api/test/ship", {
shipId: owner.shipId,
action: "move",
locationId,
});
const moved = await move("moon");
expect(moved.statusCode).toBe(200);
expect(playerStateSchema.parse(moved.json()).ships[0]!.locationId).toBe("moon");
const refused = await move("mars");
expect(refused.statusCode).toBe(409);
expect(errorResponseSchema.parse(refused.json()).error.code).toBe("location-undiscovered");
});
it("discovers every location", async () => {
const { app } = testApp();
const owner = await signUpOwner(app);
const response = await call(app, owner, "POST", "/api/test/discover-all", {});
const { locations } = playerStateSchema.parse(response.json());
expect(locations.every((l) => l.discovered)).toBe(true);
expect(locations).toHaveLength(content.locations.length);
});
});