
























@@ -25,6 +25,7 @@ vi.mock("../logging/subsystem.js", () => ({
2525}));
26262727let createExecApprovalChannelRuntime: typeof import("./exec-approval-channel-runtime.js").createExecApprovalChannelRuntime;
28+let ExecApprovalChannelRuntimeTerminalStartError: typeof import("./exec-approval-channel-runtime.js").ExecApprovalChannelRuntimeTerminalStartError;
28292930function createDeferred<T>() {
3031let resolve!: (value: T | PromiseLike<T>) => void;
@@ -116,7 +117,8 @@ afterEach(() => {
116117});
117118118119beforeAll(async () => {
119-({ createExecApprovalChannelRuntime } = await import("./exec-approval-channel-runtime.js"));
120+({ createExecApprovalChannelRuntime, ExecApprovalChannelRuntimeTerminalStartError } =
121+await import("./exec-approval-channel-runtime.js"));
120122});
121123122124describe("createExecApprovalChannelRuntime", () => {
@@ -287,6 +289,84 @@ describe("createExecApprovalChannelRuntime", () => {
287289expect(mockGatewayClientStarts).toHaveBeenCalledTimes(1);
288290});
289291292+it("waits through retryable connect auth errors until hello succeeds", async () => {
293+const authError = Object.assign(new Error("gateway token mismatch"), {
294+details: {
295+code: "AUTH_TOKEN_MISMATCH",
296+canRetryWithDeviceToken: true,
297+},
298+});
299+mockCreateOperatorApprovalsGatewayClient.mockImplementationOnce(async (params) => ({
300+start: () => {
301+mockGatewayClientStarts();
302+params.onConnectError?.(authError);
303+queueMicrotask(() => {
304+params.onHelloOk?.({ type: "hello-ok" } as never);
305+});
306+},
307+stop: mockGatewayClientStops,
308+request: mockGatewayClientRequests,
309+}));
310+const runtime = createExecApprovalChannelRuntime({
311+label: "test/exec-approvals",
312+clientDisplayName: "Test Exec Approvals",
313+cfg: {} as never,
314+isConfigured: () => true,
315+shouldHandle: () => true,
316+deliverRequested: async () => [],
317+finalizeResolved: async () => undefined,
318+});
319+320+await expect(runtime.start()).resolves.toBeUndefined();
321+322+expect(mockGatewayClientStarts).toHaveBeenCalledTimes(1);
323+expect(loggerMocks.error).toHaveBeenCalledWith("connect error: gateway token mismatch");
324+});
325+326+it("surfaces reconnect pauses as terminal startup errors", async () => {
327+const authError = Object.assign(new Error("pairing required"), {
328+details: {
329+code: "PAIRING_REQUIRED",
330+},
331+});
332+mockCreateOperatorApprovalsGatewayClient.mockImplementationOnce(async (params) => ({
333+start: () => {
334+mockGatewayClientStarts();
335+params.onConnectError?.(authError);
336+params.onReconnectPaused?.({
337+code: 1008,
338+reason: "pairing required",
339+detailCode: "PAIRING_REQUIRED",
340+});
341+params.onClose?.(1008, "pairing required");
342+},
343+stop: mockGatewayClientStops,
344+request: mockGatewayClientRequests,
345+}));
346+const runtime = createExecApprovalChannelRuntime({
347+label: "test/exec-approvals",
348+clientDisplayName: "Test Exec Approvals",
349+cfg: {} as never,
350+isConfigured: () => true,
351+shouldHandle: () => true,
352+deliverRequested: async () => [],
353+finalizeResolved: async () => undefined,
354+});
355+356+let caught: unknown;
357+await runtime.start().catch((error) => {
358+caught = error;
359+});
360+361+expect(caught).toBeInstanceOf(ExecApprovalChannelRuntimeTerminalStartError);
362+expect(caught).toMatchObject({
363+detailCode: "PAIRING_REQUIRED",
364+});
365+366+expect(mockGatewayClientStarts).toHaveBeenCalledTimes(1);
367+expect(mockGatewayClientStops).toHaveBeenCalledTimes(1);
368+});
369+290370it("does not leave a gateway client running when stop wins the startup race", async () => {
291371const pendingClient = createDeferred<GatewayClient>();
292372mockCreateOperatorApprovalsGatewayClient.mockReturnValueOnce(pendingClient.promise);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。