



















@@ -144,6 +144,39 @@ function getLatestWs(): MockWebSocket {
144144return ws;
145145}
146146147+function requireRecord(value: unknown, label: string): Record<string, unknown> {
148+if (typeof value !== "object" || value === null || Array.isArray(value)) {
149+throw new Error(`expected ${label} to be an object`);
150+}
151+return value as Record<string, unknown>;
152+}
153+154+function expectRecordFields(
155+value: unknown,
156+expected: Record<string, unknown>,
157+label: string,
158+): Record<string, unknown> {
159+const record = requireRecord(value, label);
160+for (const [key, expectedValue] of Object.entries(expected)) {
161+expect(record[key], `${label}.${key}`).toEqual(expectedValue);
162+}
163+return record;
164+}
165+166+async function expectGatewayRequestError(
167+promise: Promise<unknown>,
168+expected: Record<string, unknown>,
169+): Promise<void> {
170+let rejected: unknown;
171+try {
172+await promise;
173+} catch (error) {
174+rejected = error;
175+}
176+const error = expectRecordFields(rejected, expected, "gateway request error");
177+expectRecordFields(error.details, { method: "chat.history" }, "gateway request error details");
178+}
179+147180function createClientWithIdentity(
148181deviceId: string,
149182onClose: (code: number, reason: string) => void,
@@ -164,12 +197,8 @@ function expectSecurityConnectError(
164197onConnectError: ReturnType<typeof vi.fn>,
165198params?: { expectTailscaleHint?: boolean },
166199) {
167-expect(onConnectError).toHaveBeenCalledWith(
168-expect.objectContaining({
169-message: expect.stringContaining("SECURITY ERROR"),
170-}),
171-);
172200const error = onConnectError.mock.calls[0]?.[0] as Error;
201+expect(error.message).toContain("SECURITY ERROR");
173202expect(error.message).toContain("openclaw doctor --fix");
174203if (params?.expectTailscaleHint) {
175204expect(error.message).toContain("Tailscale Serve/Funnel");
@@ -271,12 +300,14 @@ describe("GatewayClient security checks", () => {
271300272301expect(onConnectError).not.toHaveBeenCalled();
273302expect(wsInstances.length).toBe(1);
274-expect(getLatestWs().options).not.toMatchObject({ agent: expect.any(Object) });
275-expect((global as Record<string, unknown>)["GLOBAL_AGENT"]).toEqual(
276-expect.objectContaining({
303+expect(requireRecord(getLatestWs().options, "websocket options").agent).toBeUndefined();
304+expectRecordFields(
305+(global as Record<string, unknown>)["GLOBAL_AGENT"],
306+{
277307HTTP_PROXY: "http://127.0.0.1:3128",
278308HTTPS_PROXY: "http://127.0.0.1:3128",
279-}),
309+},
310+"global agent",
280311);
281312client.stop();
282313});
@@ -299,7 +330,7 @@ describe("GatewayClient security checks", () => {
299330300331expect(onConnectError).not.toHaveBeenCalled();
301332expect(wsInstances.length).toBe(1);
302-expect(getLatestWs().options).not.toMatchObject({ agent: expect.any(Object) });
333+expect(requireRecord(getLatestWs().options, "websocket options").agent).toBeUndefined();
303334} finally {
304335client.stop();
305336await stopProxy(handle);
@@ -420,12 +451,11 @@ describe("GatewayClient request errors", () => {
420451}),
421452);
422453423-await expect(requestPromise).rejects.toMatchObject({
454+await expectGatewayRequestError(requestPromise, {
424455name: "GatewayClientRequestError",
425456gatewayCode: "UNAVAILABLE",
426457retryable: true,
427458retryAfterMs: 250,
428-details: { method: "chat.history" },
429459});
430460431461client.stop();
@@ -711,6 +741,10 @@ describe("GatewayClient connect auth payload", () => {
711741return parseConnectRequest(ws).params?.auth ?? {};
712742}
713743744+function expectConnectAuthFields(ws: MockWebSocket, expected: Record<string, unknown>): void {
745+expectRecordFields(connectFrameFrom(ws), expected, "connect auth");
746+}
747+714748function connectScopesFrom(ws: MockWebSocket) {
715749return parseConnectRequest(ws).params?.scopes ?? [];
716750}
@@ -823,9 +857,7 @@ describe("GatewayClient connect auth payload", () => {
823857ws.emitOpen();
824858emitConnectChallenge(ws);
825859826-expect(connectFrameFrom(ws)).toMatchObject({
827-token: "shared-token",
828-});
860+expectConnectAuthFields(ws, { token: "shared-token" });
829861expect(connectFrameFrom(ws).deviceToken).toBeUndefined();
830862client.stop();
831863});
@@ -838,9 +870,7 @@ describe("GatewayClient connect auth payload", () => {
838870839871const { ws, connect } = startClientWithEarlyChallenge({ client });
840872841-expect(connectFrameFrom(ws)).toMatchObject({
842-token: "shared-token",
843-});
873+expectConnectAuthFields(ws, { token: "shared-token" });
844874emitHelloOk(ws, connect.id);
845875client.stop();
846876});
@@ -857,11 +887,10 @@ describe("GatewayClient connect auth payload", () => {
857887ws.autoCloseOnClose = false;
858888client.stop();
859889860-await vi.waitFor(() =>
861-expect(onConnectError).toHaveBeenCalledWith(
862-expect.objectContaining({ message: "gateway client stopped" }),
863-),
864-);
890+await vi.waitFor(() => {
891+const error = onConnectError.mock.calls[0]?.[0] as Error | undefined;
892+expect(error?.message).toBe("gateway client stopped");
893+});
865894expect(logDebugMock).toHaveBeenCalledWith(
866895"gateway connect failed: Error: gateway client stopped",
867896);
@@ -883,9 +912,7 @@ describe("GatewayClient connect auth payload", () => {
883912ws.emitOpen();
884913emitConnectChallenge(ws);
885914886-expect(connectFrameFrom(ws)).toMatchObject({
887-password: "shared-password", // pragma: allowlist secret
888-});
915+expectConnectAuthFields(ws, { password: "shared-password" }); // pragma: allowlist secret
889916expect(connectFrameFrom(ws).token).toBeUndefined();
890917expect(connectFrameFrom(ws).deviceToken).toBeUndefined();
891918client.stop();
@@ -903,9 +930,7 @@ describe("GatewayClient connect auth payload", () => {
903930ws.emitOpen();
904931emitConnectChallenge(ws);
905932906-expect(connectFrameFrom(ws)).toMatchObject({
907-password: "shared-password", // pragma: allowlist secret
908-});
933+expectConnectAuthFields(ws, { password: "shared-password" }); // pragma: allowlist secret
909934expect(connectFrameFrom(ws).bootstrapToken).toBeUndefined();
910935expect(connectFrameFrom(ws).token).toBeUndefined();
911936client.stop();
@@ -925,7 +950,7 @@ describe("GatewayClient connect auth payload", () => {
925950ws.emitOpen();
926951emitConnectChallenge(ws);
927952928-expect(connectFrameFrom(ws)).toMatchObject({
953+expectConnectAuthFields(ws, {
929954token: "stored-device-token",
930955deviceToken: "stored-device-token",
931956});
@@ -948,7 +973,7 @@ describe("GatewayClient connect auth payload", () => {
948973ws.emitOpen();
949974emitConnectChallenge(ws);
950975951-expect(connectFrameFrom(ws)).toMatchObject({
976+expectConnectAuthFields(ws, {
952977token: "stored-device-token",
953978deviceToken: "stored-device-token",
954979});
@@ -975,14 +1000,16 @@ describe("GatewayClient connect auth payload", () => {
9751000ws.emitOpen();
9761001emitConnectChallenge(ws);
9771002978-expect(loadDeviceAuthTokenMock).toHaveBeenCalledWith(
979-expect.objectContaining({
980- deviceId: expect.any(String),
1003+const loadTokenParams = expectRecordFields(
1004+loadDeviceAuthTokenMock.mock.calls[0]?.[0],
1005+{
9811006role: "operator",
9821007 env,
983-}),
1008+},
1009+"load device token params",
9841010);
985-expect(connectFrameFrom(ws)).toMatchObject({
1011+expect(loadTokenParams.deviceId).toBeTypeOf("string");
1012+expectConnectAuthFields(ws, {
9861013token: "stored-device-token",
9871014deviceToken: "stored-device-token",
9881015});
@@ -1001,9 +1028,7 @@ describe("GatewayClient connect auth payload", () => {
10011028ws.emitOpen();
10021029emitConnectChallenge(ws);
100310301004-expect(connectFrameFrom(ws)).toMatchObject({
1005-bootstrapToken: "bootstrap-token",
1006-});
1031+expectConnectAuthFields(ws, { bootstrapToken: "bootstrap-token" });
10071032expect(connectFrameFrom(ws).token).toBeUndefined();
10081033expect(connectFrameFrom(ws).deviceToken).toBeUndefined();
10091034client.stop();
@@ -1025,7 +1050,7 @@ describe("GatewayClient connect auth payload", () => {
10251050ws.emitOpen();
10261051emitConnectChallenge(ws);
102710521028-expect(connectFrameFrom(ws)).toMatchObject({
1053+expectConnectAuthFields(ws, {
10291054token: "explicit-device-token",
10301055deviceToken: "explicit-device-token",
10311056});
@@ -1048,7 +1073,7 @@ describe("GatewayClient connect auth payload", () => {
10481073ws.emitOpen();
10491074emitConnectChallenge(ws);
105010751051-expect(connectFrameFrom(ws)).toMatchObject({
1076+expectConnectAuthFields(ws, {
10521077token: "stored-device-token",
10531078deviceToken: "stored-device-token",
10541079});
@@ -1075,10 +1100,14 @@ describe("GatewayClient connect auth payload", () => {
10751100connectId: firstConnect.id,
10761101failureDetails: { code: "AUTH_TOKEN_MISMATCH", canRetryWithDeviceToken: true },
10771102});
1078-expect(retriedAuth).toMatchObject({
1079-token: "shared-token",
1080-deviceToken: "stored-device-token",
1081-});
1103+expectRecordFields(
1104+retriedAuth,
1105+{
1106+token: "shared-token",
1107+deviceToken: "stored-device-token",
1108+},
1109+"retried connect auth",
1110+);
10821111const ws = getLatestWs();
10831112expect(connectScopesFrom(ws)).toEqual(["operator.read"]);
10841113client.stop();
@@ -1097,10 +1126,14 @@ describe("GatewayClient connect auth payload", () => {
10971126connectId: firstConnect.id,
10981127failureDetails: { code: "AUTH_UNAUTHORIZED", recommendedNextStep: "retry_with_device_token" },
10991128});
1100-expect(retriedAuth).toMatchObject({
1101-token: "shared-token",
1102-deviceToken: "stored-device-token",
1103-});
1129+expectRecordFields(
1130+retriedAuth,
1131+{
1132+token: "shared-token",
1133+deviceToken: "stored-device-token",
1134+},
1135+"retried connect auth",
1136+);
11041137client.stop();
11051138});
11061139@@ -1145,10 +1178,12 @@ describe("GatewayClient connect auth payload", () => {
11451178connectId: firstConnect.id,
11461179failureDetails: { code: "AUTH_DEVICE_TOKEN_MISMATCH" },
11471180});
1148-expect(clearDeviceAuthTokenMock).toHaveBeenCalledWith({
1149-deviceId: expect.any(String),
1150-role: "operator",
1151-});
1181+const clearTokenParams = expectRecordFields(
1182+clearDeviceAuthTokenMock.mock.calls[0]?.[0],
1183+{ role: "operator" },
1184+"clear device token params",
1185+);
1186+expect(clearTokenParams.deviceId).toBeTypeOf("string");
11521187expect(onReconnectPaused).toHaveBeenCalledWith({
11531188code: 1008,
11541189reason: "connect failed",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。