Files
openclaw/src/secrets/runtime.loadable-plugin-origins.test.ts
Sally O'Malley 6037a74660 Add plugin manifest contract for SecretRef provider integrations (#82326)
* secret-provider-integrations

Signed-off-by: sallyom <somalley@redhat.com>

* feat(secrets): configure plugin provider presets

* secrets: use plugin-managed provider refs

Signed-off-by: sallyom <somalley@redhat.com>

* fix secretref auth profile service env

* test secret provider integration e2e

* fix secretref plugin config service env

* fix secret provider preset schema alignment

* stabilize secret provider service proof

* validate secret provider plugin integrations

* harden secret provider resolver paths

* scope secret provider config validation

* stabilize openai secret provider proof

* fix secret provider metadata proof

* stabilize config baseline proof

* fix secret provider e2e lint

---------

Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-05-29 18:20:45 -04:00

185 lines
5.9 KiB
TypeScript

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { PluginManifestRecord } from "../plugins/manifest-registry.js";
import { asConfig, setupSecretsRuntimeSnapshotTestHooks } from "./runtime.test-support.ts";
const manifestMocks = vi.hoisted(() => ({
listPluginOriginsFromMetadataSnapshot: vi.fn(
(snapshot: { plugins: Array<{ id: string; origin: string }> }) =>
new Map(snapshot.plugins.map((record) => [record.id, record.origin])),
),
loadPluginMetadataSnapshot: vi.fn<() => { plugins: Array<{ id: string; origin: string }> }>(
() => ({
plugins: [],
}),
),
}));
vi.mock("./runtime-manifest.runtime.js", () => ({
listPluginOriginsFromMetadataSnapshot: manifestMocks.listPluginOriginsFromMetadataSnapshot,
loadPluginMetadataSnapshot: manifestMocks.loadPluginMetadataSnapshot,
}));
const { prepareSecretsRuntimeSnapshot } = setupSecretsRuntimeSnapshotTestHooks();
describe("prepareSecretsRuntimeSnapshot loadable plugin origins", () => {
afterEach(() => {
manifestMocks.listPluginOriginsFromMetadataSnapshot.mockClear();
manifestMocks.loadPluginMetadataSnapshot.mockReset();
manifestMocks.loadPluginMetadataSnapshot.mockReturnValue({ plugins: [] });
});
it("skips metadata snapshot loading when plugin entries are absent", async () => {
await prepareSecretsRuntimeSnapshot({
config: asConfig({
models: {
providers: {
openai: {
apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
models: [{ id: "gpt-5.4", name: "gpt-5.4" }],
},
},
},
}),
env: { OPENAI_API_KEY: "sk-test" },
includeAuthStoreRefs: false,
});
expect(manifestMocks.loadPluginMetadataSnapshot).not.toHaveBeenCalled();
expect(manifestMocks.listPluginOriginsFromMetadataSnapshot).not.toHaveBeenCalled();
});
it("derives loadable plugin origins from the shared metadata snapshot", async () => {
const snapshot = {
plugins: [{ id: "demo", origin: "workspace" }],
};
manifestMocks.loadPluginMetadataSnapshot.mockReturnValue(snapshot);
await prepareSecretsRuntimeSnapshot({
config: asConfig({
plugins: {
entries: {
demo: {
config: {
apiKey: { source: "env", provider: "default", id: "DEMO_API_KEY" },
},
},
},
},
}),
env: { HOME: "/home/demo", DEMO_API_KEY: "sk-demo" },
includeAuthStoreRefs: false,
});
const snapshotCalls = manifestMocks.loadPluginMetadataSnapshot.mock.calls as unknown as Array<
[
{
config: {
plugins?: unknown;
};
workspaceDir: unknown;
env: Record<string, unknown>;
},
]
>;
const snapshotParams = snapshotCalls[0]?.[0];
expect(snapshotParams?.config.plugins).toStrictEqual({
entries: {
demo: {
config: {
apiKey: { source: "env", provider: "default", id: "DEMO_API_KEY" },
},
},
},
});
expect(typeof snapshotParams?.workspaceDir).toBe("string");
expect(snapshotParams?.env.HOME).toBe("/home/demo");
expect(snapshotParams?.env.DEMO_API_KEY).toBe("sk-demo");
expect(manifestMocks.listPluginOriginsFromMetadataSnapshot).toHaveBeenCalledWith(snapshot);
});
it("carries the shared manifest registry into plugin-managed SecretRef resolution", async () => {
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "oc-runtime-secret-provider-"));
fs.writeFileSync(path.join(rootDir, "index.ts"), "export default {};\n", "utf8");
fs.writeFileSync(
path.join(rootDir, "resolve.mjs"),
[
"import process from 'node:process';",
"let input = '';",
"process.stdin.setEncoding('utf8');",
"process.stdin.on('data', (chunk) => input += chunk);",
"process.stdin.on('end', () => {",
" const request = JSON.parse(input);",
" process.stdout.write(JSON.stringify({ protocolVersion: 1, values: Object.fromEntries(request.ids.map((id) => [id, `value:${id}`])) }));",
"});",
"",
].join("\n"),
"utf8",
);
const plugin: PluginManifestRecord = {
id: "vault-secrets",
rootDir,
source: path.join(rootDir, "index.ts"),
manifestPath: path.join(rootDir, "openclaw.plugin.json"),
origin: "global",
channels: [],
providers: [],
cliBackends: [],
skills: [],
hooks: [],
secretProviderIntegrations: {
vault: {
providerAlias: "vault",
source: "exec",
command: "${node}",
args: ["./resolve.mjs"],
},
},
};
const pluginMetadataSnapshot = {
plugins: [plugin],
manifestRegistry: {
plugins: [plugin],
diagnostics: [],
},
};
try {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
gateway: {
auth: {
mode: "token",
token: { source: "exec", provider: "vault", id: "gateway/token" },
},
},
secrets: {
providers: {
vault: {
source: "exec",
pluginIntegration: {
pluginId: "vault-secrets",
integrationId: "vault",
},
},
},
},
}),
env: { HOME: rootDir },
includeAuthStoreRefs: false,
pluginMetadataSnapshot,
});
expect(snapshot.config.gateway?.auth?.token).toBe("value:gateway/token");
expect(manifestMocks.loadPluginMetadataSnapshot).not.toHaveBeenCalled();
expect(manifestMocks.listPluginOriginsFromMetadataSnapshot).toHaveBeenCalledWith(
pluginMetadataSnapshot,
);
} finally {
fs.rmSync(rootDir, { recursive: true, force: true });
}
});
});