133 lines
4.7 KiB
TypeScript
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);
|
|
});
|
|
});
|