Files
openclaw/extensions/onepassword/index.test.ts
Peter Steinberger 6bb85f177f feat(onepassword): optional 1Password secrets broker plugin (#106133)
* feat(onepassword): add optional 1Password secrets broker plugin

Curated slug registry with per-item auto/approve/deny policy, plugin-approval
gating with expiring allow-always grants, SQLite audit history, onepassword
status/audit CLI, and a single-attempt op client (--cache=false, minimal env).

Closes #105924

* docs(plugins): refresh generated inventory count after rebase

* fix(onepassword): scope grants and field reads

* fix(onepassword): bound grant retention

* fix(onepassword): satisfy deadcode ratchet and hook allowlist contract

* fix(onepassword): honor live policy reloads

* refactor(onepassword): trim private exports

* test(onepassword): satisfy plugin boundaries

* test(onepassword): document temp directory boundary
2026-07-13 03:12:47 -07:00

135 lines
3.8 KiB
TypeScript

import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { describe, expect, it, vi } from "vitest";
import plugin from "./index.js";
describe("onepassword plugin", () => {
it("opens bounded fail-closed grant and audit stores", () => {
const store = {
register: vi.fn(),
lookup: vi.fn(),
delete: vi.fn(),
entries: vi.fn(async () => []),
};
const openKeyedStore = vi.fn(() => store);
const registerCli = vi.fn();
const registerTool = vi.fn();
plugin.register(
createTestPluginApi({
id: "onepassword",
name: "1Password",
source: "test",
config: {},
runtime: {
state: {
openKeyedStore,
resolveStateDir: () => "/tmp/openclaw-onepassword-test",
},
} as never,
registerCli,
registerTool,
}),
);
expect(openKeyedStore).toHaveBeenNthCalledWith(1, {
namespace: "grants",
maxEntries: 1_024,
overflowPolicy: "evict-oldest",
});
expect(openKeyedStore).toHaveBeenNthCalledWith(2, {
namespace: "audit",
maxEntries: 40_000,
overflowPolicy: "evict-oldest",
});
expect(registerCli).toHaveBeenCalledTimes(1);
expect(registerTool).not.toHaveBeenCalled();
});
it("evaluates enablement and before-tool policy from live plugin config", async () => {
const pluginConfig = (policy: "auto" | "deny") => ({
vault: "Automation",
items: {
deploy: {
item: "Deploy token",
field: "credential",
policy,
},
},
});
let livePolicy: "auto" | "deny" = "auto";
let liveEnabled = true;
const current = () => ({
plugins: {
entries: {
onepassword: {
enabled: liveEnabled,
config: pluginConfig(livePolicy),
},
},
},
});
const store = {
register: vi.fn(async () => undefined),
lookup: vi.fn(async () => undefined),
delete: vi.fn(async () => undefined),
entries: vi.fn(async () => []),
};
const on = vi.fn();
plugin.register(
createTestPluginApi({
id: "onepassword",
name: "1Password",
source: "test",
config: current(),
pluginConfig: pluginConfig("auto"),
runtime: {
config: { current },
state: {
openKeyedStore: () => store,
resolveStateDir: () => "/tmp/openclaw-onepassword-test",
},
} as never,
registerCli: vi.fn(),
registerTool: vi.fn(),
on,
}),
);
const beforeToolCall = on.mock.calls.find(([name]) => name === "before_tool_call")?.[1] as
| ((
event: { toolName: string; toolCallId: string; params: Record<string, unknown> },
context: { toolName: string; toolCallId: string; agentId: string },
) => Promise<{ block?: boolean } | void>)
| undefined;
if (!beforeToolCall) {
throw new Error("missing before_tool_call hook");
}
liveEnabled = false;
await expect(
beforeToolCall(
{
toolName: "onepassword",
toolCallId: "live-enabled-1",
params: { action: "get", slug: "deploy", reason: "test live enablement" },
},
{ toolName: "onepassword", toolCallId: "live-enabled-1", agentId: "agent-a" },
),
).resolves.toMatchObject({ block: true });
liveEnabled = true;
livePolicy = "deny";
await expect(
beforeToolCall(
{
toolName: "onepassword",
toolCallId: "live-policy-1",
params: { action: "get", slug: "deploy", reason: "test live policy" },
},
{ toolName: "onepassword", toolCallId: "live-policy-1", agentId: "agent-a" },
),
).resolves.toMatchObject({ block: true });
});
});