1
0
Fork 0

test(int): cover the Friday open-slot notice

Notice-day gating in Eastern time across DST, upcoming-Friday range,
stable dedup token, slot-open semantics (drafts hold the slot,
cancelled do not, other days do not count), and claimant resolution
(superuser exclusion, earliest-created wins, cancel reverts the
claim).
This commit is contained in:
Jason Fraley 2026-09-18 14:52:39 -04:00
parent 4fad7cd259
commit fd9f36fc46

View file

@ -0,0 +1,297 @@
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 {
getFridayClaimant,
hasMissionOnFriday,
isNoticeDue,
noticeToken,
upcomingFridayRange,
} from "@/lib/intelligence/fridayOpsNotice";
let payload: Payload;
const RUN = `fon-${Date.now().toString(36)}`;
// Hermetic window: 2027-01-13 is a Wednesday far enough out that the shared
// dev database holds no real missions on its Friday (2027-01-15).
const WEDNESDAY = new Date(2027, 0, 13, 12, 0, 0, 0);
const TUESDAY = new Date(2027, 0, 12, 12, 0, 0, 0);
const FRIDAY = new Date(2027, 0, 15, 12, 0, 0, 0);
const FRIDAY_EVENING = new Date(2027, 0, 15, 20, 0, 0, 0);
const SATURDAY = new Date(2027, 0, 16, 20, 0, 0, 0);
describe("Friday open-slot notice", () => {
let mapId: number;
let campaignId: number;
let authorId: number;
let zeusId: number;
let superuserId: number;
const missionIds: number[] = [];
const userIds: number[] = [];
const roleIds: number[] = [];
beforeAll(async () => {
const payloadConfig = await config;
payload = await getPayload({ config: payloadConfig });
// Sweep leftovers from crashed earlier runs: a stray Concept mission in
// the fixture window would flip the first hasMissionOnFriday assertion.
const stale = await payload.find({
collection: "missions",
overrideAccess: true,
depth: 0,
limit: 100,
where: { codeName: { like: "fon-" } },
});
for (const doc of stale.docs) {
await payload.delete({ collection: "missions", id: doc.id, overrideAccess: true }).catch(() => {});
}
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: "Friday notice 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;
userIds.push(authorId);
const zeus = (await payload.create({
collection: "users",
data: {
username: `${RUN}-zeus`,
discordUsername: `${RUN}-zeus`,
displayName: "ZEUS",
steamId: `7656119${Math.floor(Math.random() * 1e9)}`,
password: "Test123",
roles: ["user"],
},
overrideAccess: true,
depth: 0,
})) as unknown as User;
zeusId = zeus.id;
userIds.push(zeusId);
const superRole = (await payload.create({
collection: "roles",
data: { name: `FON Superuser ${RUN}`, slug: "", isSuperuser: true },
overrideAccess: true,
depth: 0,
})) as unknown as { id: number };
roleIds.push(superRole.id);
const superuser = (await payload.create({
collection: "users",
data: {
username: `${RUN}-su`,
discordUsername: `${RUN}-su`,
displayName: "SU",
steamId: `7656119${Math.floor(Math.random() * 1e9)}`,
password: "Test123",
roles: ["user"],
roleDocs: [superRole.id],
},
overrideAccess: true,
depth: 0,
})) as unknown as User;
superuserId = superuser.id;
userIds.push(superuserId);
});
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.
for (const uid of userIds) {
const accounts = await payload
.find({
collection: "bank-accounts",
where: { ownerUser: { equals: uid } },
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: uid } },
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: uid, overrideAccess: true }).catch(() => {});
}
for (const rid of roleIds) {
await payload.delete({ collection: "roles", id: rid, overrideAccess: true }).catch(() => {});
}
});
const makeMission = async (
label: string,
start: Date,
status: Mission["ownershipAndStatus"]["status"] = "Scheduled",
creatorId?: number,
): Promise<Mission> => {
const mission = (await payload.create({
collection: "missions",
data: {
name: `${RUN} ${label}`,
codeName: `${RUN}-${label}`,
summary: "Friday notice test mission",
operationType: "side",
classification: {
map: mapId,
missionType: "PvE",
campaign: campaignId,
startDateTime: start.toISOString(),
estimatedDuration: 60,
},
ownershipAndStatus: {
authors: [authorId],
status,
visibility: "unit",
},
missionRoles: {
maxPlayers: 16,
},
gameDetails: {
serverDetails: {
serverIp: "127.0.0.1",
serverPort: 2302,
},
},
briefing: [],
...(creatorId ? { createdBy: creatorId } : {}),
},
overrideAccess: true,
depth: 0,
})) as unknown as Mission;
missionIds.push(mission.id);
return mission;
};
it("is due on Wednesdays at 8 PM Eastern, until the ET day rolls over", () => {
// 2026-09-16 is a Wednesday in EDT (UTC-4); January is EST (UTC-5).
expect(isNoticeDue(new Date("2026-09-17T00:30:00Z"))).toBe(true); // Wed 20:30 ET
expect(isNoticeDue(new Date("2026-09-17T03:59:00Z"))).toBe(true); // Wed 23:59 ET
expect(isNoticeDue(new Date("2026-09-15T20:30:00Z"))).toBe(false); // Tue 16:30 ET
expect(isNoticeDue(new Date("2026-09-16T16:00:00Z"))).toBe(false); // Wed 12:00 ET
expect(isNoticeDue(new Date("2026-09-16T23:59:00Z"))).toBe(false); // Wed 19:59 ET
expect(isNoticeDue(new Date("2026-09-17T00:00:00Z"))).toBe(true); // Wed 20:00 ET sharp
expect(isNoticeDue(new Date("2026-09-17T04:00:00Z"))).toBe(false); // Thu 00:00 ET
expect(isNoticeDue(new Date("2026-01-15T01:00:00Z"))).toBe(true); // Wed 20:00 ET (EST)
});
it("targets the upcoming Friday, strictly in the future", () => {
const fromWednesday = upcomingFridayRange(WEDNESDAY);
expect(fromWednesday.start).toEqual(new Date(2027, 0, 15, 0, 0, 0, 0));
expect(fromWednesday.end).toEqual(new Date(2027, 0, 15, 23, 59, 59, 999));
expect(fromWednesday.epoch).toBe(Math.floor(fromWednesday.start.getTime() / 1000));
// Earlier in the week still lands on the same Friday.
expect(upcomingFridayRange(new Date(2027, 0, 11, 8, 0, 0, 0)).start).toEqual(
fromWednesday.start,
);
// On a Friday itself, the next Friday is targeted instead.
expect(upcomingFridayRange(FRIDAY).start).toEqual(new Date(2027, 0, 22, 0, 0, 0, 0));
});
it("builds a stable per-Friday dedup token", () => {
expect(noticeToken(WEDNESDAY)).toBe(noticeToken(TUESDAY));
expect(noticeToken(WEDNESDAY)).toBe(`<t:${upcomingFridayRange(WEDNESDAY).epoch}:D>`);
expect(noticeToken(FRIDAY)).not.toBe(noticeToken(WEDNESDAY));
});
it("treats the Friday slot as open only when a non-cancelled mission starts that Friday", async () => {
await expect(hasMissionOnFriday(payload, WEDNESDAY)).resolves.toBe(false);
const concept = await makeMission("concept-friday", FRIDAY_EVENING, "Concept");
await expect(hasMissionOnFriday(payload, WEDNESDAY)).resolves.toBe(true);
await makeMission("cancelled-friday", FRIDAY_EVENING, "Cancelled");
await expect(hasMissionOnFriday(payload, WEDNESDAY)).resolves.toBe(true);
await payload.delete({ collection: "missions", id: concept.id, overrideAccess: true });
await expect(hasMissionOnFriday(payload, WEDNESDAY)).resolves.toBe(false);
const saturday = await makeMission("saturday-mission", SATURDAY);
await expect(hasMissionOnFriday(payload, WEDNESDAY)).resolves.toBe(false);
await payload.delete({ collection: "missions", id: saturday.id, overrideAccess: true });
});
it("credits the earliest non-superuser creator as the claimant", async () => {
await expect(getFridayClaimant(payload, WEDNESDAY)).resolves.toBeNull();
const zeusMission = await makeMission("zeus-claim", FRIDAY_EVENING, "Scheduled", zeusId);
await expect(getFridayClaimant(payload, WEDNESDAY)).resolves.toEqual({ userName: "ZEUS" });
await makeMission("su-claim", FRIDAY_EVENING, "Scheduled", superuserId);
await expect(getFridayClaimant(payload, WEDNESDAY)).resolves.toEqual({ userName: "ZEUS" });
await payload.delete({ collection: "missions", id: zeusMission.id, overrideAccess: true });
await expect(getFridayClaimant(payload, WEDNESDAY)).resolves.toBeNull();
const first = await makeMission("zeus-first", FRIDAY_EVENING, "Scheduled", zeusId);
const second = await makeMission("zeus-second", FRIDAY_EVENING, "Scheduled", zeusId);
await expect(getFridayClaimant(payload, WEDNESDAY)).resolves.toEqual({ userName: "ZEUS" });
expect(first.createdAt <= second.createdAt).toBe(true);
await payload.delete({ collection: "missions", id: first.id, overrideAccess: true });
await payload.delete({ collection: "missions", id: second.id, overrideAccess: true });
const claimer = await makeMission("zeus-cancelled", FRIDAY_EVENING, "Scheduled", zeusId);
await expect(getFridayClaimant(payload, WEDNESDAY)).resolves.toEqual({ userName: "ZEUS" });
await payload.update({
collection: "missions",
id: claimer.id,
data: { ownershipAndStatus: { status: "Cancelled" } },
overrideAccess: true,
});
await expect(getFridayClaimant(payload, WEDNESDAY)).resolves.toBeNull();
});
});