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 { 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 { const raw = await payload.create({ collection: "arma-sync-events", data: { eventId: `${RUN}-${label}`, server: serverId, type, occurredAt: new Date().toISOString(), payload: payloadData as Record, }, 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); }); });