





















1+import type {
2+OpenClawPluginNodeInvokePolicy,
3+OpenClawPluginNodeInvokePolicyContext,
4+} from "openclaw/plugin-sdk/plugin-entry";
5+import { describe, expect, it, vi } from "vitest";
6+import { createLazyFileTransferNodeInvokePolicy } from "./lazy-node-invoke-policy.js";
7+8+function createPolicyContext(
9+overrides: Partial<OpenClawPluginNodeInvokePolicyContext> = {},
10+): OpenClawPluginNodeInvokePolicyContext {
11+return {
12+nodeId: "node-1",
13+command: "file.fetch",
14+params: { path: "/tmp/a.txt" },
15+config: {} as never,
16+pluginConfig: {},
17+node: {
18+nodeId: "node-1",
19+displayName: "Test Node",
20+commands: ["file.fetch"],
21+},
22+client: null,
23+invokeNode: vi.fn<OpenClawPluginNodeInvokePolicyContext["invokeNode"]>(async () => ({
24+ok: true,
25+payload: { ok: true },
26+payloadJSON: null,
27+})),
28+ ...overrides,
29+};
30+}
31+32+describe("lazy file-transfer node invoke policy", () => {
33+it("exposes command metadata without loading the delegate", () => {
34+const loadPolicy = vi.fn<() => Promise<OpenClawPluginNodeInvokePolicy>>();
35+36+const policy = createLazyFileTransferNodeInvokePolicy(loadPolicy);
37+38+expect(policy.commands).toEqual(["file.fetch", "dir.list", "dir.fetch", "file.write"]);
39+expect(loadPolicy).not.toHaveBeenCalled();
40+});
41+42+it("loads and caches the delegate on first handle", async () => {
43+const invokeNode = vi.fn<OpenClawPluginNodeInvokePolicyContext["invokeNode"]>(async () => ({
44+ok: true,
45+payload: { ok: true },
46+payloadJSON: null,
47+}));
48+const delegateHandle = vi.fn<OpenClawPluginNodeInvokePolicy["handle"]>(async (ctx) => {
49+await ctx.invokeNode();
50+return { ok: true, payload: { delegated: true } };
51+});
52+const loadPolicy = vi.fn<() => Promise<OpenClawPluginNodeInvokePolicy>>(async () => ({
53+commands: ["file.fetch"],
54+handle: delegateHandle,
55+}));
56+const policy = createLazyFileTransferNodeInvokePolicy(loadPolicy);
57+58+await expect(policy.handle(createPolicyContext({ invokeNode }))).resolves.toEqual({
59+ok: true,
60+payload: { delegated: true },
61+});
62+await expect(policy.handle(createPolicyContext({ invokeNode }))).resolves.toEqual({
63+ok: true,
64+payload: { delegated: true },
65+});
66+67+expect(loadPolicy).toHaveBeenCalledTimes(1);
68+expect(delegateHandle).toHaveBeenCalledTimes(2);
69+expect(invokeNode).toHaveBeenCalledTimes(2);
70+});
71+72+it("fails closed when the delegate cannot load", async () => {
73+const invokeNode = vi.fn<OpenClawPluginNodeInvokePolicyContext["invokeNode"]>(async () => ({
74+ok: true,
75+payload: { ok: true },
76+payloadJSON: null,
77+}));
78+const policy = createLazyFileTransferNodeInvokePolicy(async () => {
79+throw new Error("load failed");
80+});
81+82+await expect(policy.handle(createPolicyContext({ invokeNode }))).resolves.toMatchObject({
83+ok: false,
84+code: "PLUGIN_POLICY_UNAVAILABLE",
85+unavailable: true,
86+});
87+expect(invokeNode).not.toHaveBeenCalled();
88+});
89+90+it("does not rewrite delegate failures as load failures", async () => {
91+const delegateError = new Error("delegate failed");
92+const policy = createLazyFileTransferNodeInvokePolicy(async () => ({
93+commands: ["file.fetch"],
94+handle: async () => {
95+throw delegateError;
96+},
97+}));
98+99+await expect(policy.handle(createPolicyContext())).rejects.toBe(delegateError);
100+});
101+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。