first commit
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { PluginBridgeManifestContract } from "../contracts/pluginBridge";
|
||||
import {
|
||||
createPluginBridgeDispatcher,
|
||||
createPluginBridgeHostContext,
|
||||
filterAllowedPermissions,
|
||||
parsePluginArtifactReference,
|
||||
validateBridgeExecutionRequest
|
||||
} from "./pluginBridgeHost";
|
||||
|
||||
const plugin: PluginBridgeManifestContract = {
|
||||
id: "game.example",
|
||||
declaredPermissions: ["server.read", "server.logs.read", "server.files.read", "server.artifacts.read", "ai.invoke"],
|
||||
bridgeActions: ["server.instances.read", "logs.query", "files.request", "artifacts.open", "ai.invoke"],
|
||||
pages: [
|
||||
{
|
||||
key: "logs",
|
||||
title: "Logs",
|
||||
path: "/logs",
|
||||
permissions: ["server.logs.read", "server.files.read", "server.artifacts.read", "ai.invoke"],
|
||||
bridgeActions: ["logs.query", "files.request", "artifacts.open", "ai.invoke"]
|
||||
}
|
||||
],
|
||||
aiPurposes: ["logs.diagnose"]
|
||||
};
|
||||
|
||||
describe("plugin bridge host utilities", () => {
|
||||
it("filters page permissions against manifest permissions", () => {
|
||||
expect(filterAllowedPermissions(plugin.declaredPermissions, ["server.logs.read", "server.artifacts.read"])).toEqual(["server.logs.read", "server.artifacts.read"]);
|
||||
});
|
||||
|
||||
it("creates safe host context without secret-bearing fields", () => {
|
||||
const context = createPluginBridgeHostContext({
|
||||
plugin,
|
||||
routeKey: "logs",
|
||||
serverInstanceId: "server-1",
|
||||
themeTokens: { colorScheme: "dark", accentColor: "#22c55e" }
|
||||
});
|
||||
|
||||
expect(context).toEqual({
|
||||
pluginId: "game.example",
|
||||
routeKey: "logs",
|
||||
serverInstanceId: "server-1",
|
||||
themeTokens: { colorScheme: "dark", accentColor: "#22c55e" },
|
||||
permissions: ["server.logs.read", "server.files.read", "server.artifacts.read", "ai.invoke"],
|
||||
bridgeActions: ["logs.query", "files.request", "artifacts.open", "ai.invoke"],
|
||||
aiPurposes: ["logs.diagnose"]
|
||||
});
|
||||
expect(context).not.toHaveProperty("apiKey");
|
||||
expect(context).not.toHaveProperty("runCredential");
|
||||
expect(context).not.toHaveProperty("hostPath");
|
||||
});
|
||||
|
||||
it("builds bridge execution requests through the platform client", async () => {
|
||||
const context = createPluginBridgeHostContext({
|
||||
plugin,
|
||||
routeKey: "logs",
|
||||
serverInstanceId: "server-1",
|
||||
themeTokens: { colorScheme: "dark", accentColor: "#22c55e" }
|
||||
});
|
||||
const client = {
|
||||
executePluginBridge: vi.fn(async () => ({
|
||||
requestId: "req-1",
|
||||
pluginId: "game.example",
|
||||
routeKey: "logs",
|
||||
serverInstanceId: "server-1",
|
||||
action: "logs.query",
|
||||
status: "ok",
|
||||
result: { entryCount: "0" }
|
||||
}))
|
||||
};
|
||||
|
||||
const dispatch = createPluginBridgeDispatcher(context, client);
|
||||
await expect(dispatch({ requestId: "req-1", action: "logs.query", payload: { logStreamId: "log-1" } })).resolves.toMatchObject({
|
||||
status: "ok",
|
||||
result: { entryCount: "0" }
|
||||
});
|
||||
expect(client.executePluginBridge).toHaveBeenCalledWith({
|
||||
requestId: "req-1",
|
||||
pluginId: "game.example",
|
||||
routeKey: "logs",
|
||||
serverInstanceId: "server-1",
|
||||
action: "logs.query",
|
||||
aiPurpose: undefined,
|
||||
payload: { logStreamId: "log-1" }
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects denied, unsafe, and cancelled bridge execution locally", async () => {
|
||||
const context = createPluginBridgeHostContext({
|
||||
plugin,
|
||||
routeKey: "logs",
|
||||
serverInstanceId: "server-1",
|
||||
themeTokens: { colorScheme: "dark", accentColor: "#22c55e" }
|
||||
});
|
||||
expect(validateBridgeExecutionRequest(context, { requestId: "req-denied", action: "server.instances.read" })).toMatchObject({ code: "unsupported_action" });
|
||||
expect(
|
||||
validateBridgeExecutionRequest(context, { requestId: "req-unsafe", action: "files.request", payload: { key: "/Users/tasia/.ssh/id_rsa" } })
|
||||
).toMatchObject({ code: "unsafe_payload" });
|
||||
|
||||
const client = { executePluginBridge: vi.fn() };
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
const dispatch = createPluginBridgeDispatcher(context, client);
|
||||
await expect(dispatch({ requestId: "req-cancel", action: "logs.query", payload: { logStreamId: "log-1" } }, controller.signal)).resolves.toMatchObject({
|
||||
status: "cancelled",
|
||||
error: { code: "cancelled" }
|
||||
});
|
||||
expect(client.executePluginBridge).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("dispatches mediated AI requests without provider configuration", async () => {
|
||||
const context = createPluginBridgeHostContext({
|
||||
plugin,
|
||||
routeKey: "logs",
|
||||
serverInstanceId: "server-1",
|
||||
themeTokens: { colorScheme: "dark", accentColor: "#22c55e" }
|
||||
});
|
||||
expect(validateBridgeExecutionRequest(context, { requestId: "ai-denied", action: "ai.invoke", aiPurpose: "config.suggest" })).toMatchObject({ code: "ai_purpose_denied" });
|
||||
const client = {
|
||||
executePluginBridge: vi.fn(async () => ({
|
||||
requestId: "ai-1",
|
||||
pluginId: "game.example",
|
||||
routeKey: "logs",
|
||||
serverInstanceId: "server-1",
|
||||
action: "ai.invoke",
|
||||
status: "ok",
|
||||
result: { recommendation: "Mock AI recommendation", mocked: "true" }
|
||||
}))
|
||||
};
|
||||
const dispatch = createPluginBridgeDispatcher(context, client);
|
||||
const response = await dispatch({ requestId: "ai-1", action: "ai.invoke", aiPurpose: "logs.diagnose", payload: { prompt: "Summarize logs" } });
|
||||
expect(response).toMatchObject({ status: "ok", result: { mocked: "true" } });
|
||||
const serialized = JSON.stringify(response);
|
||||
expect(serialized).not.toContain("apiKeyRef");
|
||||
expect(serialized).not.toContain("rawApiKey");
|
||||
expect(serialized).not.toContain("baseUrl");
|
||||
});
|
||||
|
||||
it("parses safe artifact bridge references and rejects backend internals", () => {
|
||||
expect(
|
||||
parsePluginArtifactReference({
|
||||
artifactId: "artifact-1",
|
||||
filename: "artifact-1.bin",
|
||||
contentType: "application/octet-stream",
|
||||
sizeBytes: "128",
|
||||
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"
|
||||
})
|
||||
).toMatchObject({ artifactId: "artifact-1", downloadUrl: "/api/v1/artifacts/artifact-1/content", rangeSupported: true });
|
||||
|
||||
expect(
|
||||
parsePluginArtifactReference({
|
||||
artifactId: "artifact-1",
|
||||
filename: "/Users/tasia/artifact.bin",
|
||||
contentType: "application/octet-stream",
|
||||
sizeBytes: "128",
|
||||
checksum: "sha256:abc",
|
||||
downloadUrl: "storage://bucket/artifact-1",
|
||||
expiresAt: "2026-07-03T00:15:00Z",
|
||||
rangeSupported: "true",
|
||||
chunkSizeBytes: "1048576"
|
||||
})
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user