import { content } from "@deadhead/content"; import { describe, expect, it } from "vitest"; import { createDispatchRecord, resolveDispatch, splitLoanRepayment, type Contract, } from "../src/index"; describe("loan repayment split", () => { it("takes min(balance, credits × share), rounded down to whole credits", () => { expect(splitLoanRepayment(1000, 5000, 0.3)).toEqual({ repayment: 300, net: 700 }); expect(splitLoanRepayment(1001, 5000, 0.3)).toEqual({ repayment: 300, net: 701 }); expect(splitLoanRepayment(1000, 120, 0.3)).toEqual({ repayment: 120, net: 880 }); }); it("takes nothing when there is no balance, or no positive payout", () => { expect(splitLoanRepayment(1000, 0, 0.3)).toEqual({ repayment: 0, net: 1000 }); expect(splitLoanRepayment(0, 5000, 0.3)).toEqual({ repayment: 0, net: 0 }); expect(splitLoanRepayment(-60, 5000, 0.3)).toEqual({ repayment: 0, net: -60 }); }); it("always splits the whole amount and never repays more than the balance", () => { for (const credits of [-500, -1, 0, 1, 7, 99, 1000, 123_457]) { for (const balance of [0, 1, 50, 299, 5000, 1_000_000]) { for (const share of [0, 0.1, 0.3, 1]) { const { repayment, net } = splitLoanRepayment(credits, balance, share); expect(net + repayment).toBe(credits); expect(repayment).toBeLessThanOrEqual(balance); expect(repayment).toBeGreaterThanOrEqual(0); expect(repayment).toBe( credits > 0 && balance > 0 ? Math.min(balance, Math.floor(credits * share)) : 0, ); } } } }); }); describe("resolution with a loan", () => { const now = Date.parse("2100-01-01T00:30:00Z"); const contract: Contract = { id: "c1", originId: "earth", destinationId: "moon", cargoTypeId: "standard", tier: 0, massTonnes: 10, payout: 2000, requiredSkill: null, requiredLongHaulLevel: 0, offeredAt: now, expiresAt: now + 3_600_000, firstContact: false, }; const record = createDispatchRecord( { id: "d1", ship: { id: "s1", modelId: "courier", locationId: "earth", inFlight: false }, pilot: { id: "p1", isAvatar: false, assignedShipId: "s1", inFlight: false, skills: {}, restEndsAt: null, }, contract, tutorial: false, now, worldSeed: 7, }, content, ); it("returns the repayment alongside the credits to add, after pilot pay", () => { const result = resolveDispatch( { record, pilotIsAvatar: false, discoveredIds: new Set(["earth", "moon"]), loanBalance: 5000, pilotDutyHours: 0, pilotSkills: {}, now: record.arrivalAt, }, content, ); if (result.status !== "resolved") throw new Error("expected a resolution"); const afterPay = 2000 - content.config.pilotPayPerDelivery; const repayment = Math.floor(afterPay * content.config.loanRepaymentShare); expect(result.resolution).toMatchObject({ loanRepayment: repayment, credits: afterPay - repayment, }); }); });