81 lines
3.3 KiB
TypeScript
81 lines
3.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
parseSafeGameClientBridgeCommand,
|
|
parseSafeGameClientBridgeSnapshotList,
|
|
parseSafeGameClientBridgeStatus
|
|
} from "./gameClientBridge";
|
|
|
|
const now = "2026-07-20T08:00:00Z";
|
|
|
|
const safeCommand = {
|
|
id: "command-1",
|
|
serverInstanceId: "server-1",
|
|
pluginId: "game.scum",
|
|
profileKey: "scum-client",
|
|
commandType: "scum.player.lookup",
|
|
priority: 10,
|
|
state: "succeeded",
|
|
approvalState: "not_required",
|
|
requesterId: "user-1",
|
|
result: { status: "succeeded", summary: "player found", payload: { found: true }, completedAt: now },
|
|
expiresAt: now,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
completedAt: now
|
|
};
|
|
|
|
const safeSnapshotList = {
|
|
items: [{
|
|
id: "snapshot-1",
|
|
serverInstanceId: "server-1",
|
|
pluginId: "game.scum",
|
|
profileKey: "scum-client",
|
|
type: "scum.players",
|
|
schemaVersion: "1",
|
|
streamKey: "current",
|
|
sequence: 1,
|
|
observedAt: now,
|
|
payload: { players: [{ playerId: "player-1" }] },
|
|
retention: { keepForSeconds: 3600, maxRecords: 24 },
|
|
createdAt: now,
|
|
expiresAt: now
|
|
}],
|
|
count: 1
|
|
};
|
|
|
|
describe("Game Client Bridge safe projection schema", () => {
|
|
it("preserves declarations, approval, result, retention and typed snapshot payloads", () => {
|
|
expect(parseSafeGameClientBridgeStatus({
|
|
serverInstanceId: "server-1",
|
|
pluginId: "game.scum",
|
|
available: true,
|
|
profiles: [{ pluginId: "game.scum", profileKey: "scum-client", available: true, commandTypes: ["scum.player.lookup"], snapshotTypes: ["scum.players"], queryTemplateKeys: ["scum.player.search"] }]
|
|
})).toMatchObject({ available: true, profiles: [{ queryTemplateKeys: ["scum.player.search"] }] });
|
|
expect(parseSafeGameClientBridgeCommand(safeCommand)).toMatchObject({ approvalState: "not_required", result: { payload: { found: true } } });
|
|
expect(parseSafeGameClientBridgeSnapshotList(safeSnapshotList)).toMatchObject({ count: 1, items: [{ retention: { maxRecords: 24 } }] });
|
|
expect(parseSafeGameClientBridgeSnapshotList({
|
|
...safeSnapshotList,
|
|
items: [{ ...safeSnapshotList.items[0], payload: { sessions: [{ sessionId: "game-session-1", playerId: "player-1" }] } }]
|
|
})).toMatchObject({ items: [{ payload: { sessions: [{ sessionId: "game-session-1" }] } }] });
|
|
});
|
|
|
|
it.each([
|
|
{ sessionToken: "component-session-material" },
|
|
{ componentSession: "component-session-material" },
|
|
{ componentKey: "raw-component-key" },
|
|
{ sourceSessionId: "component-session-1" },
|
|
{ result: { ...safeCommand.result, payload: { dsn: "sqlite:///srv/scum/SCUM.db" } } },
|
|
{ result: { ...safeCommand.result, payload: { endpoint: "tcp://127.0.0.1:9999" } } },
|
|
{ result: { ...safeCommand.result, payload: { output: "/Users/operator/scum/config.yaml" } } }
|
|
])("rejects forbidden command projection %#", (unsafe) => {
|
|
expect(() => parseSafeGameClientBridgeCommand({ ...safeCommand, ...unsafe })).toThrow(/forbidden|sensitive/i);
|
|
});
|
|
|
|
it("rejects credentials nested inside snapshot payloads", () => {
|
|
const unsafe = structuredClone(safeSnapshotList);
|
|
unsafe.items[0].payload = { players: [{ playerId: "player-1", credential: "raw-password" }] } as unknown as typeof unsafe.items[0]["payload"];
|
|
expect(() => parseSafeGameClientBridgeSnapshotList(unsafe)).toThrow(/forbidden/i);
|
|
});
|
|
});
|