import { describe, expect, it } from "vitest"; import { configDiffViewFromPreview } from "./ServerDetailPage"; import serverManagementTerminalSource from "../components/ServerManagementTerminalDrawer.tsx?raw"; import serverDetailPageSource from "./ServerDetailPage.tsx?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 AI config diff approval without exposing raw config workbench APIs", () => { expect(serverDetailPageSource).toContain("approveAIConfigDiff"); expect(serverDetailPageSource).toContain("AI 配置助手"); expect(serverDetailPageSource).not.toContain("previewServerConfigDiff"); expect(serverDetailPageSource).not.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("keeps artifact transfer and backend internals out of server detail", () => { expect(serverDetailPageSource).toContain("ServerFilesSection"); expect(serverDetailPageSource).toContain("getServerFileWorkspace"); expect(serverDetailPageSource).toContain("downloadServerFileResult"); expect(serverDetailPageSource).not.toContain("openArtifactDownload"); expect(serverDetailPageSource).not.toContain("readArtifactContent"); expect(serverDetailPageSource).not.toContain("浏览器制品传输"); expect(serverDetailPageSource).not.toContain("storage://"); expect(serverDetailPageSource).not.toContain("unix://"); expect(serverDetailPageSource).not.toContain("Bearer "); }); it("keeps run distribution and client-manager workflows out of server detail tabs", () => { expect(serverDetailPageSource).not.toContain('id="run-builder"'); expect(serverDetailPageSource).not.toContain("getServerRuntimeActions"); expect(serverDetailPageSource).not.toContain("generateRunDistribution"); expect(serverDetailPageSource).not.toContain("downloadLatestRunDistribution"); expect(serverDetailPageSource).not.toContain("pushRunUpdate"); expect(serverDetailPageSource).not.toContain("generateClientManager"); expect(serverDetailPageSource).not.toContain("ClientManagerLifecyclePanel"); expect(serverManagementTerminalSource).toContain("openServerLogEvents"); 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("does not expose manual runtime configuration surfaces", () => { expect(serverDetailPageSource).not.toContain("getServerRuntimeBinding"); expect(serverDetailPageSource).not.toContain("updateServerRuntimeBinding"); expect(serverDetailPageSource).not.toContain("运行配置绑定"); expect(serverDetailPageSource).not.toContain("RuntimeDLLExtensionsPanel"); }); it("uses the historical terminal drawer without restoring separate raw command panels", () => { expect(serverDetailPageSource).not.toContain("SourceRCONCommandPanel"); expect(serverManagementTerminalSource).toContain("dispatchSourceRCONCommand"); expect(serverDetailPageSource).not.toContain("scumManagementRCONCommandRequest"); expect(serverDetailPageSource).toContain("打开终端"); expect(serverDetailPageSource).toContain("ServerManagementTerminalDrawer"); expect(serverManagementTerminalSource).toContain("terminal-output-topbar"); expect(serverManagementTerminalSource).toContain("terminalQuickCommandCatalog"); expect(serverDetailPageSource).toContain("PluginPageHostPage"); }); it("removes generic plugin controls from server detail", () => { expect(serverDetailPageSource).not.toContain("PluginControlsSection"); expect(serverDetailPageSource).not.toContain("PluginBridgeExecutionPanel"); expect(serverDetailPageSource).not.toContain("platformApiClient.createJob({"); expect(serverDetailPageSource).not.toContain("插件控制"); expect(serverDetailPageSource).toContain("platformApiClient.startServerInstance(current.id"); expect(serverDetailPageSource).toContain("platformApiClient.stopServerInstance(current.id"); expect(serverDetailPageSource).toContain("serverLifecycleCommandRequest(current, \"start\")"); expect(serverDetailPageSource).toContain("serverLifecycleCommandRequest(current, \"stop\")"); expect(serverDetailPageSource).not.toContain('capability: "process.start"'); expect(serverDetailPageSource).not.toContain('capability: "process.stop"'); }); it("leaves guided deployment to the generated Run heartbeat workflow", () => { expect(serverDetailPageSource).not.toContain("canDeployServer(instance.data.state)"); expect(serverDetailPageSource).not.toContain("requestDeployment(instance.data)"); expect(serverDetailPageSource).not.toContain("platformApiClient.deployServerInstance(current.id"); expect(serverDetailPageSource).not.toContain('capability: "process.install"'); }); it("routes plugin-declared pages into server detail tabs", () => { expect(serverDetailPageSource).toContain("serverDetailSectionEntries(readyPlugin)"); expect(serverDetailPageSource).toContain("plugin:${page.key}"); expect(serverDetailPageSource).toContain('section === "files"'); expect(serverDetailPageSource).toContain("PluginPageSection"); expect(serverDetailPageSource).toContain("PluginPageHostPage"); expect(serverDetailPageSource).not.toContain("ScumFileManagementSection"); }); it("keeps plugin lifecycle output out of raw bridge-visible detail surfaces", () => { expect(serverDetailPageSource).not.toContain("platformApiClient.openArtifactDownload(artifact.id)"); expect(serverDetailPageSource).not.toContain("downloadArtifactReference(reference"); expect(serverDetailPageSource).not.toContain("storage://bucket"); expect(serverDetailPageSource).not.toContain("runSocket"); expect(serverDetailPageSource).not.toContain("rawApiKey"); expect(serverDetailPageSource).not.toContain("apiKeyRef"); }); });