





















@@ -65,6 +65,32 @@ async function executeNodes(
6565type NodesToolResult = Awaited<ReturnType<typeof executeNodes>>;
6666type GatewayMockResult = Record<string, unknown> | null | undefined;
676768+function requireRecord(value: unknown, label: string): Record<string, unknown> {
69+if (!value || typeof value !== "object" || Array.isArray(value)) {
70+throw new Error(`expected ${label}`);
71+}
72+return value as Record<string, unknown>;
73+}
74+75+function expectInvokeParams(
76+invokeParams: unknown,
77+expected: {
78+command: string;
79+nodeId?: string;
80+params?: Record<string, unknown>;
81+},
82+) {
83+const record = requireRecord(invokeParams, "node.invoke params");
84+expect(record.command).toBe(expected.command);
85+if (expected.nodeId !== undefined) {
86+expect(record.nodeId).toBe(expected.nodeId);
87+}
88+const params = requireRecord(record.params, `${expected.command} params`);
89+for (const [key, value] of Object.entries(expected.params ?? {})) {
90+expect(params[key]).toEqual(value);
91+}
92+}
93+6894function mockNodeList(params?: { commands?: string[]; remoteIp?: string }) {
6995return {
7096nodes: [
@@ -98,15 +124,15 @@ function expectFirstMediaUrl(result: NodesToolResult): string {
98124}
99125100126function expectFirstTextContains(result: NodesToolResult, expectedText: string) {
101-expect(result.content?.[0]).toMatchObject({
102- type: "text",
103- text: expect.stringContaining(expectedText),
104-});
127+const first = result.content?.[0];
128+expect(first?.type).toBe("text");
129+const text = first?.type === "text" ? first.text : "";
130+expect(text).toContain(expectedText);
105131}
106132107133function parseFirstTextJson(result: NodesToolResult): unknown {
108134const first = result.content?.[0];
109-expect(first).toMatchObject({ type: "text" });
135+expect(first?.type).toBe("text");
110136const text = first?.type === "text" ? first.text : "";
111137return JSON.parse(text);
112138}
@@ -138,7 +164,7 @@ function setupPhotosLatestMock(params?: { remoteIp?: string }) {
138164setupNodeInvokeMock({
139165 ...(params?.remoteIp ? { remoteIp: params.remoteIp } : {}),
140166onInvoke: (invokeParams) => {
141-expect(invokeParams).toMatchObject({
167+expectInvokeParams(invokeParams, {
142168command: "photos.latest",
143169params: PHOTOS_LATEST_DEFAULT_PARAMS,
144170});
@@ -162,7 +188,7 @@ describe("nodes camera_snap", () => {
162188it("uses front/high-quality defaults when params are omitted", async () => {
163189setupNodeInvokeMock({
164190onInvoke: (invokeParams) => {
165-expect(invokeParams).toMatchObject({
191+expectInvokeParams(invokeParams, {
166192command: "camera.snap",
167193params: {
168194facing: "front",
@@ -224,7 +250,7 @@ describe("nodes camera_snap", () => {
224250it("passes deviceId when provided", async () => {
225251setupNodeInvokeMock({
226252onInvoke: (invokeParams) => {
227-expect(invokeParams).toMatchObject({
253+expectInvokeParams(invokeParams, {
228254command: "camera.snap",
229255params: { deviceId: "cam-123" },
230256});
@@ -343,7 +369,7 @@ describe("nodes photos_latest", () => {
343369it("returns empty content/details when no photos are available", async () => {
344370setupNodeInvokeMock({
345371onInvoke: (invokeParams) => {
346-expect(invokeParams).toMatchObject({
372+expectInvokeParams(invokeParams, {
347373command: "photos.latest",
348374params: {
349375limit: 1,
@@ -380,11 +406,9 @@ describe("nodes photos_latest", () => {
380406expect(result.content ?? []).toStrictEqual([]);
381407const details =
382408(result.details as { photos?: Array<Record<string, unknown>> } | undefined)?.photos ?? [];
383-expect(details[0]).toMatchObject({
384-width: 1,
385-height: 1,
386-createdAt: "2026-03-04T00:00:00Z",
387-});
409+expect(details[0]?.width).toBe(1);
410+expect(details[0]?.height).toBe(1);
411+expect(details[0]?.createdAt).toBe("2026-03-04T00:00:00Z");
388412expect(expectFirstMediaUrl(result)).toMatch(/openclaw-camera-snap-.*\.jpg$/);
389413});
390414@@ -403,7 +427,7 @@ describe("nodes notifications_list", () => {
403427setupNodeInvokeMock({
404428commands: ["notifications.list"],
405429onInvoke: (invokeParams) => {
406-expect(invokeParams).toMatchObject({
430+expectInvokeParams(invokeParams, {
407431nodeId: NODE_ID,
408432command: "notifications.list",
409433params: {},
@@ -425,7 +449,7 @@ describe("nodes notifications_list", () => {
425449});
426450427451expectFirstTextContains(result, '"notifications"');
428-expect(parseFirstTextJson(result)).toMatchObject({
452+expect(parseFirstTextJson(result)).toStrictEqual({
429453enabled: true,
430454connected: true,
431455count: 1,
@@ -439,7 +463,7 @@ describe("nodes notifications_action", () => {
439463setupNodeInvokeMock({
440464commands: ["notifications.actions"],
441465onInvoke: (invokeParams) => {
442-expect(invokeParams).toMatchObject({
466+expectInvokeParams(invokeParams, {
443467nodeId: NODE_ID,
444468command: "notifications.actions",
445469params: {
@@ -459,7 +483,7 @@ describe("nodes notifications_action", () => {
459483});
460484461485expectFirstTextContains(result, '"dismiss"');
462-expect(parseFirstTextJson(result)).toMatchObject({
486+expect(parseFirstTextJson(result)).toStrictEqual({
463487ok: true,
464488key: "n1",
465489action: "dismiss",
@@ -470,7 +494,7 @@ describe("nodes notifications_action", () => {
470494setupNodeInvokeMock({
471495commands: ["notifications.actions"],
472496onInvoke: (invokeParams) => {
473-expect(invokeParams).toMatchObject({
497+expectInvokeParams(invokeParams, {
474498nodeId: NODE_ID,
475499command: "notifications.actions",
476500params: {
@@ -491,7 +515,7 @@ describe("nodes notifications_action", () => {
491515notificationReplyText: " On it ",
492516});
493517494-expect(parseFirstTextJson(result)).toMatchObject({
518+expect(parseFirstTextJson(result)).toStrictEqual({
495519ok: true,
496520key: "n2",
497521action: "reply",
@@ -504,7 +528,7 @@ describe("nodes location_get", () => {
504528setupNodeInvokeMock({
505529commands: ["location.get"],
506530onInvoke: (invokeParams) => {
507-expect(invokeParams).toMatchObject({
531+expectInvokeParams(invokeParams, {
508532nodeId: NODE_ID,
509533command: "location.get",
510534params: {
@@ -532,7 +556,7 @@ describe("nodes location_get", () => {
532556locationTimeoutMs: 4_500,
533557});
534558535-expect(parseFirstTextJson(result)).toMatchObject({
559+expect(parseFirstTextJson(result)).toStrictEqual({
536560latitude: 37.3346,
537561longitude: -122.009,
538562accuracyMeters: 18,
@@ -546,7 +570,7 @@ describe("nodes device_status and device_info", () => {
546570setupNodeInvokeMock({
547571commands: ["device.status", "device.info"],
548572onInvoke: (invokeParams) => {
549-expect(invokeParams).toMatchObject({
573+expectInvokeParams(invokeParams, {
550574nodeId: NODE_ID,
551575command: "device.status",
552576params: {},
@@ -571,7 +595,7 @@ describe("nodes device_status and device_info", () => {
571595setupNodeInvokeMock({
572596commands: ["device.status", "device.info"],
573597onInvoke: (invokeParams) => {
574-expect(invokeParams).toMatchObject({
598+expectInvokeParams(invokeParams, {
575599nodeId: NODE_ID,
576600command: "device.info",
577601params: {},
@@ -597,7 +621,7 @@ describe("nodes device_status and device_info", () => {
597621setupNodeInvokeMock({
598622commands: ["device.permissions"],
599623onInvoke: (invokeParams) => {
600-expect(invokeParams).toMatchObject({
624+expectInvokeParams(invokeParams, {
601625nodeId: NODE_ID,
602626command: "device.permissions",
603627params: {},
@@ -626,25 +650,21 @@ describe("nodes device_status and device_info", () => {
626650});
627651628652expectFirstTextContains(result, '"permissions"');
629-expect(parseFirstTextJson(result)).toMatchObject({
630-permissions: {
631-sms: {
632-status: "denied",
633-promptable: true,
634-capabilities: {
635-send: { status: "denied", promptable: true },
636-read: { status: "granted", promptable: false },
637-},
638-},
639-},
640-});
653+const parsed = requireRecord(parseFirstTextJson(result), "device permissions payload");
654+const permissions = requireRecord(parsed.permissions, "permissions");
655+const sms = requireRecord(permissions.sms, "sms permissions");
656+expect(sms.status).toBe("denied");
657+expect(sms.promptable).toBe(true);
658+const capabilities = requireRecord(sms.capabilities, "sms capabilities");
659+expect(capabilities.send).toStrictEqual({ status: "denied", promptable: true });
660+expect(capabilities.read).toStrictEqual({ status: "granted", promptable: false });
641661});
642662643663it("invokes device.health and returns payload", async () => {
644664setupNodeInvokeMock({
645665commands: ["device.health"],
646666onInvoke: (invokeParams) => {
647-expect(invokeParams).toMatchObject({
667+expectInvokeParams(invokeParams, {
648668nodeId: NODE_ID,
649669command: "device.health",
650670params: {},
@@ -671,7 +691,7 @@ describe("nodes invoke", () => {
671691it("allows metadata-only camera.list via generic invoke", async () => {
672692setupNodeInvokeMock({
673693onInvoke: (invokeParams) => {
674-expect(invokeParams).toMatchObject({
694+expectInvokeParams(invokeParams, {
675695command: "camera.list",
676696params: {},
677697});
@@ -689,10 +709,9 @@ describe("nodes invoke", () => {
689709invokeCommand: "camera.list",
690710});
691711692-expect(result.details).toMatchObject({
693-payload: {
694-devices: [{ id: "cam-back", name: "Back Camera" }],
695-},
712+const details = requireRecord(result.details, "camera.list details");
713+expect(details.payload).toStrictEqual({
714+devices: [{ id: "cam-back", name: "Back Camera" }],
696715});
697716});
698717@@ -710,7 +729,7 @@ describe("nodes invoke", () => {
710729it("allows media invoke commands when explicitly enabled", async () => {
711730setupNodeInvokeMock({
712731onInvoke: (invokeParams) => {
713-expect(invokeParams).toMatchObject({
732+expectInvokeParams(invokeParams, {
714733command: "photos.latest",
715734params: { limit: 1 },
716735});
@@ -732,10 +751,9 @@ describe("nodes invoke", () => {
732751{ allowMediaInvokeCommands: true },
733752);
734753735-expect(result.details).toMatchObject({
736-payload: {
737-photos: [{ format: "jpg", base64: "aGVsbG8=", width: 1, height: 1 }],
738-},
754+const details = requireRecord(result.details, "photos.latest invoke details");
755+expect(details.payload).toStrictEqual({
756+photos: [{ format: "jpg", base64: "aGVsbG8=", width: 1, height: 1 }],
739757});
740758});
741759});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。