import { renderToStaticMarkup } from "react-dom/server"; import { describe, expect, it } from "vitest"; import type { GameClientBridgeSnapshotResponse } from "../api/types"; import type { ScumOperationsPageContract } from "../contracts/scumOperations"; import { ScumOperationsPanel, type ScumOperationsPanelData } from "./ScumOperationsPanel"; import scumOperationsPanelSource from "./ScumOperationsPanel.tsx?raw"; const now = "2026-07-20T08:00:00Z"; const contract: ScumOperationsPageContract = { pluginId: "game.scum", routeKey: "files-config", serverInstanceId: "server-1", title: "SCUM 运维", permissions: ["server.read", "server.logs.read", "server.game-client.read", "server.game-client.command", "server.game-client.maintenance"], bridgeActions: ["server.instances.read", "logs.query", "client-manager.request"], commands: [ { type: "announcement.send", title: "Send announcement", permission: "server.game-client.command", approvalLevel: "operator", payloadSchemaRef: "schemas/bridge/announcement.json", timeoutSeconds: 30, maxPayloadBytes: 4096 }, { type: "companion.diagnostics", title: "Collect diagnostics", permission: "server.game-client.read", approvalLevel: "none", payloadSchemaRef: "schemas/bridge/diagnostics.json", timeoutSeconds: 30, maxPayloadBytes: 2048 }, { type: "restart.prepare", title: "Prepare restart", permission: "server.game-client.maintenance", approvalLevel: "platform-admin", payloadSchemaRef: "schemas/bridge/restart.json", timeoutSeconds: 60, maxPayloadBytes: 4096 } ], snapshots: ["companion.health", "online.sessions", "players", "squads", "vehicles", "flags"].map((type) => ({ type, schemaVersion: "1", schemaRef: `schemas/bridge/${type}.json`, keepForSeconds: 3600, maxRecords: 24 })), queryTemplates: [], logSources: [{ key: "scum-chat-events", kind: "file.tail", streamKey: "scum.chat", retentionDays: 30 }], logEvents: [ { key: "scum-chat", title: "SCUM chat", sourceKey: "scum-chat-events", eventType: "scum.chat", permission: "server.logs.read", schemaRef: "schemas/log-events/chat.json", retentionDays: 30, severity: "info" }, { key: "scum-kill", title: "SCUM kill", sourceKey: "scum-chat-events", eventType: "scum.kill", permission: "server.logs.read", schemaRef: "schemas/log-events/kill.json", retentionDays: 30, severity: "warning" } ], productionLifecycle: { operations: ["install", "enable", "disable", "upgrade", "rollback", "retire", "dependency-check"], dependencyPolicy: "required", approvalRequired: ["disable", "rollback", "retire"] } }; function snapshot(type: string, payload: GameClientBridgeSnapshotResponse["payload"]): GameClientBridgeSnapshotResponse { return { id: `snapshot-${type}`, serverInstanceId: "server-1", pluginId: "game.scum", profileKey: "scum-client", type, schemaVersion: "1", streamKey: "current", sequence: 1, observedAt: now, payload, retention: { keepForSeconds: 3600, maxRecords: 24 }, createdAt: now, expiresAt: "2026-07-20T09:00:00Z" }; } const data: ScumOperationsPanelData = { status: { serverInstanceId: "server-1", pluginId: "game.scum", available: true, profiles: [{ pluginId: "game.scum", profileKey: "scum-client", available: true, commandTypes: ["announcement.send", "companion.diagnostics", "restart.prepare"], snapshotTypes: contract.snapshots.map((item) => item.type), queryTemplateKeys: [] }] }, commands: [{ id: "command-1", serverInstanceId: "server-1", pluginId: "game.scum", profileKey: "scum-client", commandType: "announcement.send", priority: 50, state: "succeeded", approvalState: "approved", resultSummary: "announcement delivered", result: { status: "succeeded", summary: "announcement delivered", payload: { delivered: true }, completedAt: now }, expiresAt: now, createdAt: now, updatedAt: now, completedAt: now }], snapshots: [ snapshot("companion.health", { status: "online", observedAt: now, version: "1.0.0", latencyMs: 20 }), snapshot("online.sessions", { observedAt: now, onlineCount: 1, sessions: [{ sessionId: "game-session-1", playerName: "Moonlight" }] }), snapshot("players", { observedAt: now, players: [{ playerId: "player-1", playerName: "Moonlight", status: "online" }] }), snapshot("squads", { observedAt: now, squads: [{ squadId: "squad-1", name: "Lunar", memberCount: 4 }] }), snapshot("vehicles", { observedAt: now, vehicles: [{ vehicleId: "vehicle-1", vehicleType: "truck", status: "parked" }] }), snapshot("flags", { observedAt: now, flags: [{ flagId: "flag-1", status: "active" }] }) ], logs: [{ streamKey: "scum.chat", eventType: "scum.chat", entry: { seq: 1, timestamp: now, level: "info", line: "token=raw-secret /Users/operator/scum.log", redacted: true } }], backups: [{ id: "backup-1", serverInstanceId: "server-1", artifactId: "artifact-1", checksum: "sha256:safe", sizeBytes: 2048, state: "available", recoveryStatus: "verified", retentionUntil: "2026-07-27T08:00:00Z", createdAt: now, updatedAt: now }], errors: [] }; describe("ScumOperationsPanel", () => { it("renders the first safe SCUM operations surface", () => { const html = renderToStaticMarkup(); for (const label of ["Companion", "命令队列与结果", "玩家与世界状态快照", "玩家 1", "会话 1", "小队 1", "载具 1", "旗帜 1", "语义日志", "scum.chat", "维护与备份策略", "backup-1", "已批准"]) { expect(html).toContain(label); } expect(html).toContain("不触发自动封禁或惩罚"); expect(html).not.toContain("raw-secret"); expect(html).not.toContain("/Users/"); expect(html).not.toMatch(/sessionToken|componentKey|secretRef|hostPath|dsn|runSocket|credential/i); }); it("keeps commands disabled with a visible bridge availability reason", () => { const html = renderToStaticMarkup( ({ ...profile, available: false, reason: "component heartbeat is unavailable" })) } }} />); expect(html).toContain("compatible companion is offline"); expect(html).toContain("disabled"); const profileReasonHtml = renderToStaticMarkup( ({ ...profile, available: false, reason: "component heartbeat is unavailable" })) } }} />); expect(profileReasonHtml).toContain("component heartbeat is unavailable"); expect(profileReasonHtml).toContain("disabled"); }); it("renders every command approval state without leaking unsafe result details", () => { const approvalStates = ["not_required", "pending", "approved", "rejected"] as const; const unsafeSummary = "token=raw-command-secret /Users/operator/result.json unix:///var/run/scum.sock"; const commands = approvalStates.map((approvalState, index) => ({ ...data.commands[0]!, id: `command-${index + 1}`, approvalState, resultSummary: approvalState === "rejected" ? unsafeSummary : `approval ${approvalState}`, result: approvalState === "rejected" ? { ...data.commands[0]!.result!, summary: unsafeSummary } : data.commands[0]!.result })); const html = renderToStaticMarkup(); for (const label of ["无需审批", "待审批", "已批准", "已拒绝"]) { expect(html).toContain(label); } expect(html).not.toContain("raw-command-secret"); expect(html).not.toContain("/Users/operator"); expect(html).not.toContain("unix:///var/run"); }); it("uses shared console surfaces without page-local ambient decoration", () => { expect(scumOperationsPanelSource).toContain('className="console-panel"'); expect(scumOperationsPanelSource).toContain('className="resource-table-wrap"'); expect(scumOperationsPanelSource).not.toMatch(/position:\s*fixed|sparkle|snowflake|magic-circle|backdrop-layer/i); expect(scumOperationsPanelSource).not.toContain("InsecureSkipVerify"); }); });