




















@@ -1,7 +1,41 @@
11import { afterEach, describe, expect, it, vi } from "vitest";
2+import { GatewayTransportError } from "../gateway/call.js";
23import { runRegisteredCli } from "../test-utils/command-runner.js";
34import { formatLogTimestamp, registerLogsCli } from "./logs-cli.js";
456+const { MockGatewayTransportError } = vi.hoisted(() => ({
7+MockGatewayTransportError: class extends Error {
8+readonly kind: string;
9+readonly connectionDetails: unknown;
10+readonly code?: number;
11+readonly reason?: string;
12+readonly timeoutMs?: number;
13+14+constructor(params: {
15+kind: string;
16+message: string;
17+connectionDetails: unknown;
18+code?: number;
19+reason?: string;
20+timeoutMs?: number;
21+}) {
22+super(params.message);
23+this.name = "GatewayTransportError";
24+this.kind = params.kind;
25+this.connectionDetails = params.connectionDetails;
26+if (params.code !== undefined) {
27+this.code = params.code;
28+}
29+if (params.reason !== undefined) {
30+this.reason = params.reason;
31+}
32+if (params.timeoutMs !== undefined) {
33+this.timeoutMs = params.timeoutMs;
34+}
35+}
36+},
37+}));
38+539const callGatewayFromCli = vi.fn();
640const readConfiguredLogTail = vi.fn();
741const buildGatewayConnectionDetails = vi.fn(
@@ -18,9 +52,11 @@ const buildGatewayConnectionDetails = vi.fn(
1852);
19532054vi.mock("../gateway/call.js", () => ({
55+GatewayTransportError: MockGatewayTransportError,
2156buildGatewayConnectionDetails: (
2257 ...args: Parameters<typeof import("../gateway/call.js").buildGatewayConnectionDetails>
2358) => buildGatewayConnectionDetails(...args),
59+isGatewayTransportError: (value: unknown) => value instanceof MockGatewayTransportError,
2460}));
25612662vi.mock("../logging/log-tail.js", () => ({
@@ -155,7 +191,7 @@ describe("logs cli", () => {
155191maxBytes: 250_000,
156192});
157193expect(stdoutWrites.join("")).toContain("local fallback line");
158-expect(stderrWrites.join("")).toContain("reading local log file instead");
194+expect(stderrWrites.join("")).toContain("Local Gateway RPC unavailable");
159195});
160196161197it("falls back to the local log file on loopback scope-upgrade errors", async () => {
@@ -178,12 +214,22 @@ describe("logs cli", () => {
178214179215expect(readConfiguredLogTail).toHaveBeenCalledTimes(1);
180216expect(stdoutWrites.join("")).toContain("local fallback line");
181-expect(stderrWrites.join("")).toContain("reading local log file instead");
217+expect(stderrWrites.join("")).toContain("Local Gateway RPC unavailable");
182218});
183219184220it("falls back to the configured Gateway file log on loopback gateway close errors", async () => {
185221callGatewayFromCli.mockRejectedValueOnce(
186-new Error("gateway closed (1000 normal closure): no close reason"),
222+new GatewayTransportError({
223+kind: "closed",
224+code: 1000,
225+reason: "no close reason",
226+connectionDetails: {
227+url: "ws://127.0.0.1:18789",
228+urlSource: "local loopback",
229+message: "",
230+},
231+message: "gateway closed (1000 normal closure): no close reason",
232+}),
187233);
188234readConfiguredLogTail.mockResolvedValueOnce({
189235file: "/tmp/openclaw.log",
@@ -204,9 +250,40 @@ describe("logs cli", () => {
204250expect(stderrWrites.join("")).toContain("Local Gateway RPC unavailable");
205251});
206252253+it("falls back to the configured Gateway file log on post-handshake plain close errors", async () => {
254+callGatewayFromCli.mockRejectedValueOnce(new Error("gateway closed (1006): abnormal closure"));
255+readConfiguredLogTail.mockResolvedValueOnce({
256+file: "/tmp/openclaw.log",
257+cursor: 5,
258+size: 5,
259+lines: ["local fallback line"],
260+truncated: false,
261+reset: false,
262+});
263+264+const stdoutWrites = captureStdoutWrites();
265+const stderrWrites = captureStderrWrites();
266+267+await runLogsCli(["logs"]);
268+269+expect(readConfiguredLogTail).toHaveBeenCalledTimes(1);
270+expect(stdoutWrites.join("")).toContain("local fallback line");
271+expect(stderrWrites.join("")).toContain("Local Gateway RPC unavailable");
272+});
273+207274it("does not use local fallback for explicit Gateway URLs", async () => {
208275callGatewayFromCli.mockRejectedValueOnce(
209-new Error("gateway closed (1000 normal closure): no close reason"),
276+new GatewayTransportError({
277+kind: "closed",
278+code: 1000,
279+reason: "no close reason",
280+connectionDetails: {
281+url: "ws://127.0.0.1:18789",
282+urlSource: "local loopback",
283+message: "",
284+},
285+message: "gateway closed (1000 normal closure): no close reason",
286+}),
210287);
211288212289const stdoutWrites = captureStdoutWrites();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。