From 8f7573f742f65118a44d59720b644414a7117953 Mon Sep 17 00:00:00 2001 From: Z8MB1E Date: Fri, 18 Sep 2026 13:09:35 -0400 Subject: [PATCH] test(int): cover imminent reminder query boundaries Window edges, post-start grace zone, and exclusions for marked missions, missing roll-call, non-unit visibility, and draft status. --- tests/int/imminent-reminders.int.spec.ts | 214 +++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 tests/int/imminent-reminders.int.spec.ts diff --git a/tests/int/imminent-reminders.int.spec.ts b/tests/int/imminent-reminders.int.spec.ts new file mode 100644 index 0000000..f29f827 --- /dev/null +++ b/tests/int/imminent-reminders.int.spec.ts @@ -0,0 +1,214 @@ +import { getPayload, Payload } from "payload"; +import config from "@/payload.config"; + +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import type { Campaign, Map as MissionMap, Mission, User } from "@/payload-types"; +import { + IMMINENT_REMINDER_GRACE_MS, + IMMINENT_REMINDER_WINDOW_MS, + getImminentReminderMissions, +} from "@/lib/intelligence/imminentReminders"; + +let payload: Payload; + +const RUN = `imr-${Date.now().toString(36)}`; + +describe("Imminent-op reminder query", () => { + let mapId: number; + let campaignId: number; + let authorId: number; + const missionIds: number[] = []; + + beforeAll(async () => { + const payloadConfig = await config; + payload = await getPayload({ config: payloadConfig }); + + const map = (await payload.create({ + collection: "maps", + data: { name: `${RUN} Map`, worldSizeWidth: 8192, worldSizeHeight: 8192, basemapMode: "image" }, + overrideAccess: true, + depth: 0, + })) as unknown as MissionMap; + mapId = map.id; + + const campaign = (await payload.create({ + collection: "campaigns", + data: { name: `${RUN} Campaign`, summary: "Imminent reminder test campaign", status: "concept", campaignMode: "custom" }, + overrideAccess: true, + depth: 0, + })) as unknown as Campaign; + campaignId = campaign.id; + + const author = (await payload.create({ + collection: "users", + data: { + username: `${RUN}-author`, + discordUsername: `${RUN}-author`, + displayName: "AUTHOR", + steamId: `7656119${Math.floor(Math.random() * 1e9)}`, + password: "Test123", + roles: ["user"], + }, + overrideAccess: true, + depth: 0, + })) as unknown as User; + authorId = author.id; + }); + + afterAll(async () => { + for (const id of missionIds) { + await payload.delete({ collection: "missions", id, overrideAccess: true }).catch(() => {}); + } + await payload + .delete({ collection: "campaigns", id: campaignId, overrideAccess: true }) + .catch(() => {}); + await payload.delete({ collection: "maps", id: mapId, overrideAccess: true }).catch(() => {}); + // User deletion trips FK constraints unless the hook-provisioned + // personal bank account and profile are removed first. + const accounts = await payload + .find({ + collection: "bank-accounts", + where: { ownerUser: { equals: authorId } }, + limit: 5, + depth: 0, + overrideAccess: true, + }) + .catch(() => null); + for (const account of accounts?.docs ?? []) { + await payload + .delete({ collection: "bank-accounts", id: account.id, overrideAccess: true }) + .catch(() => {}); + } + const profiles = await payload + .find({ + collection: "profiles", + where: { user: { equals: authorId } }, + limit: 5, + depth: 0, + overrideAccess: true, + }) + .catch(() => null); + for (const profile of profiles?.docs ?? []) { + await payload + .delete({ collection: "profiles", id: profile.id, overrideAccess: true }) + .catch(() => {}); + } + await payload + .delete({ collection: "users", id: authorId, overrideAccess: true }) + .catch(() => {}); + }); + + const makeMission = async ( + label: string, + overrides: { + status?: Mission["ownershipAndStatus"]["status"]; + visibility?: Mission["ownershipAndStatus"]["visibility"]; + start: Date; + withRollCall?: boolean; + marked?: boolean; + }, + ): Promise => { + const mission = (await payload.create({ + collection: "missions", + data: { + name: `${RUN} ${label}`, + codeName: `${RUN}-${label}`, + summary: "Imminent reminder boundary test mission", + operationType: "main", + classification: { + map: mapId, + missionType: "PvE", + campaign: campaignId, + startDateTime: overrides.start.toISOString(), + estimatedDuration: 60, + }, + ownershipAndStatus: { + authors: [authorId], + status: overrides.status ?? "Scheduled", + visibility: overrides.visibility ?? "unit", + }, + missionRoles: { + maxPlayers: 16, + }, + gameDetails: { + serverDetails: { + serverIp: "127.0.0.1", + serverPort: 2302, + }, + }, + briefing: [], + ...(overrides.withRollCall === false + ? {} + : { discordMessageId: `${RUN}-${label}-message` }), + ...(overrides.marked ? { imminentReminderSentAt: new Date().toISOString() } : {}), + }, + overrideAccess: true, + depth: 0, + })) as unknown as Mission; + missionIds.push(mission.id); + return mission; + }; + + it("finds posted Ready/Scheduled ops starting inside the 15 minute window", async () => { + const now = new Date(); + const start = new Date(now.getTime() + IMMINENT_REMINDER_WINDOW_MS - 60_000); + const ready = await makeMission("in-window-ready", { status: "Ready", start }); + const scheduled = await makeMission("in-window-scheduled", { status: "Scheduled", start }); + + const found = await getImminentReminderMissions(payload, now); + expect(found.map((m) => m.id)).toContain(ready.id); + expect(found.map((m) => m.id)).toContain(scheduled.id); + }); + + it("includes the exact window edge and the grace zone just past the start", async () => { + const now = new Date(); + const edge = await makeMission("window-edge", { + start: new Date(now.getTime() + IMMINENT_REMINDER_WINDOW_MS), + }); + const justStarted = await makeMission("grace-zone", { + start: new Date(now.getTime() - 5 * 60_000), + }); + + const found = await getImminentReminderMissions(payload, now); + expect(found.map((m) => m.id)).toContain(edge.id); + expect(found.map((m) => m.id)).toContain(justStarted.id); + }); + + it("excludes ops outside the window, past the grace bound, marked, without a roll-call, or not unit-visible", async () => { + const now = new Date(); + const tooEarly = await makeMission("too-early", { + start: new Date(now.getTime() + IMMINENT_REMINDER_WINDOW_MS + 60_000), + }); + const tooLate = await makeMission("too-late", { + start: new Date(now.getTime() - IMMINENT_REMINDER_GRACE_MS - 60_000), + }); + const alreadyReminded = await makeMission("already-reminded", { + start: new Date(now.getTime() + 10 * 60_000), + marked: true, + }); + const noRollCall = await makeMission("no-roll-call", { + start: new Date(now.getTime() + 10 * 60_000), + withRollCall: false, + }); + const notUnitVisible = await makeMission("not-unit-visible", { + start: new Date(now.getTime() + 10 * 60_000), + visibility: "leadership", + }); + const draftStatus = await makeMission("draft-status", { + status: "Planning", + start: new Date(now.getTime() + 10 * 60_000), + }); + + const foundIds = (await getImminentReminderMissions(payload, now)).map((m) => m.id); + for (const excluded of [ + tooEarly, + tooLate, + alreadyReminded, + noRollCall, + notUnitVisible, + draftStatus, + ]) { + expect(foundIds).not.toContain(excluded.id); + } + }); +});