import { describe, expect, it } from "vitest"; import { bridgeError, canRequestBridgeAction, createAIInvocationRequest, createArtifactOpenRequest, createClientManagerRequest, createBridgeExecutionRequest, createLifecycleDispatchRequest, createBridgeRequest, createDependencyActionRequest, createLogBackfillRequest, createRemoteAccessRequest, createRunDistributionRequest, hasPluginPermission, parseArtifactReference, parseClientManagerLifecycleStatus, parseBridgeExecutionResponse, parseAIInvocationResponse, type GamePluginManifest, type RuntimeClientManagerProfile, type PluginLifecycleActionDeclaration, type PluginBridgeContext } from "../sdk/index.js"; import { validateLifecycleActionFile, validateManifestFile } from "../scripts/validate-manifest.js"; describe("plugin manifest validation", () => { it("accepts the development example manifest", () => { expect(validateManifestFile("examples/dev-game-plugin/manifest.json")).toEqual([]); }); it("accepts the SCUM server plugin manifest", () => { expect(validateManifestFile("examples/scum-server-plugin/manifest.json")).toEqual([]); }); it("accepts the Minecraft server plugin manifest", () => { expect(validateManifestFile("examples/minecraft-server-plugin/manifest.json")).toEqual([]); }); it("rejects a manifest with an invalid create form schema", () => { const errors = validateManifestFile("tests/fixtures/invalid-create-form-manifest.json"); expect(errors.some((error) => error.includes("createForm") && error.includes("label"))).toBe(true); }); it("rejects unsafe direct run and raw AI key requests", () => { const errors = validateManifestFile("tests/fixtures/unsafe-manifest.json"); expect(errors.some((error) => error.includes("direct run access"))).toBe(true); expect(errors.some((error) => error.includes("raw credential or AI/provider key"))).toBe(true); }); it("rejects unsafe runtime profile values", () => { const errors = validateManifestFile("tests/fixtures/unsafe-runtime-profile-manifest.json"); expect(errors.some((error) => error.includes("raw credential or AI/provider key"))).toBe(true); expect(errors.some((error) => error.includes("raw host path"))).toBe(true); expect(errors.some((error) => error.includes("arbitrary shell"))).toBe(true); expect(errors.some((error) => error.includes("not approved for dependency download"))).toBe(true); expect(errors.some((error) => error.includes("client-manager.deploy is required"))).toBe(true); expect(errors.some((error) => error.includes("offline threshold"))).toBe(true); expect(errors.some((error) => error.includes("profile version is below minimumVersion"))).toBe(true); }); it("validates typed lifecycle declarations and rejects shell/path escapes", () => { const declaration: PluginLifecycleActionDeclaration = { version: 1, action: "start", mode: "supervised", executableKey: "bin/game-server", arguments: ["--foreground"] }; expect(declaration.action).toBe("start"); const errors = validateLifecycleActionFile("tests/fixtures/unsafe-lifecycle-action.json", "start"); expect(errors.some((error) => error.includes("pattern") || error.includes("arbitrary shell"))).toBe(true); expect(errors.some((error) => error.includes("raw credential"))).toBe(true); }); }); describe("plugin SDK", () => { it("checks declared bridge permissions", () => { const context: PluginBridgeContext = { pluginId: "game.example", routeKey: "logs", permissions: ["server.read", "server.logs.read", "server.artifacts.read"] }; expect(hasPluginPermission(context, "server.logs.read")).toBe(true); expect(hasPluginPermission(context, "server.artifacts.read")).toBe(true); expect(hasPluginPermission(context, "ai.invoke")).toBe(false); }); it("builds typed bridge request envelopes without owning transport", () => { const context: PluginBridgeContext = { pluginId: "game.example", routeKey: "logs", serverInstanceId: "server-1", permissions: ["server.read", "server.logs.read"] }; const request = createBridgeRequest({ id: "request-1", context, action: "logs.query", payload: { streamKey: "stdout", limit: 100 } }); expect(request).toEqual({ id: "request-1", pluginId: "game.example", routeKey: "logs", serverInstanceId: "server-1", action: "logs.query", payload: { streamKey: "stdout", limit: 100 } }); }); it("checks bridge action permissions and AI purposes locally", () => { const context: PluginBridgeContext = { pluginId: "game.example", routeKey: "logs", permissions: ["server.read", "server.logs.read", "ai.invoke"], aiPurposes: ["logs.diagnose"] }; expect(canRequestBridgeAction(context, "logs.query")).toBe(true); expect(canRequestBridgeAction(context, "files.request")).toBe(false); expect(canRequestBridgeAction(context, "ai.invoke", { aiPurpose: "logs.diagnose" })).toBe(true); expect(canRequestBridgeAction(context, "ai.invoke", { aiPurpose: "config.suggest" })).toBe(false); }); it("returns safe bridge errors without credential or transport fields", () => { const error = bridgeError("missing_permission", "Permission is required", ["server.files.read"]); expect(error).toEqual({ code: "missing_permission", message: "Permission is required", details: ["server.files.read"] }); expect(error).not.toHaveProperty("apiKey"); expect(error).not.toHaveProperty("runSocket"); expect(error).not.toHaveProperty("hostPath"); }); it("builds and parses execution envelopes without owning transport", () => { const context: PluginBridgeContext = { pluginId: "game.example", routeKey: "logs", serverInstanceId: "server-1", permissions: ["server.read", "server.logs.read"] }; const request = createBridgeExecutionRequest({ requestId: "exec-1", context, action: "logs.query", payload: { logStreamId: "log-1", limit: "100" } }); expect(request).toEqual({ requestId: "exec-1", pluginId: "game.example", routeKey: "logs", serverInstanceId: "server-1", action: "logs.query", aiPurpose: undefined, payload: { logStreamId: "log-1", limit: "100" } }); expect(request).not.toHaveProperty("fetch"); expect(request).not.toHaveProperty("authorization"); const parsed = parseBridgeExecutionResponse({ requestId: "exec-1", pluginId: "game.example", routeKey: "logs", serverInstanceId: "server-1", action: "logs.query", status: "ok", result: { entryCount: "0" } }); expect(parsed).toMatchObject({ status: "ok", result: { entryCount: "0" } }); expect(parsed).not.toHaveProperty("apiKey"); expect(parsed).not.toHaveProperty("runSocket"); }); it("builds mediated AI requests and parses redacted responses", () => { const context: PluginBridgeContext = { pluginId: "game.example", routeKey: "logs", serverInstanceId: "server-1", permissions: ["ai.invoke"], aiPurposes: ["logs.diagnose"] }; const request = createAIInvocationRequest({ requestId: "ai-1", context, purpose: "logs.diagnose", prompt: "Summarize warnings", contextRefs: { server: "server://server-1" } }); expect(request).toMatchObject({ pluginId: "game.example", purpose: "logs.diagnose", prompt: "Summarize warnings" }); expect(request).not.toHaveProperty("apiKeyRef"); expect(request).not.toHaveProperty("providerBaseUrl"); const parsed = parseAIInvocationResponse({ requestId: "ai-1", purpose: "logs.diagnose", status: "ok", recommendation: "Review the warning trend.", usage: { model: "mock", mocked: true, inputTokens: 5, outputTokens: 6 } }); expect(parsed).toMatchObject({ status: "ok", usage: { mocked: true } }); expect(JSON.stringify(parsed)).not.toContain("sk-"); expect(JSON.stringify(parsed)).not.toContain("apiKeyRef"); }); it("builds artifact open envelopes and parses safe platform references", () => { const context: PluginBridgeContext = { pluginId: "game.example", routeKey: "logs", serverInstanceId: "server-1", permissions: ["server.artifacts.read"] }; expect(canRequestBridgeAction(context, "artifacts.open")).toBe(true); expect( createArtifactOpenRequest({ requestId: "artifact-open-1", context, artifactId: "artifact-1" }) ).toEqual({ requestId: "artifact-open-1", pluginId: "game.example", routeKey: "logs", serverInstanceId: "server-1", action: "artifacts.open", aiPurpose: undefined, payload: { artifactId: "artifact-1" } }); const reference = parseArtifactReference({ artifactId: "artifact-1", filename: "artifact-1.bin", contentType: "application/octet-stream", sizeBytes: "64", checksum: "sha256:abc", downloadUrl: "/api/v1/artifacts/artifact-1/content", expiresAt: "2026-07-03T00:15:00Z", rangeSupported: "true", chunkSizeBytes: "1048576", storageBehavior: "platform-memory-transfer-session" }); expect(reference).toMatchObject({ artifactId: "artifact-1", rangeSupported: true }); expect(JSON.stringify(reference)).not.toContain("/Users/"); expect(JSON.stringify(reference)).not.toContain("storage://"); expect(JSON.stringify(reference)).not.toContain("Bearer "); expect( parseArtifactReference({ artifactId: "artifact-1", filename: "artifact.bin", contentType: "application/octet-stream", sizeBytes: "64", checksum: "sha256:abc", downloadUrl: "storage://bucket/artifact-1", expiresAt: "2026-07-03T00:15:00Z", rangeSupported: "true", chunkSizeBytes: "1048576" }) ).toBeUndefined(); }); it("builds lifecycle dispatch envelopes without direct run transport", () => { const context: PluginBridgeContext = { pluginId: "game.example", routeKey: "overview", serverInstanceId: "server-1", permissions: ["server.lifecycle"] }; expect(canRequestBridgeAction(context, "jobs.dispatch")).toBe(true); const request = createLifecycleDispatchRequest({ requestId: "lifecycle-start-1", context, action: "start", expectedConfigVersion: 2, idempotencyKey: "idem-lifecycle-start" }); expect(request).toEqual({ requestId: "lifecycle-start-1", pluginId: "game.example", routeKey: "overview", serverInstanceId: "server-1", action: "jobs.dispatch", aiPurpose: undefined, payload: { lifecycleAction: "start", capability: "process.start", expectedConfigVersion: "2", idempotencyKey: "idem-lifecycle-start" } }); expect(JSON.stringify(request)).not.toContain("http://"); expect(JSON.stringify(request)).not.toContain("unix://"); expect(JSON.stringify(request)).not.toContain("/Users/"); expect(JSON.stringify(request)).not.toContain("Bearer "); expect(JSON.stringify(request)).not.toContain("sk-"); }); it("builds remote access request envelopes without direct transport secrets", () => { const context: PluginBridgeContext = { pluginId: "game.minecraft", routeKey: "rcon", serverInstanceId: "server-1", permissions: ["server.remote.access"] }; expect(canRequestBridgeAction(context, "remote.access.request")).toBe(true); const request = createRemoteAccessRequest({ requestId: "remote-rcon-1", context, capability: "remote.run.rcon.command", targetKey: "rcon/command", inputRef: "input://server-1/rcon/command/1", idempotencyKey: "idem-remote-rcon" }); expect(request).toEqual({ requestId: "remote-rcon-1", pluginId: "game.minecraft", routeKey: "rcon", serverInstanceId: "server-1", action: "remote.access.request", aiPurpose: undefined, payload: { capability: "remote.run.rcon.command", targetKey: "rcon/command", inputRef: "input://server-1/rcon/command/1", idempotencyKey: "idem-remote-rcon" } }); expect(JSON.stringify(request)).not.toContain("tcp://"); expect(JSON.stringify(request)).not.toContain("password="); expect(JSON.stringify(request)).not.toContain("/Users/"); }); it("types runtime profile declarations without raw credentials", () => { const clientManager: RuntimeClientManagerProfile = { key: "safe-client-manager", version: "1.2.3", repository: { url: "https://github.com/example/safe-client.git", revisionPolicy: "pinned", revision: "0123456789abcdef" }, supportedTargets: [{ os: "linux", arch: "amd64" }], build: { system: "go", entryRef: "cmd/client/main.go" }, outputArtifacts: ["safe-client"], deployment: { mode: "run-supervised", executableRef: "safe-client", arguments: ["--config", "config.json"], requiredRunCapabilities: ["client-manager.deploy", "client-manager.control", "client-manager.update", "client-manager.rollback", "client-manager.uninstall"] }, lifecycle: { actions: ["start", "stop", "restart", "status", "update", "rollback", "uninstall"], startupTimeoutSeconds: 30, stopTimeoutSeconds: 15 }, health: { mode: "component-heartbeat", intervalSeconds: 15, degradedAfterSeconds: 45, offlineAfterSeconds: 120, requiredCapabilities: ["component.register", "component.heartbeat", "component.health"] }, compatibility: { minimumVersion: "1.0.0", allowDowngrade: false }, updatePolicy: { strategy: "manual-staged", requireApproval: true, healthConfirmationSeconds: 60, retainPrevious: true } }; const manifest: GamePluginManifest = { id: "game.runtime", name: "Runtime Fixture", version: "0.1.0", kind: "game-plugin", server: { type: "runtime", displayName: "Runtime Fixture", createFormSchema: "schemas/create-form.schema.json" }, capabilities: ["process.start", "process.stop", "logs.read"], permissions: ["server.read", "server.lifecycle", "server.logs.read"], runtimeProfiles: { discovery: [{ key: "java", kind: "command.version", targetKey: "java", required: true }], dependencyProbes: [{ key: "java-21", kind: "java.version", targetKey: "java", minimumVersion: "21" }], logSources: [{ key: "console", kind: "process.stdout", streamKey: "console", cursorKind: "sequence" }], transportProfiles: [{ key: "files", kind: "file", capabilities: ["files.read"] }], clientManagers: [clientManager] } }; expect(manifest.runtimeProfiles?.discovery?.[0].targetKey).toBe("java"); expect(manifest.runtimeProfiles?.clientManagers?.[0].deployment?.requiredRunCapabilities).toContain("client-manager.deploy"); expect(JSON.stringify(manifest)).not.toContain("password="); }); it("builds run distribution, dependency, log backfill, and client-manager envelopes", () => { const context: PluginBridgeContext = { pluginId: "game.scum", routeKey: "remote", serverInstanceId: "server-1", permissions: ["server.run.distribution", "server.dependencies.manage", "server.logs.read", "server.client-manager.manage"] }; expect(canRequestBridgeAction(context, "run.distribution.request")).toBe(true); expect(createRunDistributionRequest({ requestId: "run-gen-1", context, operation: "generate", targetOS: "windows", targetArch: "amd64", idempotencyKey: "idem-run" })).toMatchObject({ action: "run.distribution.request", payload: { operation: "generate", targetOS: "windows", targetArch: "amd64", idempotencyKey: "idem-run" } }); expect(createDependencyActionRequest({ requestId: "dep-1", context, operation: "check", probeKey: "steamcmd", idempotencyKey: "idem-dep" })).toMatchObject({ action: "dependencies.request", payload: { operation: "check", probeKey: "steamcmd" } }); expect(createDependencyActionRequest({ requestId: "dep-2", context, operation: "install", probeKey: "steamcmd", planKey: "install-steamcmd-linux", planDigest: `sha256:${"a".repeat(64)}`, idempotencyKey: "idem-dep-install" })).toMatchObject({ action: "dependencies.request", payload: { operation: "install", probeKey: "steamcmd", planKey: "install-steamcmd-linux", planDigest: `sha256:${"a".repeat(64)}` } }); expect(() => createDependencyActionRequest({ requestId: "dep-unsafe", context, operation: "install", probeKey: "steamcmd", planKey: "install-steamcmd-linux", idempotencyKey: "idem-dep-unsafe" })).toThrow(/reviewed plan SHA-256 digest/); expect(createLogBackfillRequest({ requestId: "logs-1", context, sourceKey: "chat-log", limit: 500, idempotencyKey: "idem-logs" })).toMatchObject({ action: "logs.backfill.request", payload: { sourceKey: "chat-log", limit: "500" } }); expect(createClientManagerRequest({ requestId: "client-1", context, operation: "generate", profileKey: "scum-client-manager", targetOS: "windows", targetArch: "amd64", idempotencyKey: "idem-client" })).toMatchObject({ action: "client-manager.request", payload: { operation: "generate", profileKey: "scum-client-manager" } }); expect(JSON.stringify(createClientManagerRequest({ requestId: "client-2", context, operation: "reset-key", profileKey: "scum-client-manager", idempotencyKey: "idem-reset" }))).not.toContain("secret"); expect(createClientManagerRequest({ requestId: "client-3", context, operation: "deploy", profileKey: "scum-client-manager", installationId: "cm-install-1", artifactId: "artifact-1", expectedDeploymentGeneration: 2, idempotencyKey: "idem-deploy" })).toMatchObject({ action: "client-manager.request", payload: { operation: "deploy", installationId: "cm-install-1", artifactId: "artifact-1", expectedDeploymentGeneration: "2" } }); expect(parseClientManagerLifecycleStatus({ installationId: "cm-install-1", profileKey: "scum-client-manager", status: "online", phase: "healthy", targetOS: "windows", targetArch: "amd64", version: "1.0.0", artifactId: "artifact-1", deploymentGeneration: "2", health: "healthy", actions: "stop,restart,update,uninstall" })).toMatchObject({ installationId: "cm-install-1", deploymentGeneration: 2, actions: ["stop", "restart", "update", "uninstall"] }); expect(parseClientManagerLifecycleStatus({ installationId: "cm-install-1", profileKey: "scum-client-manager", status: "online", deploymentGeneration: "2", actions: "stop", healthReason: "Bearer stolen-session" })).toBeUndefined(); }); });