1
0
Fork 0
polaris-task-force/tests/int/operation-allocation.int.spec.ts
Z8MB1E 67c993ca10 feat(operations): mission reservations and deterministic settlement
Reserve personnel, vehicles, cargo, and budget with idempotent keys and exactly-once settlement; extraction effects preflight before any write so validated HQ deposits and locker credits are final and survive mission failure. Failure sweeps touch only unextracted reservations.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-09-20 02:43:47 -04:00

1071 lines
37 KiB
TypeScript

import { getPayload, Payload } from "payload";
import config from "@/payload.config";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import {
expireOperationReservations,
failOperationReservations,
reserveOperationAssets,
settleOperationReservation,
type SettlementRecord,
} from "@/lib/operations/allocation";
import { applyTransaction, createAccount, getMainCurrencyId } from "@/lib/banking";
import type { BankAccount, GameStructure, OperationReservation, Structure } from "@/payload-types";
let payload: Payload;
const RUN = `alloc-${Date.now().toString(36)}`;
const LEXICAL_EMPTY = {
root: {
children: [{ text: "" }],
direction: null,
format: "" as const,
indent: 0,
type: "text",
version: 1,
},
};
// ---------------------------------------------------------------------------
// Fixture tracking for cleanup
// ---------------------------------------------------------------------------
const reservationIds: number[] = [];
const syncEventIds: number[] = [];
const opEventIds: number[] = [];
const opEffectIds: number[] = [];
const userIds: number[] = [];
const roleIds: number[] = [];
const accountIds: number[] = [];
const gameStructureIds: number[] = [];
const blueprintIds: number[] = [];
const gameVehicleIds: number[] = [];
const vehicleBlueprintIds: number[] = [];
const resourceIds: number[] = [];
const missionIds: number[] = [];
const campaignIds: number[] = [];
const mapIds: number[] = [];
let serverId: number;
let commandUserId: number;
let plainUserId: number;
let soldierOneId: number;
let soldierTwoId: number;
let mapId: number;
let campaignId: number;
let missionId: number;
let resourceAId: number;
let resourceBId: number;
let normalBlueprintId: number;
let restrictedBlueprintId: number;
let vehicleBlueprintId: number;
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function relId(value: unknown): number {
return typeof value === "object" && value !== null
? (value as { id: number }).id
: (value as number);
}
function settlementOf(doc: OperationReservation): SettlementRecord {
return doc.settlement as unknown as SettlementRecord;
}
async function makeUser(label: string, roleDocIds?: number[]): Promise<number> {
const user = await payload.create({
collection: "users",
data: {
username: `${RUN}-${label}`,
discordUsername: `${RUN}-${label}`,
displayName: `${RUN} ${label}`,
steamId: `7656119${Math.floor(Math.random() * 1e9)}`,
password: "Test1234",
roles: ["user"],
...(roleDocIds && roleDocIds.length > 0 ? { roleDocs: roleDocIds } : {}),
},
overrideAccess: true,
depth: 0,
});
userIds.push(user.id);
return user.id;
}
async function makeMission(label: string): Promise<number> {
const mission = await payload.create({
collection: "missions",
data: {
name: `${RUN} ${label}`,
codeName: `${RUN}-${label}`,
summary: "Allocation test mission",
operationType: "main",
classification: {
map: mapId,
missionType: "PvE",
campaign: campaignId,
startDateTime: new Date(Date.now() + 86_400_000).toISOString(),
estimatedDuration: 60,
},
ownershipAndStatus: {
authors: [commandUserId],
status: "Scheduled",
visibility: "unit",
},
missionRoles: { maxPlayers: 16 },
gameDetails: { serverDetails: { serverIp: "127.0.0.1", serverPort: 2302 } },
briefing: [],
},
overrideAccess: true,
depth: 0,
});
missionIds.push(mission.id);
return mission.id;
}
async function makeOrigin(stock: { resourceId: number; amount: number }[]): Promise<number> {
const site = await payload.create({
collection: "game-structures",
data: {
name: `${RUN} origin ${gameStructureIds.length}`,
type: normalBlueprintId,
map: mapId,
coordinates: [100 + gameStructureIds.length, 100],
constructionStatus: "complete",
storedResources: stock.map((s, i) => ({
resource: s.resourceId,
amount: s.amount,
gridX: 0,
gridY: i,
rotated: false,
})),
},
overrideAccess: true,
depth: 0,
});
gameStructureIds.push(site.id);
return site.id;
}
async function makeDestination(restricted = false): Promise<number> {
const site = await payload.create({
collection: "game-structures",
data: {
name: `${RUN} dest ${gameStructureIds.length}`,
type: restricted ? restrictedBlueprintId : normalBlueprintId,
map: mapId,
coordinates: [500 + gameStructureIds.length, 500],
constructionStatus: "complete",
},
overrideAccess: true,
depth: 0,
});
gameStructureIds.push(site.id);
return site.id;
}
async function makeVehicle(atId: number, status: "idle" | "damaged" = "idle"): Promise<number> {
const vehicle = await payload.create({
collection: "game-vehicles",
data: {
name: `${RUN} vic ${gameVehicleIds.length}`,
type: vehicleBlueprintId,
deployedAt: atId,
status,
currentFuel: 1000,
currentHealth: 100,
},
overrideAccess: true,
depth: 0,
});
gameVehicleIds.push(vehicle.id);
return vehicle.id;
}
async function makeFundedAccount(amount: number): Promise<BankAccount> {
const account = await createAccount(payload, {
name: `${RUN} Treasury ${accountIds.length}`,
accountType: "treasury",
});
accountIds.push(account.id);
if (amount > 0) {
await applyTransaction(payload, {
type: "deposit",
toAccountId: account.id,
amount,
memo: `${RUN} test funding`,
});
}
return account;
}
async function findByKey(key: string): Promise<OperationReservation | null> {
const res = await payload.find({
collection: "operation-reservations",
where: { reservationKey: { equals: key } },
limit: 1,
depth: 0,
overrideAccess: true,
});
return (res.docs[0] as OperationReservation | undefined) ?? null;
}
async function storedAmountAt(siteId: number, resourceId: number): Promise<number> {
const doc = (await payload.findByID({
collection: "game-structures",
id: siteId,
depth: 0,
overrideAccess: true,
})) as unknown as GameStructure;
return (doc.storedResources ?? [])
.filter((r) => relId(r.resource) === resourceId)
.reduce((sum, r) => sum + r.amount, 0);
}
// ---------------------------------------------------------------------------
// Setup / teardown
// ---------------------------------------------------------------------------
beforeAll(async () => {
const payloadConfig = await config;
payload = await getPayload({ config: payloadConfig });
const currencyId = await getMainCurrencyId(payload);
if (!currencyId) throw new Error("Test requires a main currency in Game Rules.");
const superRole = await payload.create({
collection: "roles",
data: { name: `${RUN} Command`, slug: `${RUN}-command`, isSuperuser: true },
overrideAccess: true,
depth: 0,
});
roleIds.push(superRole.id);
commandUserId = await makeUser("command", [superRole.id]);
plainUserId = await makeUser("plain");
soldierOneId = await makeUser("soldier1");
soldierTwoId = await makeUser("soldier2");
const map = await payload.create({
collection: "maps",
data: { name: `${RUN} Map`, worldSizeWidth: 8192, worldSizeHeight: 8192, basemapMode: "image" },
overrideAccess: true,
depth: 0,
});
mapId = map.id;
mapIds.push(map.id);
const campaign = await payload.create({
collection: "campaigns",
data: {
name: `${RUN} Campaign`,
summary: "Allocation test campaign",
status: "concept",
campaignMode: "custom",
},
overrideAccess: true,
depth: 0,
});
campaignId = campaign.id;
campaignIds.push(campaign.id);
missionId = await makeMission("Main");
const server = await payload.create({
collection: "game-servers",
data: { serverId: `${RUN}-srv`, name: `${RUN} Server`, status: "online" },
overrideAccess: true,
depth: 0,
});
serverId = server.id;
for (const label of ["alpha", "bravo"] as const) {
const resource = await payload.create({
collection: "resources",
data: {
name: `${RUN} ${label}`,
codeName: `${RUN}-${label}`,
approvalStatus: "in_progress",
type: "physical",
baseValue: 1,
rarity: "common",
unitOfMeasure: "kg",
massPerUnit: 1,
gridWidth: 1,
gridHeight: 1,
},
overrideAccess: true,
depth: 0,
});
resourceIds.push(resource.id);
if (label === "alpha") resourceAId = resource.id;
else resourceBId = resource.id;
}
const normalBlueprint = await payload.create({
collection: "structures",
data: {
name: `${RUN} Depot`,
codeName: `${RUN}-depot`,
approvalStatus: "in_progress",
description: LEXICAL_EMPTY as unknown as Structure["description"],
category: "logistics",
materials: [{ resource: resourceAId, amount: 1 }],
constructionDurationMinutes: 1,
terrainType: "land",
maxHealth: 100,
},
overrideAccess: true,
depth: 0,
});
normalBlueprintId = normalBlueprint.id;
blueprintIds.push(normalBlueprint.id);
const restrictedBlueprint = await payload.create({
collection: "structures",
data: {
name: `${RUN} Vault`,
codeName: `${RUN}-vault`,
approvalStatus: "in_progress",
description: LEXICAL_EMPTY as unknown as Structure["description"],
category: "logistics",
materials: [{ resource: resourceAId, amount: 1 }],
constructionDurationMinutes: 1,
terrainType: "land",
maxHealth: 100,
restrictToAllowed: true,
allowedStorage: [],
},
overrideAccess: true,
depth: 0,
});
restrictedBlueprintId = restrictedBlueprint.id;
blueprintIds.push(restrictedBlueprint.id);
const vehicleBlueprint = await payload.create({
collection: "vehicles",
data: {
name: `${RUN} Truck`,
approvalStatus: "in_progress",
transportMode: "ground",
maxSpeedOnRoad: 60,
fuel: { fuelType: resourceAId, fuelCapacity: 1000, fuelConsumptionRate: 0.01 },
},
overrideAccess: true,
depth: 0,
});
vehicleBlueprintId = vehicleBlueprint.id;
vehicleBlueprintIds.push(vehicleBlueprint.id);
});
afterAll(async () => {
if (!payload) return;
for (const id of reservationIds) {
await payload.delete({ collection: "operation-reservations", id, overrideAccess: true }).catch(() => {});
}
for (const id of opEffectIds) {
await payload.delete({ collection: "operation-effects", id, overrideAccess: true }).catch(() => {});
}
for (const id of opEventIds) {
await payload.delete({ collection: "operation-events", id, overrideAccess: true }).catch(() => {});
}
for (const id of syncEventIds) {
await payload.delete({ collection: "arma-sync-events", id, overrideAccess: true }).catch(() => {});
}
for (const id of gameVehicleIds) {
await payload.delete({ collection: "game-vehicles", id, overrideAccess: true }).catch(() => {});
}
for (const id of gameStructureIds) {
await payload.delete({ collection: "game-structures", id, overrideAccess: true }).catch(() => {});
}
for (const id of vehicleBlueprintIds) {
await payload.delete({ collection: "vehicles", id, overrideAccess: true }).catch(() => {});
}
for (const id of blueprintIds) {
await payload.delete({ collection: "structures", id, overrideAccess: true }).catch(() => {});
}
for (const id of resourceIds) {
await payload.delete({ collection: "resources", id, overrideAccess: true }).catch(() => {});
}
for (const id of missionIds) {
await payload.delete({ collection: "missions", id, overrideAccess: true }).catch(() => {});
}
for (const id of campaignIds) {
await payload.delete({ collection: "campaigns", id, overrideAccess: true }).catch(() => {});
}
for (const id of mapIds) {
await payload.delete({ collection: "maps", id, overrideAccess: true }).catch(() => {});
}
const deleteAccountChain = async (accountId: number) => {
const txns = await payload.find({
collection: "bank-transactions",
where: { or: [{ fromAccount: { equals: accountId } }, { toAccount: { equals: accountId } }] },
limit: 100,
depth: 0,
overrideAccess: true,
});
for (const txn of txns.docs) {
const entries = await payload.find({
collection: "ledger-entries",
where: { transaction: { equals: txn.id } },
limit: 100,
depth: 0,
overrideAccess: true,
});
for (const entry of entries.docs) {
await payload.delete({ collection: "ledger-entries", id: entry.id, overrideAccess: true }).catch(() => {});
}
await payload.delete({ collection: "bank-transactions", id: txn.id, overrideAccess: true }).catch(() => {});
}
await payload.delete({ collection: "bank-accounts", id: accountId, overrideAccess: true }).catch(() => {});
};
for (const id of accountIds) {
await deleteAccountChain(id);
}
for (const userId of userIds) {
const profiles = await payload.find({
collection: "profiles",
where: { user: { equals: userId } },
limit: 5,
depth: 0,
overrideAccess: true,
});
for (const profile of profiles.docs) {
await payload.delete({ collection: "profiles", id: profile.id, overrideAccess: true }).catch(() => {});
}
const accounts = await payload.find({
collection: "bank-accounts",
where: { ownerUser: { equals: userId } },
limit: 5,
depth: 0,
overrideAccess: true,
});
for (const account of accounts.docs) {
await deleteAccountChain(account.id);
}
await payload.delete({ collection: "users", id: userId, overrideAccess: true }).catch(() => {});
}
for (const id of roleIds) {
await payload.delete({ collection: "roles", id, overrideAccess: true }).catch(() => {});
}
if (serverId) {
await payload.delete({ collection: "game-servers", id: serverId, overrideAccess: true }).catch(() => {});
}
});
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe("Operation allocation", () => {
describe("reserveOperationAssets", () => {
it("reserves personnel, vehicles, cargo, and budget in one record", async () => {
const originId = await makeOrigin([{ resourceId: resourceAId, amount: 100 }]);
const destinationId = await makeDestination();
const vehicleId = await makeVehicle(originId);
const account = await makeFundedAccount(1000);
const key = `${RUN}-happy`;
const doc = await reserveOperationAssets(payload, {
reservationKey: key,
operationId: `${RUN}-op-happy`,
actorId: commandUserId,
missionId,
personnel: [{ userId: soldierOneId, slot: "Team Lead" }],
vehicleIds: [vehicleId],
cargo: [{ resourceId: resourceAId, amount: 40 }],
budget: { accountId: account.id, amount: 200 },
originId,
destinationId,
expiresAt: new Date(Date.now() + 3_600_000).toISOString(),
});
reservationIds.push(doc.id);
expect(doc.reservationKey).toBe(key);
expect(doc.status).toBe("reserved");
expect(doc.operationId).toBe(`${RUN}-op-happy`);
expect(relId(doc.mission)).toBe(missionId);
expect(doc.personnel).toHaveLength(1);
expect(relId(doc.personnel?.[0]?.user)).toBe(soldierOneId);
expect(doc.personnel?.[0]?.slot).toBe("Team Lead");
expect(doc.vehicles).toHaveLength(1);
expect(relId(doc.vehicles?.[0]?.vehicle)).toBe(vehicleId);
expect(doc.cargo).toHaveLength(1);
expect(relId(doc.cargo?.[0]?.resource)).toBe(resourceAId);
expect(doc.cargo?.[0]?.amount).toBe(40);
expect(relId(doc.budget?.account)).toBe(account.id);
expect(doc.budget?.amount).toBe(200);
expect(relId(doc.origin)).toBe(originId);
expect(relId(doc.destination)).toBe(destinationId);
expect(relId(doc.createdBy)).toBe(commandUserId);
});
it("is idempotent for a retry with the same reservation key", async () => {
const originId = await makeOrigin([{ resourceId: resourceAId, amount: 50 }]);
const key = `${RUN}-retry`;
const input = {
reservationKey: key,
operationId: `${RUN}-op-retry`,
actorId: commandUserId,
cargo: [{ resourceId: resourceAId, amount: 10 }],
originId,
};
const first = await reserveOperationAssets(payload, input);
const second = await reserveOperationAssets(payload, input);
reservationIds.push(first.id);
expect(second.id).toBe(first.id);
const all = await payload.find({
collection: "operation-reservations",
where: { reservationKey: { equals: key } },
limit: 10,
depth: 0,
overrideAccess: true,
});
expect(all.docs).toHaveLength(1);
});
it("rejects an unauthorized actor without writing anything", async () => {
const originId = await makeOrigin([{ resourceId: resourceAId, amount: 100 }]);
const key = `${RUN}-unauthorized`;
await expect(
reserveOperationAssets(payload, {
reservationKey: key,
operationId: `${RUN}-op-unauthorized`,
actorId: plainUserId,
cargo: [{ resourceId: resourceAId, amount: 10 }],
originId,
}),
).rejects.toThrow(/not authorized/i);
expect(await findByKey(key)).toBeNull();
});
it("fails insufficient cargo without partial writes", async () => {
const originId = await makeOrigin([{ resourceId: resourceAId, amount: 100 }]);
const key = `${RUN}-insufficient`;
await expect(
reserveOperationAssets(payload, {
reservationKey: key,
operationId: `${RUN}-op-insufficient`,
actorId: commandUserId,
cargo: [{ resourceId: resourceAId, amount: 101 }],
originId,
}),
).rejects.toThrow(/insufficient cargo/i);
expect(await findByKey(key)).toBeNull();
expect(await storedAmountAt(originId, resourceAId)).toBe(100);
});
it("requires an origin structure when cargo is reserved", async () => {
const key = `${RUN}-no-origin`;
await expect(
reserveOperationAssets(payload, {
reservationKey: key,
operationId: `${RUN}-op-no-origin`,
actorId: commandUserId,
cargo: [{ resourceId: resourceAId, amount: 1 }],
}),
).rejects.toThrow(/origin/i);
expect(await findByKey(key)).toBeNull();
});
it("accounts for cargo already reserved by other active reservations", async () => {
const originId = await makeOrigin([{ resourceId: resourceAId, amount: 100 }]);
const first = await reserveOperationAssets(payload, {
reservationKey: `${RUN}-spend-1`,
operationId: `${RUN}-op-spend`,
actorId: commandUserId,
cargo: [{ resourceId: resourceAId, amount: 60 }],
originId,
});
reservationIds.push(first.id);
await expect(
reserveOperationAssets(payload, {
reservationKey: `${RUN}-spend-2`,
operationId: `${RUN}-op-spend`,
actorId: commandUserId,
cargo: [{ resourceId: resourceAId, amount: 50 }],
originId,
}),
).rejects.toThrow(/insufficient cargo/i);
expect(await findByKey(`${RUN}-spend-2`)).toBeNull();
const third = await reserveOperationAssets(payload, {
reservationKey: `${RUN}-spend-3`,
operationId: `${RUN}-op-spend`,
actorId: commandUserId,
cargo: [{ resourceId: resourceAId, amount: 40 }],
originId,
});
reservationIds.push(third.id);
expect(third.status).toBe("reserved");
});
it("enforces destination storage rules at reserve time", async () => {
const originId = await makeOrigin([{ resourceId: resourceAId, amount: 100 }]);
const restrictedDestinationId = await makeDestination(true);
const key = `${RUN}-dest-rules`;
await expect(
reserveOperationAssets(payload, {
reservationKey: key,
operationId: `${RUN}-op-dest-rules`,
actorId: commandUserId,
cargo: [{ resourceId: resourceAId, amount: 10 }],
originId,
destinationId: restrictedDestinationId,
}),
).rejects.toThrow(/allowed storage list/i);
expect(await findByKey(key)).toBeNull();
});
it("rejects unavailable vehicles", async () => {
const originId = await makeOrigin([]);
const otherOriginId = await makeOrigin([]);
const reservedVehicleId = await makeVehicle(originId);
const damagedVehicleId = await makeVehicle(originId, "damaged");
const elsewhereVehicleId = await makeVehicle(otherOriginId);
const first = await reserveOperationAssets(payload, {
reservationKey: `${RUN}-vic-1`,
operationId: `${RUN}-op-vic`,
actorId: commandUserId,
vehicleIds: [reservedVehicleId],
originId,
});
reservationIds.push(first.id);
await expect(
reserveOperationAssets(payload, {
reservationKey: `${RUN}-vic-2`,
operationId: `${RUN}-op-vic`,
actorId: commandUserId,
vehicleIds: [reservedVehicleId],
originId,
}),
).rejects.toThrow(/already reserved/i);
await expect(
reserveOperationAssets(payload, {
reservationKey: `${RUN}-vic-3`,
operationId: `${RUN}-op-vic`,
actorId: commandUserId,
vehicleIds: [damagedVehicleId],
originId,
}),
).rejects.toThrow(/not available/i);
await expect(
reserveOperationAssets(payload, {
reservationKey: `${RUN}-vic-4`,
operationId: `${RUN}-op-vic`,
actorId: commandUserId,
vehicleIds: [elsewhereVehicleId],
originId,
}),
).rejects.toThrow(/not stationed/i);
expect(await findByKey(`${RUN}-vic-2`)).toBeNull();
expect(await findByKey(`${RUN}-vic-3`)).toBeNull();
expect(await findByKey(`${RUN}-vic-4`)).toBeNull();
});
it("rejects an overdrawn treasury budget, including other reservations' earmarks", async () => {
const account = await makeFundedAccount(500);
await expect(
reserveOperationAssets(payload, {
reservationKey: `${RUN}-budget-1`,
operationId: `${RUN}-op-budget`,
actorId: commandUserId,
budget: { accountId: account.id, amount: 600 },
}),
).rejects.toThrow(/insufficient budget/i);
const ok = await reserveOperationAssets(payload, {
reservationKey: `${RUN}-budget-2`,
operationId: `${RUN}-op-budget`,
actorId: commandUserId,
budget: { accountId: account.id, amount: 500 },
});
reservationIds.push(ok.id);
await expect(
reserveOperationAssets(payload, {
reservationKey: `${RUN}-budget-3`,
operationId: `${RUN}-op-budget`,
actorId: commandUserId,
budget: { accountId: account.id, amount: 1 },
}),
).rejects.toThrow(/insufficient budget/i);
expect(await findByKey(`${RUN}-budget-1`)).toBeNull();
expect(await findByKey(`${RUN}-budget-3`)).toBeNull();
const fresh = (await payload.findByID({
collection: "bank-accounts",
id: account.id,
depth: 0,
overrideAccess: true,
})) as unknown as BankAccount;
expect(fresh.balance).toBe(500);
});
it("rejects personnel already booked on the same mission", async () => {
const personnelMissionId = await makeMission("Personnel");
const first = await reserveOperationAssets(payload, {
reservationKey: `${RUN}-pers-1`,
operationId: `${RUN}-op-pers`,
actorId: commandUserId,
missionId: personnelMissionId,
personnel: [{ userId: soldierTwoId }],
});
reservationIds.push(first.id);
await expect(
reserveOperationAssets(payload, {
reservationKey: `${RUN}-pers-2`,
operationId: `${RUN}-op-pers`,
actorId: commandUserId,
missionId: personnelMissionId,
personnel: [{ userId: soldierTwoId }],
}),
).rejects.toThrow(/already reserved/i);
const otherMission = await reserveOperationAssets(payload, {
reservationKey: `${RUN}-pers-3`,
operationId: `${RUN}-op-pers`,
actorId: commandUserId,
missionId,
personnel: [{ userId: soldierTwoId }],
});
reservationIds.push(otherMission.id);
expect(otherMission.status).toBe("reserved");
});
});
describe("settleOperationReservation", () => {
it("cancels: returns everything, exactly once", async () => {
const originId = await makeOrigin([{ resourceId: resourceAId, amount: 100 }]);
const vehicleId = await makeVehicle(originId);
const account = await makeFundedAccount(300);
const doc = await reserveOperationAssets(payload, {
reservationKey: `${RUN}-cancel`,
operationId: `${RUN}-op-cancel`,
actorId: commandUserId,
personnel: [{ userId: soldierOneId }],
vehicleIds: [vehicleId],
cargo: [{ resourceId: resourceAId, amount: 25 }],
budget: { accountId: account.id, amount: 120 },
originId,
});
reservationIds.push(doc.id);
const settled = await settleOperationReservation(
payload,
{ reservationId: doc.id },
{ outcome: "cancelled", actorId: commandUserId },
);
expect(settled.status).toBe("cancelled");
expect(settled.settledAt).toBeTruthy();
const record = settlementOf(settled);
expect(record.outcome).toBe("cancelled");
expect(record.reason).toBe("cancelled");
expect(record.consumed).toEqual({ personnel: [], vehicles: [], cargo: [], budget: 0 });
expect(record.returned.personnel).toEqual([soldierOneId]);
expect(record.returned.vehicles).toEqual([vehicleId]);
expect(record.returned.cargo).toEqual([{ resource: resourceAId, amount: 25 }]);
expect(record.returned.budget).toBe(120);
// Second settle is a no-op: same status, same settlement timestamp.
const again = await settleOperationReservation(
payload,
{ reservationId: doc.id },
{ outcome: "cancelled", actorId: commandUserId },
);
expect(again.status).toBe("cancelled");
expect(again.settledAt).toBe(settled.settledAt);
// A different outcome cannot re-settle either.
const third = await settleOperationReservation(
payload,
{ reservationId: doc.id },
{ outcome: "settled", consumed: { budgetAmount: 120 }, actorId: commandUserId },
);
expect(third.status).toBe("cancelled");
expect(third.settledAt).toBe(settled.settledAt);
});
it("settles a partial success with exact consumed/returned math, exactly once", async () => {
const originId = await makeOrigin([
{ resourceId: resourceAId, amount: 100 },
{ resourceId: resourceBId, amount: 10 },
]);
const vehicleId = await makeVehicle(originId);
const account = await makeFundedAccount(400);
const doc = await reserveOperationAssets(payload, {
reservationKey: `${RUN}-partial`,
operationId: `${RUN}-op-partial`,
actorId: commandUserId,
personnel: [{ userId: soldierOneId }, { userId: soldierTwoId }],
vehicleIds: [vehicleId],
cargo: [
{ resourceId: resourceAId, amount: 50 },
{ resourceId: resourceBId, amount: 10 },
],
budget: { accountId: account.id, amount: 300 },
originId,
});
reservationIds.push(doc.id);
// Over-consumption is rejected before any write.
await expect(
settleOperationReservation(
payload,
{ reservationId: doc.id },
{ outcome: "settled", consumed: { cargo: [{ resourceId: resourceAId, amount: 51 }] } },
),
).rejects.toThrow(/exceeds the reserved amount/i);
expect((await findByKey(`${RUN}-partial`))?.status).toBe("reserved");
const settled = await settleOperationReservation(
payload,
{ reservationId: doc.id },
{
outcome: "settled",
reason: "partial-success",
consumed: {
personnel: [soldierOneId],
vehicleIds: [vehicleId],
cargo: [{ resourceId: resourceAId, amount: 20 }],
budgetAmount: 100,
},
actorId: commandUserId,
},
);
expect(settled.status).toBe("settled");
const record = settlementOf(settled);
expect(record.outcome).toBe("settled");
expect(record.reason).toBe("partial-success");
expect(record.consumed.personnel).toEqual([soldierOneId]);
expect(record.consumed.vehicles).toEqual([vehicleId]);
expect(record.consumed.cargo).toEqual([{ resource: resourceAId, amount: 20 }]);
expect(record.consumed.budget).toBe(100);
expect(record.returned.personnel).toEqual([soldierTwoId]);
expect(record.returned.vehicles).toEqual([]);
expect(record.returned.cargo).toEqual([
{ resource: resourceAId, amount: 30 },
{ resource: resourceBId, amount: 10 },
]);
expect(record.returned.budget).toBe(200);
const again = await settleOperationReservation(
payload,
{ reservationId: doc.id },
{ outcome: "settled", reason: "partial-success", consumed: { budgetAmount: 100 } },
);
expect(again.settledAt).toBe(settled.settledAt);
expect(settlementOf(again).consumed.budget).toBe(100);
});
it("records a no-show as a distinct, auditable outcome", async () => {
const originId = await makeOrigin([{ resourceId: resourceAId, amount: 50 }]);
const doc = await reserveOperationAssets(payload, {
reservationKey: `${RUN}-noshow`,
operationId: `${RUN}-op-noshow`,
actorId: commandUserId,
personnel: [{ userId: soldierOneId }],
cargo: [{ resourceId: resourceAId, amount: 10 }],
originId,
});
reservationIds.push(doc.id);
const settled = await settleOperationReservation(
payload,
{ reservationKey: `${RUN}-noshow` },
{ outcome: "no-show", actorId: commandUserId },
);
expect(settled.status).toBe("no-show");
const record = settlementOf(settled);
expect(record.outcome).toBe("no-show");
expect(record.reason).toBe("no-show");
expect(record.consumed).toEqual({ personnel: [], vehicles: [], cargo: [], budget: 0 });
expect(record.returned.personnel).toEqual([soldierOneId]);
expect(record.returned.cargo).toEqual([{ resource: resourceAId, amount: 10 }]);
});
});
describe("expireOperationReservations", () => {
it("settles overdue reservations as expired and leaves future ones reserved", async () => {
const originId = await makeOrigin([{ resourceId: resourceAId, amount: 100 }]);
const overdue = await reserveOperationAssets(payload, {
reservationKey: `${RUN}-exp-overdue`,
operationId: `${RUN}-op-exp`,
actorId: commandUserId,
cargo: [{ resourceId: resourceAId, amount: 5 }],
originId,
expiresAt: new Date(Date.now() - 60_000).toISOString(),
});
reservationIds.push(overdue.id);
const future = await reserveOperationAssets(payload, {
reservationKey: `${RUN}-exp-future`,
operationId: `${RUN}-op-exp`,
actorId: commandUserId,
cargo: [{ resourceId: resourceAId, amount: 5 }],
originId,
expiresAt: new Date(Date.now() + 3_600_000).toISOString(),
});
reservationIds.push(future.id);
const settledCount = await expireOperationReservations(payload);
expect(settledCount).toBeGreaterThanOrEqual(1);
const overdueDoc = await findByKey(`${RUN}-exp-overdue`);
expect(overdueDoc?.status).toBe("expired");
expect(settlementOf(overdueDoc as OperationReservation).returned.cargo).toEqual([
{ resource: resourceAId, amount: 5 },
]);
const futureDoc = await findByKey(`${RUN}-exp-future`);
expect(futureDoc?.status).toBe("reserved");
// Sweeping again is a no-op for the already-expired row.
const secondCount = await expireOperationReservations(payload);
const overdueAgain = await findByKey(`${RUN}-exp-overdue`);
expect(overdueAgain?.settledAt).toBe(overdueDoc?.settledAt);
expect(secondCount).toBe(0);
});
});
describe("failOperationReservations", () => {
it("settles only unextracted reservations and never reverses a recorded extraction", async () => {
const operationId = `${RUN}-op-fail`;
// Validated extraction evidence through the real hook path.
const syncEvent = await payload.create({
collection: "arma-sync-events",
data: {
eventId: `${RUN}-extract-1`,
server: serverId,
type: "operation.extraction",
occurredAt: new Date().toISOString(),
payload: { operationId, version: 1, player: `${RUN} soldier1` },
},
overrideAccess: true,
depth: 0,
});
syncEventIds.push(syncEvent.id);
const opEvents = await payload.find({
collection: "operation-events",
where: { sourceEvent: { equals: syncEvent.id } },
limit: 1,
depth: 0,
overrideAccess: true,
});
expect(opEvents.docs).toHaveLength(1);
opEventIds.push(opEvents.docs[0].id);
const effects = await payload.find({
collection: "operation-effects",
where: { operationEvent: { equals: opEvents.docs[0].id } },
limit: 10,
depth: 0,
overrideAccess: true,
});
expect(effects.docs).toHaveLength(1);
const extractionEffect = effects.docs[0];
expect(extractionEffect.effectType).toBe("operation.extraction-recorded");
expect(extractionEffect.status).toBe("applied");
opEffectIds.push(extractionEffect.id);
// One reservation already settled by the extraction (partial success);
// one still reserved (unextracted).
const originId = await makeOrigin([{ resourceId: resourceAId, amount: 100 }]);
const extracted = await reserveOperationAssets(payload, {
reservationKey: `${RUN}-fail-extracted`,
operationId,
actorId: commandUserId,
cargo: [{ resourceId: resourceAId, amount: 10 }],
originId,
});
reservationIds.push(extracted.id);
const preSettled = await settleOperationReservation(
payload,
{ reservationId: extracted.id },
{
outcome: "settled",
reason: "partial-success",
consumed: { cargo: [{ resourceId: resourceAId, amount: 4 }] },
effectId: extractionEffect.id,
actorId: commandUserId,
},
);
expect(preSettled.status).toBe("settled");
const pending = await reserveOperationAssets(payload, {
reservationKey: `${RUN}-fail-pending`,
operationId,
actorId: commandUserId,
cargo: [{ resourceId: resourceAId, amount: 10 }],
originId,
});
reservationIds.push(pending.id);
const settledCount = await failOperationReservations(payload, operationId, {
effectId: extractionEffect.id,
actorId: commandUserId,
});
expect(settledCount).toBe(1);
// The still-reserved row settles as mission-failed with everything returned.
const failedDoc = await findByKey(`${RUN}-fail-pending`);
expect(failedDoc?.status).toBe("settled");
const failedRecord = settlementOf(failedDoc as OperationReservation);
expect(failedRecord.reason).toBe("mission-failed");
expect(failedRecord.consumed).toEqual({ personnel: [], vehicles: [], cargo: [], budget: 0 });
expect(failedRecord.returned.cargo).toEqual([{ resource: resourceAId, amount: 10 }]);
expect(failedRecord.effectId).toBe(extractionEffect.id);
expect(relId(failedDoc?.settledByEffect)).toBe(extractionEffect.id);
// The previously settled (extracted) row is untouched.
const extractedDoc = await findByKey(`${RUN}-fail-extracted`);
expect(extractedDoc?.status).toBe("settled");
expect(extractedDoc?.settledAt).toBe(preSettled.settledAt);
const extractedRecord = settlementOf(extractedDoc as OperationReservation);
expect(extractedRecord.reason).toBe("partial-success");
expect(extractedRecord.consumed.cargo).toEqual([{ resource: resourceAId, amount: 4 }]);
// The extraction effect is final: still applied, never reversed, and
// failure settlement wrote no new effects for the operation.
const freshEffect = await payload.findByID({
collection: "operation-effects",
id: extractionEffect.id,
depth: 0,
overrideAccess: true,
});
expect(freshEffect.status).toBe("applied");
expect(freshEffect.reversedBy).toBeNull();
const effectsAfter = await payload.find({
collection: "operation-effects",
where: { operationId: { equals: operationId } },
limit: 10,
depth: 0,
overrideAccess: true,
});
expect(effectsAfter.docs).toHaveLength(1);
});
});
});