Reusable dossier primitives (classification strips, brackets, readouts, provenance chips, notice states) plus server-rendered /operations overview, ledger drill-down, and AAR with permission-gated sections and an authorized reconciliation panel. Realtime refresh is enhancement only. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
305 lines
10 KiB
TypeScript
305 lines
10 KiB
TypeScript
import { getPayload, Payload } from "payload";
|
|
import config from "@/payload.config";
|
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
import { getOperationOverview, getSurfaceCapabilities } from "@/lib/operations/surface";
|
|
import { getOperationAar, getOperationLedger } from "@/lib/operations/surfaceQuery";
|
|
|
|
let payload: Payload;
|
|
const RUN = `ops-surface-${Date.now().toString(36)}`;
|
|
|
|
const syncEventIds: number[] = [];
|
|
const opEventIds: number[] = [];
|
|
const opEffectIds: number[] = [];
|
|
const userIds: number[] = [];
|
|
const roleIds: number[] = [];
|
|
const zoneIds: number[] = [];
|
|
const mapIds: number[] = [];
|
|
const factionIds: number[] = [];
|
|
let serverId: number;
|
|
let plainUserId: number;
|
|
let permUserId: number;
|
|
let adminUserId: number;
|
|
let mapId: number;
|
|
let zoneId: number;
|
|
let factionId: number;
|
|
|
|
const OP_A = `${RUN}-op-alpha`;
|
|
const OP_B = `${RUN}-op-beta`;
|
|
|
|
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 makeRawEvent(label: string, type: string, payloadData: unknown): Promise<number> {
|
|
const raw = await payload.create({
|
|
collection: "arma-sync-events",
|
|
data: {
|
|
eventId: `${RUN}-${label}`,
|
|
server: serverId,
|
|
type,
|
|
occurredAt: new Date().toISOString(),
|
|
payload: payloadData as Record<string, unknown>,
|
|
},
|
|
overrideAccess: true,
|
|
depth: 0,
|
|
});
|
|
syncEventIds.push(raw.id);
|
|
const found = await payload.find({
|
|
collection: "operation-events",
|
|
where: { sourceEvent: { equals: raw.id } },
|
|
limit: 1,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
});
|
|
expect(found.docs).toHaveLength(1);
|
|
opEventIds.push(found.docs[0].id);
|
|
const effects = await payload.find({
|
|
collection: "operation-effects",
|
|
where: { operationEvent: { equals: found.docs[0].id } },
|
|
limit: 10,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
});
|
|
for (const effect of effects.docs) opEffectIds.push(effect.id);
|
|
return raw.id;
|
|
}
|
|
|
|
beforeAll(async () => {
|
|
const payloadConfig = await config;
|
|
payload = await getPayload({ config: payloadConfig });
|
|
|
|
const superRole = await payload.create({
|
|
collection: "roles",
|
|
data: { name: `${RUN} Surface Admin`, slug: `${RUN}-surface-admin`, isSuperuser: true },
|
|
overrideAccess: true,
|
|
depth: 0,
|
|
});
|
|
roleIds.push(superRole.id);
|
|
|
|
const permRole = await payload.create({
|
|
collection: "roles",
|
|
data: {
|
|
name: `${RUN} Surface Viewer`,
|
|
slug: `${RUN}-surface-viewer`,
|
|
permissions: [
|
|
"operation-reservations:read",
|
|
"zone-pressure:read",
|
|
"operation-readiness:read",
|
|
],
|
|
},
|
|
overrideAccess: true,
|
|
depth: 0,
|
|
});
|
|
roleIds.push(permRole.id);
|
|
|
|
const faction = await payload.create({
|
|
collection: "factions",
|
|
data: { name: `${RUN} Faction`, type: "majorFaction" },
|
|
overrideAccess: true,
|
|
depth: 0,
|
|
});
|
|
factionIds.push(faction.id);
|
|
factionId = faction.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;
|
|
|
|
const map = await payload.create({
|
|
collection: "maps",
|
|
data: { name: `${RUN} Map`, worldSizeWidth: 8192, worldSizeHeight: 8192, basemapMode: "image" },
|
|
overrideAccess: true,
|
|
depth: 0,
|
|
});
|
|
mapIds.push(map.id);
|
|
mapId = map.id;
|
|
|
|
const zone = await payload.create({
|
|
collection: "map-zones",
|
|
data: {
|
|
name: `${RUN} Zone Alpha`,
|
|
map: mapId,
|
|
type: "territory",
|
|
points: [
|
|
[0, 0],
|
|
[100, 0],
|
|
[100, 100],
|
|
[0, 100],
|
|
],
|
|
},
|
|
overrideAccess: true,
|
|
depth: 0,
|
|
});
|
|
zoneIds.push(zone.id);
|
|
zoneId = zone.id;
|
|
|
|
plainUserId = await makeUser("plain");
|
|
permUserId = await makeUser("perm", [permRole.id]);
|
|
adminUserId = await makeUser("admin", [superRole.id]);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
if (!payload) return;
|
|
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 zoneIds) {
|
|
await payload.delete({ collection: "map-zones", id, overrideAccess: true }).catch(() => {});
|
|
}
|
|
for (const id of mapIds) {
|
|
await payload.delete({ collection: "maps", id, overrideAccess: true }).catch(() => {});
|
|
}
|
|
for (const id of factionIds) {
|
|
await payload.delete({ collection: "factions", 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(() => {});
|
|
}
|
|
});
|
|
|
|
describe("operation surfaces: capabilities", () => {
|
|
it("grants a superuser every gated capability", async () => {
|
|
const adminUser = await payload.findByID({
|
|
collection: "users",
|
|
id: adminUserId,
|
|
overrideAccess: true,
|
|
depth: 0,
|
|
});
|
|
const caps = await getSurfaceCapabilities(payload, { id: adminUserId });
|
|
expect(adminUser.id).toBe(adminUserId);
|
|
expect(caps.canSeeReservations).toBe(true);
|
|
expect(caps.canSeeReadiness).toBe(true);
|
|
expect(caps.canSeePressure).toBe(true);
|
|
});
|
|
|
|
it("denies a plain user every gated capability", async () => {
|
|
const caps = await getSurfaceCapabilities(payload, { id: plainUserId });
|
|
expect(caps.canSeeReservations).toBe(false);
|
|
expect(caps.canSeeReadiness).toBe(false);
|
|
expect(caps.canSeePressure).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("operation surfaces: overview", () => {
|
|
it("groups recent events by operation with effect status counts", async () => {
|
|
await makeRawEvent("start-a1", "operation.start", { operationId: OP_A, version: 1 });
|
|
await makeRawEvent("start-a2", "operation.start", { operationId: OP_A, version: 1 });
|
|
await makeRawEvent("start-b1", "operation.start", { operationId: OP_B, version: 1 });
|
|
|
|
const overview = await getOperationOverview(payload, { id: adminUserId });
|
|
const opA = overview.recentOperations.find((op) => op.operationId === OP_A);
|
|
expect(opA).toBeDefined();
|
|
expect(opA?.eventCount).toBe(2);
|
|
expect(opA?.appliedCount).toBe(2);
|
|
expect(opA?.failedCount).toBe(0);
|
|
expect(opA?.lastOccurredAt).toBeTruthy();
|
|
});
|
|
|
|
it("scopes my extractions to the requesting actor only", async () => {
|
|
await makeRawEvent("extract-b1", "operation.extraction", {
|
|
operationId: OP_B,
|
|
version: 1,
|
|
actorId: plainUserId,
|
|
kind: "player",
|
|
items: [{ assetId: 999_999_999, quantity: 1 }],
|
|
});
|
|
|
|
const mine = await getOperationOverview(payload, { id: plainUserId });
|
|
const myRows = mine.myExtractions.filter((ex) => ex.operationId === OP_B);
|
|
expect(myRows).toHaveLength(1);
|
|
expect(myRows[0].effectStatus).toBeTruthy();
|
|
|
|
const others = await getOperationOverview(payload, { id: permUserId });
|
|
expect(others.myExtractions.some((ex) => ex.operationId === OP_B)).toBe(false);
|
|
});
|
|
|
|
it("hides gated sections from plain users but keeps the ledger visible", async () => {
|
|
const overview = await getOperationOverview(payload, { id: plainUserId });
|
|
expect(overview.capabilities.canSeePressure).toBe(false);
|
|
expect(overview.pressure).toHaveLength(0);
|
|
expect(overview.recentOperations.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe("operation surfaces: ledger and AAR", () => {
|
|
it("filters the ledger by operation and reports effect counts without leaking payload", async () => {
|
|
const result = await getOperationLedger(payload, { id: adminUserId }, { operationId: OP_A });
|
|
expect(result.events).toHaveLength(2);
|
|
expect(result.events.every((ev) => ev.operationId === OP_A)).toBe(true);
|
|
expect(result.effectCounts.applied).toBe(2);
|
|
for (const ev of result.events) {
|
|
expect("payload" in ev ? ev.payload : undefined).toBeUndefined();
|
|
}
|
|
});
|
|
|
|
it("paginates the ledger", async () => {
|
|
const page1 = await getOperationLedger(payload, { id: adminUserId }, {}, 1);
|
|
expect(page1.page).toBe(1);
|
|
expect(page1.totalDocs).toBeGreaterThanOrEqual(3);
|
|
});
|
|
|
|
it("returns null for an unknown operation id", async () => {
|
|
const aar = await getOperationAar(payload, { id: adminUserId }, `${RUN}-does-not-exist`);
|
|
expect(aar).toBeNull();
|
|
});
|
|
|
|
it("builds an AAR with counts and never includes raw effect payloads", async () => {
|
|
const aar = await getOperationAar(payload, { id: adminUserId }, OP_B);
|
|
expect(aar).not.toBeNull();
|
|
expect(aar?.events.length).toBe(2);
|
|
expect(aar?.counts.validated).toBe(2);
|
|
expect(aar?.counts.applied).toBe(1);
|
|
const failed = aar?.effects.filter((eff) => eff.status === "failed") ?? [];
|
|
expect(failed.length).toBe(1);
|
|
expect(failed[0]?.effectType).toBe("operation.extraction-recorded");
|
|
for (const eff of aar?.effects ?? []) {
|
|
expect("payload" in eff ? eff.payload : undefined).toBeUndefined();
|
|
}
|
|
});
|
|
|
|
it("gates AAR pressure and reservation sections by permission", async () => {
|
|
await makeRawEvent("end-a1", "operation.end", {
|
|
operationId: OP_A,
|
|
version: 1,
|
|
outcome: "success",
|
|
zoneId,
|
|
factionId,
|
|
});
|
|
|
|
const forAdmin = await getOperationAar(payload, { id: adminUserId }, OP_A);
|
|
expect(forAdmin?.pressure).toHaveLength(1);
|
|
expect(forAdmin?.pressure[0]?.zoneId).toBe(zoneId);
|
|
expect(forAdmin?.pressure[0]?.delta).toBe(-25);
|
|
|
|
const forPlain = await getOperationAar(payload, { id: plainUserId }, OP_A);
|
|
expect(forPlain?.pressure).toHaveLength(0);
|
|
expect(forPlain?.reservations).toHaveLength(0);
|
|
});
|
|
});
|