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

135 lines
5.0 KiB
TypeScript

// Ported from apps/server/test (CR-014 Step 14.5): now runs on the engine.
import { content } from "@deadhead/content";
import { companyResponseSchema, errorResponseSchema } from "@deadhead/schema";
import { describe, expect, it } from "vitest";
import {
useTestApp,
signUp,
call,
getState,
edit,
setCredits,
testContract,
insertDispatch,
signUpOwner,
type Guest,
} from "../support";
const testApp = useTestApp();
const claim = (guest: Guest, goalId: string) =>
call(testApp().app, guest, "POST", `/api/goals/${goalId}/claim`);
const errorCode = (response: { json(): unknown }) =>
errorResponseSchema.parse(response.json()).error.code;
/** An owner who has accepted the deal (so "pay off your loan" can apply). */
async function dealOwner() {
const owner = await signUpOwner(testApp().app);
await edit(testApp().app, (save) => {
save.player.dealAccepted = true;
});
return owner;
}
const status = async (guest: Guest, goalId: string) =>
(await getState(testApp().app, guest)).goals.find((g) => g.id === goalId)?.status;
describe("onboarding goals", () => {
it("are absent from state during the tutorial", async () => {
const guest = await signUp(testApp().app);
expect((await getState(testApp().app, guest)).goals).toEqual([]);
});
it("are listed in content order once the tutorial is over", async () => {
const owner = await dealOwner();
const goals = (await getState(testApp().app, owner)).goals;
expect(goals.map((g) => g.id)).toEqual(content.onboardingGoals.map((g) => g.id));
// Paid-off loan: the test owner has no balance.
expect(goals.find((g) => g.id === "loan-paid-off")?.status).toBe("ready");
expect(goals.find((g) => g.id === "priority-contract")?.status).toBe("incomplete");
});
it("becomes ready when its condition is met: a delivery with the player's own ship", async () => {
const { clock } = testApp();
const owner = await dealOwner();
expect(await status(owner, "first-own-delivery")).toBe("incomplete");
const record = await insertDispatch(testApp().app, {
shipId: owner.shipId,
pilotId: owner.avatarId,
departAt: clock.now,
});
clock.now = record.arrivalAt;
expect(await status(owner, "first-own-delivery")).toBe("ready");
});
it("counts a ship bought from the Market, not the deal's (CR-013)", async () => {
const owner = await dealOwner();
expect(await status(owner, "market-ship")).toBe("incomplete");
await setCredits(testApp().app, content.shipModel("lighter")!.price);
const response = await call(testApp().app, owner, "POST", "/api/ships", {
modelId: "lighter",
bayId: owner.earthBayId,
});
expect(response.statusCode).toBe(200);
expect(await status(owner, "market-ship")).toBe("ready");
});
it("counts a Priority delivery, not a Standard one (CR-013)", async () => {
const { clock } = testApp();
const owner = await dealOwner();
const fly = async (priority: boolean) => {
const record = await insertDispatch(testApp().app, {
shipId: owner.shipId,
pilotId: owner.avatarId,
departAt: clock.now,
contract: testContract({ priority, deliveryWindowGameHours: priority ? 1000 : null }),
});
clock.now = record.arrivalAt;
};
await fly(false);
expect(await status(owner, "priority-contract")).toBe("incomplete");
await fly(true);
expect(await status(owner, "priority-contract")).toBe("ready");
});
it("counts skill points spent", async () => {
const owner = await dealOwner();
await edit(testApp().app, (save) => {
save.pilots.find((p) => p.id === owner.avatarId)!.skillPointsSpent = 1;
});
expect(await status(owner, "first-skill-point")).toBe("ready");
});
it("claiming a completed goal adds its reward once", async () => {
const owner = await dealOwner();
await setCredits(testApp().app, 100);
const reward = content.onboardingGoal("loan-paid-off")!.reward;
const response = await claim(owner, "loan-paid-off");
expect(response.statusCode).toBe(200);
expect(companyResponseSchema.parse(response.json()).company.credits).toBe(100 + reward);
expect(await status(owner, "loan-paid-off")).toBe("claimed");
const again = await claim(owner, "loan-paid-off");
expect(again.statusCode).toBe(409);
expect(errorCode(again)).toBe("goal-already-claimed");
expect((await getState(testApp().app, owner)).company.credits).toBe(100 + reward);
});
it("refuses an incomplete goal and an unknown one", async () => {
const owner = await dealOwner();
await setCredits(testApp().app, 100);
const response = await claim(owner, "priority-contract");
expect(response.statusCode).toBe(409);
expect(errorCode(response)).toBe("goal-not-ready");
expect((await claim(owner, "win-the-game")).statusCode).toBe(404);
expect((await getState(testApp().app, owner)).company.credits).toBe(100);
});
it("refuses claims during the tutorial", async () => {
const guest = await signUp(testApp().app);
expect(errorCode(await claim(guest, "loan-paid-off"))).toBe("goal-not-ready");
});
});