226 lines
No EOL
7.5 KiB
TypeScript
226 lines
No EOL
7.5 KiB
TypeScript
import { getPayload, Payload } from "payload";
|
|
import config from "@/payload.config";
|
|
import { afterAll, beforeAll, describe, expect, it, vi, type MockInstance } from "vitest";
|
|
import type { ArmaCommand, SupplyBox, User } from "@/payload-types";
|
|
import { queueCrateCommand } from "@/app/(frontend)/logistics/supply-boxes/actions";
|
|
|
|
// Mock next/headers so the action's authenticate() can run outside a request.
|
|
vi.mock("next/headers", () => ({
|
|
headers: async () => new Headers(),
|
|
}));
|
|
|
|
let payload: Payload;
|
|
let authSpy: MockInstance;
|
|
|
|
const RUN = `sbx-${Date.now().toString(36)}`;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Fixture tracking for cleanup
|
|
// ---------------------------------------------------------------------------
|
|
const userIds: number[] = [];
|
|
const roleIds: number[] = [];
|
|
const syncEventIds: number[] = [];
|
|
const boxIds: number[] = [];
|
|
const commandIds: number[] = [];
|
|
let serverId: number;
|
|
|
|
let commandUser: User;
|
|
let plainUser: User;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
async function makeUser(label: string, roleDocIds?: number[]): Promise<User> {
|
|
const user = (await payload.create({
|
|
collection: "users",
|
|
data: {
|
|
username: `${RUN}-${label}`,
|
|
discordUsername: `${RUN}-${label}-discord`,
|
|
displayName: `${RUN} ${label}`,
|
|
steamId: `${RUN}-steam-${label}`,
|
|
password: "Test1234",
|
|
roles: ["user"],
|
|
...(roleDocIds && roleDocIds.length > 0 ? { roleDocs: roleDocIds } : {}),
|
|
},
|
|
overrideAccess: true,
|
|
depth: 0,
|
|
})) as unknown as User;
|
|
userIds.push(user.id);
|
|
return user;
|
|
}
|
|
|
|
// Creates a supply box directly (the crate-sync handler is covered by
|
|
// supply-box-sync.int.spec.ts) with a real arma-sync-events row as the
|
|
// required sourceEvent provenance anchor.
|
|
async function makeBox(boxId: string): Promise<SupplyBox> {
|
|
const raw = await payload.create({
|
|
collection: "arma-sync-events",
|
|
data: {
|
|
eventId: `${RUN}-evt-${boxId}`,
|
|
server: serverId,
|
|
type: "fixture",
|
|
occurredAt: new Date().toISOString(),
|
|
payload: [],
|
|
},
|
|
overrideAccess: true,
|
|
depth: 0,
|
|
});
|
|
syncEventIds.push(raw.id);
|
|
const box = (await payload.create({
|
|
collection: "supply-boxes",
|
|
data: {
|
|
boxId,
|
|
sessionId: RUN,
|
|
server: serverId,
|
|
boxClass: "Box_NATO_Wps_F",
|
|
displayName: "Ammo Crate",
|
|
location: { x: 100, y: 200, z: 0 },
|
|
contents: [{ class: "Item_1", count: 10 }],
|
|
lastSyncedAt: new Date().toISOString(),
|
|
status: "active",
|
|
sourceEvent: raw.id,
|
|
},
|
|
overrideAccess: true,
|
|
depth: 0,
|
|
})) as unknown as SupplyBox;
|
|
boxIds.push(box.id);
|
|
return box;
|
|
}
|
|
|
|
async function findCommand(commandId: string): Promise<ArmaCommand | null> {
|
|
const found = await payload.find({
|
|
collection: "arma-commands",
|
|
where: { commandId: { equals: commandId } },
|
|
limit: 1,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
});
|
|
return (found.docs[0] as ArmaCommand | undefined) ?? null;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Setup / teardown
|
|
// ---------------------------------------------------------------------------
|
|
beforeAll(async () => {
|
|
const payloadConfig = await config;
|
|
payload = await getPayload({ config: payloadConfig });
|
|
authSpy = vi.spyOn(payload, "auth");
|
|
|
|
const superRole = await payload.create({
|
|
collection: "roles",
|
|
data: { name: `${RUN} Command`, slug: `${RUN}-command`, isSuperuser: true },
|
|
overrideAccess: true,
|
|
depth: 0,
|
|
});
|
|
roleIds.push(superRole.id);
|
|
|
|
commandUser = await makeUser("command", [superRole.id]);
|
|
plainUser = await makeUser("plain");
|
|
|
|
const server = await payload.create({
|
|
collection: "game-servers",
|
|
data: { serverId: `${RUN}-srv`, name: `${RUN} Server`, status: "online" },
|
|
overrideAccess: true,
|
|
depth: 0,
|
|
});
|
|
serverId = server.id;
|
|
});
|
|
|
|
afterAll(async () => {
|
|
if (!payload) return;
|
|
for (const id of commandIds) {
|
|
await payload.delete({ collection: "arma-commands", id, overrideAccess: true }).catch(() => {});
|
|
}
|
|
for (const id of boxIds) {
|
|
await payload.delete({ collection: "supply-boxes", id, overrideAccess: true }).catch(() => {});
|
|
}
|
|
for (const id of syncEventIds) {
|
|
await payload.delete({ collection: "arma-sync-events", 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(() => {});
|
|
}
|
|
if (serverId) {
|
|
await payload.delete({ collection: "game-servers", id: serverId, overrideAccess: true }).catch(() => {});
|
|
}
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests
|
|
// ---------------------------------------------------------------------------
|
|
describe("supply box crate command actions", () => {
|
|
describe("queueCrateCommand", () => {
|
|
it("queues a crate.apply command with the contract payload for a qualified user", async () => {
|
|
authSpy.mockResolvedValue({ user: commandUser });
|
|
const box = await makeBox(`box:${RUN}:apply`);
|
|
|
|
const result = await queueCrateCommand(box.boxId, "apply", [
|
|
[
|
|
["Item_1", 10],
|
|
["Item_2", 5],
|
|
],
|
|
]);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data?.status).toBe("queued");
|
|
expect(result.data?.commandId).toMatch(/^crate-[0-9a-f]{24}$/);
|
|
|
|
const cmd = await findCommand(result.data!.commandId);
|
|
expect(cmd).not.toBeNull();
|
|
expect(cmd?.type).toBe("crate.apply");
|
|
expect(cmd?.server).toBe(serverId);
|
|
expect(cmd?.status).toBe("queued");
|
|
expect(cmd?.payload).toEqual([
|
|
box.boxId,
|
|
[
|
|
["Item_1", 10],
|
|
["Item_2", 5],
|
|
],
|
|
]);
|
|
commandIds.push(cmd!.id);
|
|
});
|
|
|
|
it("rejects a user without the logistics qualification with a forbidden error", async () => {
|
|
authSpy.mockResolvedValue({ user: plainUser });
|
|
const box = await makeBox(`box:${RUN}:forbidden`);
|
|
|
|
const result = await queueCrateCommand(box.boxId, "despawn", []);
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.error).toContain("forbidden");
|
|
});
|
|
|
|
it("maps an unknown box id to a not-found error", async () => {
|
|
authSpy.mockResolvedValue({ user: commandUser });
|
|
|
|
const result = await queueCrateCommand(`box:${RUN}:missing`, "despawn", []);
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.error).toContain("not found");
|
|
});
|
|
|
|
it("mints a unique command id per queue", async () => {
|
|
authSpy.mockResolvedValue({ user: commandUser });
|
|
const box = await makeBox(`box:${RUN}:unique`);
|
|
|
|
const first = await queueCrateCommand(box.boxId, "despawn", []);
|
|
const second = await queueCrateCommand(box.boxId, "despawn", []);
|
|
|
|
expect(first.success).toBe(true);
|
|
expect(second.success).toBe(true);
|
|
expect(first.data?.commandId).not.toBe(second.data?.commandId);
|
|
|
|
const firstCmd = await findCommand(first.data!.commandId);
|
|
const secondCmd = await findCommand(second.data!.commandId);
|
|
expect(firstCmd).not.toBeNull();
|
|
expect(secondCmd).not.toBeNull();
|
|
expect(firstCmd?.id).not.toBe(secondCmd?.id);
|
|
expect(firstCmd?.payload).toEqual([box.boxId]);
|
|
expect(secondCmd?.payload).toEqual([box.boxId]);
|
|
commandIds.push(firstCmd!.id, secondCmd!.id);
|
|
});
|
|
});
|
|
}); |