135 lines
5.0 KiB
TypeScript
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");
|
|
});
|
|
});
|