

























@@ -8,6 +8,7 @@ const mocks = vi.hoisted(() => ({
88buildGatewayConnectionDetailsWithResolvers: vi.fn(),
99resolveGatewayProbeTarget: vi.fn(),
1010probeGateway: vi.fn(),
11+callGateway: vi.fn(),
1112resolveGatewayProbeAuthResolution: vi.fn(),
1213pickGatewaySelfPresence: vi.fn(),
1314}));
@@ -24,6 +25,10 @@ vi.mock("../gateway/probe.js", () => ({
2425probeGateway: mocks.probeGateway,
2526}));
262728+vi.mock("../gateway/call.js", () => ({
29+callGateway: mocks.callGateway,
30+}));
31+2732vi.mock("./status.gateway-probe.js", () => ({
2833resolveGatewayProbeAuthResolution: mocks.resolveGatewayProbeAuthResolution,
2934}));
@@ -50,6 +55,7 @@ describe("resolveGatewayProbeSnapshot", () => {
5055warning: "warn",
5156});
5257mocks.pickGatewaySelfPresence.mockReturnValue({ host: "box" });
58+mocks.callGateway.mockRejectedValue(new Error("status rpc unavailable"));
5359});
54605561it("skips auth resolution and probe for missing remote urls by default", async () => {
@@ -178,6 +184,138 @@ describe("resolveGatewayProbeSnapshot", () => {
178184expect(result.gatewayReachable).toBe(true);
179185expect(result.gatewayProbe?.error).toBe("missing scope: operator.read; warn");
180186});
187+188+it("uses a bounded local status RPC fallback when the detail probe times out", async () => {
189+mocks.resolveGatewayProbeTarget.mockReturnValue({
190+mode: "local",
191+gatewayMode: "local",
192+remoteUrlMissing: false,
193+});
194+mocks.probeGateway.mockResolvedValue({
195+ok: false,
196+url: "ws://127.0.0.1:18789",
197+connectLatencyMs: null,
198+error: "timeout",
199+close: null,
200+auth: {
201+role: null,
202+scopes: [],
203+capability: "unknown",
204+},
205+health: null,
206+status: null,
207+presence: null,
208+configSnapshot: null,
209+});
210+mocks.callGateway.mockResolvedValue({ sessions: 1 });
211+212+const result = await resolveGatewayProbeSnapshot({
213+cfg: {},
214+opts: {
215+timeoutMs: 8000,
216+},
217+});
218+219+expect(mocks.callGateway).toHaveBeenCalledWith(
220+expect.objectContaining({
221+config: {},
222+method: "status",
223+token: "tok",
224+password: "pw",
225+timeoutMs: 2000,
226+mode: "backend",
227+clientName: "gateway-client",
228+}),
229+);
230+expect(mocks.callGateway.mock.calls[0]?.[0]).not.toHaveProperty("deviceIdentity");
231+expect(result.gatewayReachable).toBe(true);
232+expect(result.gatewayProbe).toMatchObject({
233+ok: true,
234+error: "timeout",
235+status: { sessions: 1 },
236+auth: { capability: "read_only" },
237+});
238+expect(result.gatewayProbeAuthWarning).toBe("warn");
239+});
240+241+it("lets callGateway reuse paired-device auth for local status RPC fallback", async () => {
242+mocks.resolveGatewayProbeTarget.mockReturnValue({
243+mode: "local",
244+gatewayMode: "local",
245+remoteUrlMissing: false,
246+});
247+mocks.resolveGatewayProbeAuthResolution.mockResolvedValue({
248+auth: {},
249+warning: undefined,
250+});
251+mocks.probeGateway.mockResolvedValue({
252+ok: false,
253+url: "ws://127.0.0.1:18789",
254+connectLatencyMs: null,
255+error: "timeout",
256+close: null,
257+auth: {
258+role: "operator",
259+scopes: ["operator.read"],
260+capability: "read_only",
261+},
262+health: null,
263+status: null,
264+presence: null,
265+configSnapshot: null,
266+});
267+mocks.callGateway.mockResolvedValue({ sessions: 1 });
268+269+const result = await resolveGatewayProbeSnapshot({
270+cfg: {},
271+opts: {},
272+});
273+274+expect(mocks.callGateway).toHaveBeenCalledWith(
275+expect.objectContaining({
276+config: {},
277+method: "status",
278+token: undefined,
279+password: undefined,
280+mode: "backend",
281+clientName: "gateway-client",
282+}),
283+);
284+expect(mocks.callGateway.mock.calls[0]?.[0]).not.toHaveProperty("deviceIdentity");
285+expect(result.gatewayReachable).toBe(true);
286+});
287+288+it("does not use the status RPC fallback for remote probe failures", async () => {
289+mocks.resolveGatewayProbeTarget.mockReturnValue({
290+mode: "remote",
291+gatewayMode: "remote",
292+remoteUrlMissing: false,
293+});
294+mocks.probeGateway.mockResolvedValue({
295+ok: false,
296+url: "wss://gateway.example/ws",
297+connectLatencyMs: null,
298+error: "timeout",
299+close: null,
300+auth: {
301+role: null,
302+scopes: [],
303+capability: "unknown",
304+},
305+health: null,
306+status: null,
307+presence: null,
308+configSnapshot: null,
309+});
310+311+const result = await resolveGatewayProbeSnapshot({
312+cfg: { gateway: { mode: "remote", remote: { url: "wss://gateway.example/ws" } } },
313+opts: {},
314+});
315+316+expect(mocks.callGateway).not.toHaveBeenCalled();
317+expect(result.gatewayReachable).toBe(false);
318+});
181319});
182320183321describe("resolveSharedMemoryStatusSnapshot", () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。