1
0
Fork 0
polaris-task-force/tests/int/operation-readiness.int.spec.ts
Z8MB1E 3c70160dfc feat(operations): readiness and zone pressure aggregates
Deterministic, explainable, advisory readiness aggregates per subject behind a permission gate, and mission-scoped zone pressure with read-time decay yielding route-risk and yield multipliers.

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

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

352 lines
12 KiB
TypeScript

import { getPayload, Payload } from "payload";
import config from "@/payload.config";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { computeReadiness, getReadiness, type ReadinessScope } from "@/lib/operations/readiness";
import { READINESS_CALCULATION_VERSION } from "@/lib/operations/readinessTypes";
let payload: Payload;
const RUN = `ready-${Date.now().toString(36)}`;
// ---------------------------------------------------------------------------
// Fixture tracking for cleanup
// ---------------------------------------------------------------------------
const syncEventIds: number[] = [];
const opEventIds: number[] = [];
const opEffectIds: number[] = [];
const userIds: number[] = [];
const roleIds: number[] = [];
const missionIds: number[] = [];
const attendanceIds: number[] = [];
const evaluationIds: number[] = [];
const campaignIds: number[] = [];
const mapIds: number[] = [];
const readinessIds: number[] = [];
let serverId: number;
let subjectId: number;
let raterId: number;
let adminId: number;
let mapId: number;
let campaignId: number;
let windowStart: Date;
let windowEnd: Date;
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
async function makeUser(label: string, roleDocIds?: number[]): Promise<number> {
const user = await payload.create({
collection: "users",
data: {
username: `${RUN}-${label}`,
discordUsername: `${RUN}-${label}-discord`,
displayName: `${RUN} ${label}`,
steamId: `${RUN}-steam-${label}`,
password: "Test1234",
...(roleDocIds && roleDocIds.length > 0 ? { roleDocs: roleDocIds } : {}),
},
overrideAccess: true,
depth: 0,
});
userIds.push(user.id);
return user.id;
}
async function makeMission(label: string, authorId: number): Promise<number> {
const mission = await payload.create({
collection: "missions",
data: {
name: `${RUN} ${label}`,
codeName: `${RUN}-${label}`,
summary: "Readiness test mission",
operationType: "main",
classification: {
map: mapId,
missionType: "PvE",
campaign: campaignId,
startDateTime: new Date(Date.now() + 86_400_000).toISOString(),
estimatedDuration: 60,
},
ownershipAndStatus: {
authors: [authorId],
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 makeAttendance(missionId: number, userId: number): Promise<number> {
const doc = await payload.create({
collection: "mission-attendances",
data: { mission: missionId, user: userId, response: "yes" },
overrideAccess: true,
depth: 0,
});
attendanceIds.push(doc.id);
return doc.id;
}
async function makeEvaluation(missionId: number, ratee: number): Promise<number> {
const doc = await payload.create({
collection: "evaluations",
data: { rater: raterId, ratee, mission: missionId, kind: "leader-direct", level: "proficient" },
overrideAccess: true,
depth: 0,
});
evaluationIds.push(doc.id);
return doc.id;
}
async function makeObjectiveEvent(label: string, actor: number): Promise<void> {
const sync = await payload.create({
collection: "arma-sync-events",
data: {
eventId: `${RUN}-${label}`,
server: serverId,
type: "operation.objective",
occurredAt: new Date().toISOString(),
payload: { operationId: RUN, version: 1, objective: "Cache secured", actorId: actor },
},
overrideAccess: true,
depth: 0,
});
syncEventIds.push(sync.id);
const events = await payload.find({
collection: "operation-events",
where: { sourceEvent: { equals: sync.id } },
limit: 1,
depth: 0,
overrideAccess: true,
});
opEventIds.push(events.docs[0].id);
const effects = await payload.find({
collection: "operation-effects",
where: { operationEvent: { equals: events.docs[0].id } },
limit: 1,
depth: 0,
overrideAccess: true,
});
opEffectIds.push(effects.docs[0].id);
}
function scopeFor(windowStart: Date, windowEnd: Date): ReadinessScope {
return {
subjectType: "user",
subjectId,
windowStart,
windowEnd,
windowLabel: `${RUN}-window`,
operationId: RUN,
};
}
// ---------------------------------------------------------------------------
// Setup / teardown
// ---------------------------------------------------------------------------
beforeAll(async () => {
const payloadConfig = await config;
payload = await getPayload({ config: payloadConfig });
// One shared, minute-floored window: the aggregate identity is
// `subjectType:subjectId:windowStart`, so every test that expects to find
// another test's row must use the exact same windowStart instant.
const flooredMinute = Math.floor(Date.now() / 60_000) * 60_000;
windowStart = new Date(flooredMinute - 3_600_000);
windowEnd = new Date(flooredMinute + 3_600_000);
const superRole = await payload.create({
collection: "roles",
data: { name: `${RUN} Readiness Admin`, slug: `${RUN}-readiness-admin`, isSuperuser: true },
overrideAccess: true,
depth: 0,
});
roleIds.push(superRole.id);
const server = await payload.create({
collection: "game-servers",
data: { serverId: `${RUN}-srv`, name: `${RUN} Server`, status: "online" },
overrideAccess: true,
depth: 0,
});
serverId = server.id;
subjectId = await makeUser("subject");
raterId = await makeUser("rater");
adminId = await makeUser("admin", [superRole.id]);
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: "Readiness test campaign",
status: "concept",
campaignMode: "custom",
},
overrideAccess: true,
depth: 0,
});
campaignId = campaign.id;
campaignIds.push(campaign.id);
});
afterAll(async () => {
if (!payload) return;
const readinessRows = await payload.find({
collection: "operation-readiness",
where: { operationId: { equals: RUN } },
limit: 100,
depth: 0,
overrideAccess: true,
});
for (const row of readinessRows.docs) {
await payload.delete({ collection: "operation-readiness", id: row.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 evaluationIds) {
await payload.delete({ collection: "evaluations", id, overrideAccess: true }).catch(() => {});
}
for (const id of attendanceIds) {
await payload.delete({ collection: "mission-attendances", 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(() => {});
}
for (const id of userIds) {
await payload.delete({ collection: "users", id, overrideAccess: true }).catch(() => {});
}
for (const id of roleIds) {
await payload.delete({ collection: "roles", id, overrideAccess: true }).catch(() => {});
}
});
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe("Operation readiness", () => {
it("computes a deterministic aggregate from window inputs and stores it with an explanation", async () => {
const mission = await makeMission("Alpha", adminId);
await makeAttendance(mission, subjectId);
await makeEvaluation(mission, subjectId);
await makeObjectiveEvent("obj-1", subjectId);
const now = new Date();
const result = (await computeReadiness(payload, scopeFor(windowStart, windowEnd))) as {
id: number;
readinessScore: number;
planningConfidenceScore: number;
contributingCategories: { category: string; weight: number; score: number; inputCount: number }[];
calculationVersion: string;
nature: string;
explanation: string;
};
readinessIds.push(result.id);
const categories = result.contributingCategories.map((c) => c.category);
expect(categories).toContain("attendance");
expect(categories).toContain("evaluation");
expect(categories).toContain("objectives");
expect(result.calculationVersion).toBe(READINESS_CALCULATION_VERSION);
expect(result.nature).toBe("advisory");
const attendance = result.contributingCategories.find((c) => c.category === "attendance");
expect(attendance?.inputCount).toBe(1);
expect(attendance?.score).toBe(34);
const evaluation = result.contributingCategories.find((c) => c.category === "evaluation");
expect(evaluation?.score).toBe(70);
expect(result.explanation).toContain("Attendance");
expect(result.explanation).not.toContain(raterDisplayName());
expect(result.explanation).not.toContain("evaluator");
});
it("returns the stored row unchanged when inputs are unchanged (no drift)", async () => {
const first = (await computeReadiness(payload, scopeFor(windowStart, windowEnd))) as { id: number; readinessScore: number };
const second = (await computeReadiness(payload, scopeFor(windowStart, windowEnd))) as { id: number; readinessScore: number };
expect(second.id).toBe(first.id);
expect(second.readinessScore).toBe(first.readinessScore);
});
it("replaces the stored aggregate atomically when inputs change", async () => {
const scope = scopeFor(windowStart, windowEnd);
const before = (await computeReadiness(payload, scope)) as { id: number; readinessScore: number };
const missions = missionIds;
await makeAttendance(missions[0], subjectId);
const after = (await computeReadiness(payload, scope)) as {
id: number;
readinessScore: number;
contributingCategories: { category: string; inputCount: number }[];
};
expect(after.id).toBe(before.id);
const attendance = after.contributingCategories.find((c) => c.category === "attendance");
expect(attendance?.inputCount).toBe(2);
expect(after.readinessScore).not.toBe(before.readinessScore);
});
it("excludes inputs outside the window and omits absent categories", async () => {
const future = scopeFor(new Date(Date.now() + 86_400_000), new Date(Date.now() + 7 * 86_400_000));
const result = (await computeReadiness(payload, future)) as {
readinessScore: number;
contributingCategories: unknown[];
explanation: string;
};
expect(result.contributingCategories).toHaveLength(0);
expect(result.readinessScore).toBe(0);
expect(result.explanation).toContain("No qualifying operation data");
});
it("gates reads behind the readiness permission without leaking evaluator content", async () => {
const scope = scopeFor(windowStart, windowEnd);
const forbidden = await getReadiness(payload, { subjectType: "user", subjectId, windowStart: scope.windowStart }, {
id: raterId,
});
expect(forbidden.status).toBe("forbidden");
const allowed = await getReadiness(payload, { subjectType: "user", subjectId, windowStart: scope.windowStart }, {
id: adminId,
});
expect(allowed.status).toBe("ok");
const data = allowed.data as { explanation: string; rater?: unknown; evaluator?: unknown; comment?: unknown };
expect(data.explanation).not.toContain(raterDisplayName());
expect(data.rater).toBeUndefined();
expect(data.evaluator).toBeUndefined();
expect(data.comment).toBeUndefined();
});
});
function raterDisplayName(): string {
return `${RUN} rater`;
}