import { describe, expect, it } from "vitest"; import { configDiffViewFromPreview } from "./ServerDetailPage"; import serverDetailPageSource from "./ServerDetailPage.tsx?raw"; import clientManagerLifecyclePanelSource from "../components/ClientManagerLifecyclePanel.tsx?raw"; import runtimeDLLExtensionsPanelSource from "../components/RuntimeDLLExtensionsPanel.tsx?raw"; import sourceRCONCommandPanelSource from "../components/SourceRCONCommandPanel.tsx?raw"; import artifactTransferSource from "../utils/artifactTransfer.ts?raw"; import type { ServerConfigDiffPreviewResponse } from "../api/types"; const preview: ServerConfigDiffPreviewResponse = { serverInstanceId: "server-1", configVersion: 7, key: "server.properties", currentContent: "max-players=20\npvp=true\n", proposedContent: "max-players=40\npvp=true\n", diff: [ { kind: "removed", oldNumber: 1, content: "max-players=20" }, { kind: "added", newNumber: 1, content: "max-players=40" }, { kind: "context", oldNumber: 2, newNumber: 2, content: "pvp=true" } ], hasChanges: true, source: "platform-review", reviewedAt: "2026-07-06T00:00:00Z" }; describe("ServerDetailPage config write approval", () => { it("maps platform diff preview responses into the display diff without losing approval metadata", () => { const view = configDiffViewFromPreview(preview); expect(view).toMatchObject({ serverInstanceId: "server-1", configVersion: 7, key: "server.properties", source: "platform-review", summary: "+1 / -1 行变更", nextContent: "max-players=40\npvp=true\n" }); expect(view.lines).toEqual([ { kind: "removed", text: "max-players=20" }, { kind: "added", text: "max-players=40" }, { kind: "same", text: "pvp=true" } ]); }); it("uses config preview and approval APIs instead of generic config.write job creation", () => { expect(serverDetailPageSource).toContain("previewServerConfigDiff"); expect(serverDetailPageSource).toContain("approveServerConfigWrite"); expect(serverDetailPageSource).not.toContain('capability: "config.write"'); }); it("uses typed server metadata APIs and keeps destructive deletion out of detail metadata", () => { expect(serverDetailPageSource).toContain("updateServerInstance(instance.id"); expect(serverDetailPageSource).toContain("serverMetadataUpdateRequestFromForm"); expect(serverDetailPageSource).not.toContain("deleteServerInstance"); expect(serverDetailPageSource).not.toContain("serverDeleteConfirmation"); expect(serverDetailPageSource).not.toContain("canDeleteServer(instance.state)"); expect(serverDetailPageSource).not.toContain("请输入当前登录密码"); expect(serverDetailPageSource).not.toContain("fallbackConfig"); }); it("does not locally mutate visible config after dispatching approval jobs", () => { expect(serverDetailPageSource).not.toContain("setCurrentConfig(suggestion.diff.nextContent)"); expect(serverDetailPageSource).not.toContain("content: diff.nextContent"); }); it("uses platform-mediated artifact download and bridge references without backend internals", () => { expect(serverDetailPageSource).toContain("openArtifactDownload"); expect(serverDetailPageSource).toContain("readArtifactContent"); expect(serverDetailPageSource).toContain("parsePluginArtifactReference"); expect(serverDetailPageSource).toContain("浏览器制品传输"); expect(serverDetailPageSource).not.toContain("storage://"); expect(serverDetailPageSource).not.toContain("unix://"); expect(serverDetailPageSource).not.toContain("Bearer "); }); it("surfaces run distribution and client-manager workflows through platform-mediated APIs", () => { expect(serverDetailPageSource).toContain('params.routeKey !== "run-builder"'); expect(serverDetailPageSource).toContain('id="run-builder"'); expect(serverDetailPageSource).toContain("scrollIntoView"); expect(serverDetailPageSource).toContain("getServerRuntimeActions"); expect(serverDetailPageSource).toContain("generateRunDistribution"); expect(serverDetailPageSource).toContain("downloadLatestRunDistribution"); expect(serverDetailPageSource).toContain("pushRunUpdate"); expect(serverDetailPageSource).toContain("generateClientManager"); expect(serverDetailPageSource).toContain("resetClientManagerKey"); expect(serverDetailPageSource).toContain("checkDependencies"); expect(serverDetailPageSource).toContain("installDependencies"); expect(serverDetailPageSource).toContain("listServerLiveLogs"); expect(serverDetailPageSource).toContain("requestLogBackfill"); expect(serverDetailPageSource).toContain("ClientManagerLifecyclePanel"); expect(clientManagerLifecyclePanelSource).toContain("listClientManagerLifecycles"); expect(clientManagerLifecyclePanelSource).toContain("deployClientManager"); expect(clientManagerLifecyclePanelSource).toContain("controlClientManager"); expect(clientManagerLifecyclePanelSource).toContain("updateClientManager"); expect(clientManagerLifecyclePanelSource).toContain("retryClientManagerLifecycle"); expect(clientManagerLifecyclePanelSource).toContain("revokeClientManagerSession"); expect(clientManagerLifecyclePanelSource).toContain("uninstallClientManager"); expect(clientManagerLifecyclePanelSource).toContain("expectedDeploymentGeneration"); expect(clientManagerLifecyclePanelSource).not.toContain("secretRef"); expect(clientManagerLifecyclePanelSource).not.toContain("hostPath"); expect(clientManagerLifecyclePanelSource).not.toContain("process.pid"); expect(serverDetailPageSource).not.toContain("authKey"); expect(serverDetailPageSource).not.toContain("password="); expect(serverDetailPageSource).not.toContain("unix://"); expect(serverDetailPageSource).not.toContain("tcp://"); expect(serverDetailPageSource).not.toContain("mysql://"); expect(serverDetailPageSource).not.toContain("sqlite://"); }); it("renders declared UE4SS DLL update policy without local paths or RCON secrets", () => { expect(serverDetailPageSource).toContain("RuntimeDLLExtensionsPanel"); expect(runtimeDLLExtensionsPanelSource).toContain("启动前自动校验和更新"); expect(runtimeDLLExtensionsPanelSource).toContain("运行:SCUM 服务器受监管启动"); expect(runtimeDLLExtensionsPanelSource).toContain("由 UE4SS 正常加载"); expect(runtimeDLLExtensionsPanelSource).toContain("Linux 启动前拒绝"); for (const forbidden of ["dllRef", "targetKey", "rconPort", "password="]) { expect(runtimeDLLExtensionsPanelSource).not.toContain(forbidden); } }); it("adds direct SCUM chat and raw commands through the one-time typed RCON API", () => { expect(serverDetailPageSource).toContain("SourceRCONCommandPanel"); expect(sourceRCONCommandPanelSource).toContain("sendSourceRCONCommand"); expect(sourceRCONCommandPanelSource).toContain("不保留聊天或指令记录"); expect(sourceRCONCommandPanelSource).toContain("不会显示执行回包"); expect(sourceRCONCommandPanelSource).not.toContain("ConfirmDialog"); expect(sourceRCONCommandPanelSource).not.toContain("operations."); for (const forbidden of ["password", "host", "transcript", "history"]) { expect(sourceRCONCommandPanelSource).not.toContain(forbidden); } }); it("loads the dependency catalog only after runtime actions expose dependency operations", () => { const runtimeDistributionSectionSource = serverDetailPageSource.split("function RuntimeDistributionSection")[1]?.split("function RuntimeBindingFields")[0] ?? ""; expect(runtimeDistributionSectionSource).toContain('action.key === "dependencies-check" || action.key === "dependencies-install"'); expect(runtimeDistributionSectionSource).toContain("dependencyActions.some((action) => action.available)"); expect(runtimeDistributionSectionSource).toContain("getDependencyCatalog(instance.id)"); }); it("reviews and updates only redacted runtime binding metadata", () => { const runtimeBindingSectionSource = serverDetailPageSource.split("function RuntimeBindingSection")[1]?.split("function RuntimeDistributionSection")[0] ?? ""; expect(serverDetailPageSource).toContain("getServerRuntimeBinding"); expect(serverDetailPageSource).toContain("updateServerRuntimeBinding"); expect(runtimeBindingSectionSource).toContain("missingKeys"); expect(runtimeBindingSectionSource).toContain('type={field.sensitive ? "password" : "text"}'); for (const forbidden of ["secret://", "/Users/", "/var/run/", "unix://", "tcp://", "mysql://", "sqlite://"]) { expect(runtimeBindingSectionSource).not.toContain(forbidden); } }); it("routes plugin lifecycle controls through platform lifecycle APIs instead of generic jobs", () => { expect(serverDetailPageSource).toContain('action === "install" || action === "restart"'); expect(serverDetailPageSource).toContain('action !== "start" && action !== "stop" && action !== "status"'); expect(serverDetailPageSource).toContain('control.lifecycleAction === "start" || control.lifecycleAction === "stop" || control.lifecycleAction === "status"'); expect(serverDetailPageSource).toContain("platformApiClient.startServerInstance(instance.id"); expect(serverDetailPageSource).toContain("platformApiClient.stopServerInstance(instance.id"); expect(serverDetailPageSource).toContain("platformApiClient.queryServerProcessStatus(instance.id"); expect(serverDetailPageSource).toContain("serverLifecycleCommandRequest(instance, \"start\")"); expect(serverDetailPageSource).toContain("serverLifecycleCommandRequest(instance, \"stop\")"); expect(serverDetailPageSource).not.toContain('capability: "process.start"'); expect(serverDetailPageSource).not.toContain('capability: "process.stop"'); }); it("keeps plugin lifecycle and bridge-visible output on platform-owned logical references", () => { expect(serverDetailPageSource).toContain("parsePluginArtifactReference(result)"); expect(serverDetailPageSource).toContain("platformApiClient.openArtifactDownload(artifact.id)"); expect(serverDetailPageSource).toContain("downloadArtifactReference(reference"); expect(artifactTransferSource).toContain("readContent(reference.artifactId"); expect(artifactTransferSource).toContain("replace(/Bearer\\s+[^\\s]+/gi, \"[token]\")"); expect(artifactTransferSource).toContain("replace(/sk-[A-Za-z0-9_-]+/g, \"[secret]\")"); expect(serverDetailPageSource).not.toContain("storage://bucket"); expect(serverDetailPageSource).not.toContain("runSocket"); expect(serverDetailPageSource).not.toContain("rawApiKey"); expect(serverDetailPageSource).not.toContain("apiKeyRef"); }); });