test: clear devices cli broad matchers

This commit is contained in:
Peter Steinberger
2026-05-10 13:29:43 +01:00
parent cfadb0a356
commit da20e8b7f0

View File

@@ -118,6 +118,42 @@ function mockLocalPairingFallback(message?: string) {
summarizeDeviceTokens.mockReturnValue(undefined);
}
function requireRecord(value: unknown, label: string): Record<string, unknown> {
expect(typeof value).toBe("object");
expect(value).not.toBeNull();
if (typeof value !== "object" || value === null) {
throw new Error(`${label} was not an object`);
}
return value as Record<string, unknown>;
}
function expectRecordFields(record: Record<string, unknown>, fields: Record<string, unknown>) {
for (const [key, value] of Object.entries(fields)) {
expect(record[key]).toEqual(value);
}
}
function requireGatewayCall(index: number): Record<string, unknown> {
const call = (callGateway.mock.calls as unknown[][])[index]?.[0];
return requireRecord(call, `gateway call ${index + 1}`);
}
function expectGatewayCall(index: number, fields: Record<string, unknown>) {
expectRecordFields(requireGatewayCall(index), fields);
}
function hasGatewayMethod(method: string): boolean {
return (callGateway.mock.calls as unknown[][]).some((call) => {
const params = call[0];
return (
typeof params === "object" &&
params !== null &&
"method" in params &&
params.method === method
);
});
}
describe("devices cli approve", () => {
it("uses admin scope when approving an admin-scope request", async () => {
callGateway
@@ -130,20 +166,12 @@ describe("devices cli approve", () => {
await runDevicesApprove(["req-123"]);
expect(callGateway).toHaveBeenCalledTimes(2);
expect(callGateway).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
method: "device.pair.list",
}),
);
expect(callGateway).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
method: "device.pair.approve",
params: { requestId: "req-123" },
scopes: ["operator.admin"],
}),
);
expectGatewayCall(0, { method: "device.pair.list" });
expectGatewayCall(1, {
method: "device.pair.approve",
params: { requestId: "req-123" },
scopes: ["operator.admin"],
});
});
it("keeps pairing scope for non-admin device approvals", async () => {
@@ -161,14 +189,11 @@ describe("devices cli approve", () => {
await runDevicesApprove(["req-pairing"]);
expect(callGateway).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
method: "device.pair.approve",
params: { requestId: "req-pairing" },
scopes: ["operator.pairing"],
}),
);
expectGatewayCall(1, {
method: "device.pair.approve",
params: { requestId: "req-pairing" },
scopes: ["operator.pairing"],
});
});
it("retries explicit approval with admin scope when a paired-device session is ownership-denied", async () => {
@@ -183,22 +208,16 @@ describe("devices cli approve", () => {
await runDevicesApprove(["req-cross-device"]);
expect(callGateway).toHaveBeenCalledTimes(3);
expect(callGateway).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
method: "device.pair.approve",
params: { requestId: "req-cross-device" },
scopes: undefined,
}),
);
expect(callGateway).toHaveBeenNthCalledWith(
3,
expect.objectContaining({
method: "device.pair.approve",
params: { requestId: "req-cross-device" },
scopes: ["operator.admin"],
}),
);
expectGatewayCall(1, {
method: "device.pair.approve",
params: { requestId: "req-cross-device" },
scopes: undefined,
});
expectGatewayCall(2, {
method: "device.pair.approve",
params: { requestId: "req-cross-device" },
scopes: ["operator.admin"],
});
});
it("uses admin scope when a repair approval would inherit an admin token", async () => {
@@ -220,14 +239,11 @@ describe("devices cli approve", () => {
await runDevicesApprove(["req-repair"]);
expect(callGateway).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
method: "device.pair.approve",
params: { requestId: "req-repair" },
scopes: ["operator.admin"],
}),
);
expectGatewayCall(1, {
method: "device.pair.approve",
params: { requestId: "req-repair" },
scopes: ["operator.admin"],
});
});
it("prints selected details and exits when implicit approval is used", async () => {
@@ -256,9 +272,7 @@ describe("devices cli approve", () => {
await runDevicesApprove([]);
expect(callGateway).toHaveBeenCalledTimes(1);
expect(callGateway).toHaveBeenCalledWith(
expect.objectContaining({ method: "device.pair.list" }),
);
expectGatewayCall(0, { method: "device.pair.list" });
const logOutput = runtime.log.mock.calls.map((c) => readRuntimeCallText(c)).join("\n");
expect(logOutput).toContain("req-abc");
expect(logOutput).toContain("Device Nine");
@@ -268,9 +282,7 @@ describe("devices cli approve", () => {
expect.stringContaining("openclaw devices approve req-abc"),
);
expect(runtime.exit).toHaveBeenCalledWith(1);
expect(callGateway).not.toHaveBeenCalledWith(
expect.objectContaining({ method: "device.pair.approve" }),
);
expect(hasGatewayMethod("device.pair.approve")).toBe(false);
});
it("sanitizes preview ip output for implicit approval", async () => {
@@ -329,13 +341,8 @@ describe("devices cli approve", () => {
await runDevicesApprove(args);
expect(callGateway).toHaveBeenNthCalledWith(
1,
expect.objectContaining({ method: "device.pair.list" }),
);
expect(callGateway).not.toHaveBeenCalledWith(
expect.objectContaining({ method: "device.pair.approve" }),
);
expectGatewayCall(0, { method: "device.pair.list" });
expect(hasGatewayMethod("device.pair.approve")).toBe(false);
expect(runtime.error).toHaveBeenCalledWith(
expect.stringContaining(`openclaw devices approve ${expectedRequestId}`),
);
@@ -360,9 +367,7 @@ describe("devices cli approve", () => {
expect(runtime.error).toHaveBeenCalledWith(
expect.stringContaining("openclaw devices approve req-blank"),
);
expect(callGateway).not.toHaveBeenCalledWith(
expect.objectContaining({ method: "device.pair.approve" }),
);
expect(hasGatewayMethod("device.pair.approve")).toBe(false);
});
it("includes explicit gateway flags in the rerun approval command", async () => {
@@ -386,9 +391,7 @@ describe("devices cli approve", () => {
);
expect(errorOutput).toContain("Reuse the same --token option when rerunning.");
expect(errorOutput).not.toContain("secret-token");
expect(callGateway).not.toHaveBeenCalledWith(
expect.objectContaining({ method: "device.pair.approve" }),
);
expect(hasGatewayMethod("device.pair.approve")).toBe(false);
});
it("returns JSON for implicit approval preview in JSON mode", async () => {
@@ -415,9 +418,7 @@ describe("devices cli approve", () => {
},
});
expect(runtime.exit).toHaveBeenCalledWith(1);
expect(callGateway).not.toHaveBeenCalledWith(
expect.objectContaining({ method: "device.pair.approve" }),
);
expect(hasGatewayMethod("device.pair.approve")).toBe(false);
});
it("prints an error and exits when no pending requests are available", async () => {
@@ -426,14 +427,10 @@ describe("devices cli approve", () => {
await runDevicesApprove([]);
expect(callGateway).toHaveBeenCalledTimes(1);
expect(callGateway).toHaveBeenCalledWith(
expect.objectContaining({ method: "device.pair.list" }),
);
expectGatewayCall(0, { method: "device.pair.list" });
expect(runtime.error).toHaveBeenCalledWith("No pending device pairing requests to approve");
expect(runtime.exit).toHaveBeenCalledWith(1);
expect(callGateway).not.toHaveBeenCalledWith(
expect.objectContaining({ method: "device.pair.approve" }),
);
expect(hasGatewayMethod("device.pair.approve")).toBe(false);
});
});
@@ -444,12 +441,10 @@ describe("devices cli remove", () => {
await runDevicesCommand(["remove", "device-1"]);
expect(callGateway).toHaveBeenCalledTimes(1);
expect(callGateway).toHaveBeenCalledWith(
expect.objectContaining({
method: "device.pair.remove",
params: { deviceId: "device-1" },
}),
);
expectGatewayCall(0, {
method: "device.pair.remove",
params: { deviceId: "device-1" },
});
});
});
@@ -474,22 +469,10 @@ describe("devices cli clear", () => {
await runDevicesCommand(["clear", "--yes", "--pending"]);
expect(callGateway).toHaveBeenNthCalledWith(
1,
expect.objectContaining({ method: "device.pair.list" }),
);
expect(callGateway).toHaveBeenNthCalledWith(
2,
expect.objectContaining({ method: "device.pair.remove", params: { deviceId: "device-1" } }),
);
expect(callGateway).toHaveBeenNthCalledWith(
3,
expect.objectContaining({ method: "device.pair.remove", params: { deviceId: "device-2" } }),
);
expect(callGateway).toHaveBeenNthCalledWith(
4,
expect.objectContaining({ method: "device.pair.reject", params: { requestId: "req-1" } }),
);
expectGatewayCall(0, { method: "device.pair.list" });
expectGatewayCall(1, { method: "device.pair.remove", params: { deviceId: "device-1" } });
expectGatewayCall(2, { method: "device.pair.remove", params: { deviceId: "device-2" } });
expectGatewayCall(3, { method: "device.pair.reject", params: { requestId: "req-1" } });
});
});
@@ -531,7 +514,7 @@ describe("devices cli tokens", () => {
])("$label", async ({ argv, expectedCall }) => {
callGateway.mockResolvedValueOnce({ ok: true });
await runDevicesCommand(argv);
expect(callGateway).toHaveBeenCalledWith(expect.objectContaining(expectedCall));
expectGatewayCall(0, expectedCall);
});
it("rejects blank device or role values", async () => {
@@ -553,9 +536,7 @@ describe("devices cli local fallback", () => {
await runDevicesCommand(["list"]);
expect(callGateway).toHaveBeenCalledWith(
expect.objectContaining({ method: "device.pair.list" }),
);
expectGatewayCall(0, { method: "device.pair.list" });
expect(listDevicePairing).toHaveBeenCalledTimes(1);
expect(runtime.log).toHaveBeenCalledWith(expect.stringContaining(fallbackNotice));
});