1
0
Fork 0
polaris-task-force/tests/int/server-presence.int.spec.ts

118 lines
4.2 KiB
TypeScript

import { getPayload, Payload } from "payload";
import config from "@/payload.config";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import type { GameServer } from "@/payload-types";
import { markStaleServersOffline, SERVER_OFFLINE_AFTER_MS } from "@/lib/arma-bridge/presence";
let payload: Payload;
const RUN = `srv-${Date.now().toString(36)}`;
describe("Server presence sweep", () => {
let now: Date;
const serverIds: number[] = [];
beforeAll(async () => {
const payloadConfig = await config;
payload = await getPayload({ config: payloadConfig });
now = new Date();
now.setMilliseconds(0);
});
afterAll(async () => {
for (const id of serverIds) {
await payload.delete({ collection: "game-servers", id, overrideAccess: true }).catch(() => {});
}
const events = await payload
.find({
collection: "game-event-logs",
where: { and: [{ type: { equals: "server:offline" } }, { targetId: { in: serverIds } }] },
limit: 50,
depth: 0,
overrideAccess: true,
})
.catch(() => null);
for (const event of events?.docs ?? []) {
await payload
.delete({ collection: "game-event-logs", id: event.id, overrideAccess: true })
.catch(() => {});
}
});
const makeServer = async (
label: string,
data: { status?: GameServer["status"]; lastSeenAt?: Date },
): Promise<GameServer> => {
const server = (await payload.create({
collection: "game-servers",
data: {
serverId: `${RUN}-${label}`,
name: `${RUN} ${label}`,
status: data.status ?? "online",
lastSeenAt: data.lastSeenAt ? data.lastSeenAt.toISOString() : undefined,
},
overrideAccess: true,
depth: 0,
})) as unknown as GameServer;
serverIds.push(server.id);
return server;
};
const fetchServer = (id: number): Promise<GameServer> =>
payload.findByID({
collection: "game-servers",
id,
depth: 0,
overrideAccess: true,
}) as unknown as Promise<GameServer>;
it("flips online servers with no recent heartbeat to offline and leaves the rest alone", async () => {
const fresh = await makeServer("fresh", { lastSeenAt: new Date(now.getTime() - 60_000) });
const stale = await makeServer("stale", {
lastSeenAt: new Date(now.getTime() - (SERVER_OFFLINE_AFTER_MS + 1_000)),
});
const neverSeen = await makeServer("never-seen", {});
const offlineStale = await makeServer("offline-stale", {
status: "offline",
lastSeenAt: new Date(now.getTime() - 3_600_000),
});
const boundary = await makeServer("boundary", {
lastSeenAt: new Date(now.getTime() - SERVER_OFFLINE_AFTER_MS),
});
// Scope to this run's fixtures so the sweep never flips other servers in the
// shared dev database (stale real servers would be flipped legitimately).
const result = await markStaleServersOffline(payload, now, { serverIds });
expect(result.markedOffline).toBe(2);
expect(result.servers.map((s) => s.id).sort()).toEqual([stale.id, neverSeen.id].sort());
expect(result.servers.find((s) => s.id === stale.id)?.serverId).toBe(`${RUN}-stale`);
expect((await fetchServer(fresh.id)).status).toBe("online");
expect((await fetchServer(stale.id)).status).toBe("offline");
expect((await fetchServer(neverSeen.id)).status).toBe("offline");
expect((await fetchServer(offlineStale.id)).status).toBe("offline");
// Exactly at the cutoff the server is still within its heartbeat window.
expect((await fetchServer(boundary.id)).status).toBe("online");
});
it("logs a server:offline event per flipped server", async () => {
const events = await payload.find({
collection: "game-event-logs",
where: {
and: [{ type: { equals: "server:offline" } }, { targetId: { in: serverIds } }],
},
limit: 10,
depth: 0,
overrideAccess: true,
});
expect(events.docs).toHaveLength(2);
});
it("is idempotent — a second sweep finds nothing left to flip", async () => {
const result = await markStaleServersOffline(payload, now, { serverIds });
expect(result.markedOffline).toBe(0);
expect(result.servers).toEqual([]);
});
});