deadhead-space-freight-tycoon/apps/server/test/loan.test.ts

133 lines
4.7 KiB
TypeScript

import { content } from "@deadhead/content";
import { companyResponseSchema, errorResponseSchema } from "@deadhead/schema";
import { eq } from "drizzle-orm";
import { describe, expect, it } from "vitest";
import { companies } from "../src/db/schema";
import { setCredits, useTestDatabase } from "./db";
import {
call,
getState,
insertDispatch,
signUpOwner,
testContract,
useTestApp,
type Guest,
} from "./game";
const database = useTestDatabase();
const testApp = useTestApp(database);
async function setLoan(guest: Guest, loanBalance: number) {
await database()
.db.update(companies)
.set({ loanBalance })
.where(eq(companies.playerId, guest.playerId));
}
const share = content.config.loanRepaymentShare;
describe("loan repayment on delivery", () => {
it("adds the net to credits, lowers the balance by the repayment, and records it", async () => {
const { app, clock } = testApp();
const owner = await signUpOwner(app, database().db);
await setCredits(database().db, owner.playerId, 1000);
await setLoan(owner, 5000);
const record = await insertDispatch(database().db, {
playerId: owner.playerId,
shipId: owner.shipId,
pilotId: owner.avatarId,
departAt: clock.now,
contract: testContract({ payout: 2000 }),
});
clock.now = record.arrivalAt;
const state = await getState(app, owner);
// The avatar flew it, so there is no pilot pay.
const repayment = Math.floor(2000 * share);
expect(state.company).toMatchObject({
credits: 1000 + 2000 - repayment,
loanBalance: 5000 - repayment,
});
expect(state.summaries[0]).toMatchObject({
deadhead: false,
credits: 2000 - repayment,
loanRepayment: repayment,
});
});
it("repays only what is owed when the balance is small", async () => {
const { app, clock } = testApp();
const owner = await signUpOwner(app, database().db);
await setCredits(database().db, owner.playerId, 0);
await setLoan(owner, 50);
const record = await insertDispatch(database().db, {
playerId: owner.playerId,
shipId: owner.shipId,
pilotId: owner.avatarId,
departAt: clock.now,
contract: testContract({ payout: 2000 }),
});
clock.now = record.arrivalAt;
const state = await getState(app, owner);
expect(state.company).toMatchObject({ credits: 1950, loanBalance: 0 });
});
it("puts the full amount into credits when there is no balance", async () => {
const { app, clock } = testApp();
const owner = await signUpOwner(app, database().db);
await setCredits(database().db, owner.playerId, 0);
const record = await insertDispatch(database().db, {
playerId: owner.playerId,
shipId: owner.shipId,
pilotId: owner.avatarId,
departAt: clock.now,
contract: testContract({ payout: 2000 }),
});
clock.now = record.arrivalAt;
const state = await getState(app, owner);
expect(state.company).toMatchObject({ credits: 2000, loanBalance: 0 });
expect(state.summaries[0]!.loanRepayment).toBe(0);
});
});
describe("paying down the loan", () => {
const repay = (guest: Guest, amount: number) =>
call(testApp().app, guest, "POST", "/api/loan/repay", { amount });
it("moves the amount from credits to the loan", async () => {
const owner = await signUpOwner(testApp().app, database().db);
await setCredits(database().db, owner.playerId, 3000);
await setLoan(owner, 5000);
const response = await repay(owner, 1200);
expect(response.statusCode).toBe(200);
expect(companyResponseSchema.parse(response.json()).company).toMatchObject({
credits: 1800,
loanBalance: 3800,
});
});
it("takes only the balance when asked to repay more", async () => {
const owner = await signUpOwner(testApp().app, database().db);
await setCredits(database().db, owner.playerId, 3000);
await setLoan(owner, 400);
const { company } = companyResponseSchema.parse((await repay(owner, 2500)).json());
expect(company).toMatchObject({ credits: 2600, loanBalance: 0 });
});
it("fails with no changes when repaying more than the player has", async () => {
const { app } = testApp();
const owner = await signUpOwner(app, database().db);
await setCredits(database().db, owner.playerId, 300);
await setLoan(owner, 5000);
const response = await repay(owner, 1000);
expect(response.statusCode).toBe(409);
expect(errorResponseSchema.parse(response.json()).error.code).toBe("insufficient-funds");
expect((await getState(app, owner)).company).toMatchObject({ credits: 300, loanBalance: 5000 });
});
it("rejects a non-positive amount", async () => {
const owner = await signUpOwner(testApp().app, database().db);
expect((await repay(owner, 0)).statusCode).toBe(400);
});
});