diff --git a/src/lib/messaging-bridge-health.test.ts b/src/lib/messaging-bridge-health.test.ts new file mode 100644 index 0000000000..ffbaa0e193 --- /dev/null +++ b/src/lib/messaging-bridge-health.test.ts @@ -0,0 +1,134 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { describe, it, expect, vi, beforeEach } from "vitest"; + +vi.mock("./resolve-openshell.js", () => ({ + resolveOpenshell: vi.fn(() => "/usr/local/bin/openshell"), +})); + +vi.mock("node:child_process", async (importOriginal) => { + const actual = await importOriginal(); + return { ...actual, spawnSync: vi.fn() }; +}); + +import { checkMessagingBridgeHealth } from "./messaging-bridge-health.js"; +import { spawnSync } from "node:child_process"; +import { resolveOpenshell } from "./resolve-openshell.js"; + +const spawnSyncMock = vi.mocked(spawnSync); +const resolveOpenshellMock = vi.mocked(resolveOpenshell); + +type SpawnResult = ReturnType; +function mockSpawn(overrides: Partial = {}): void { + spawnSyncMock.mockReturnValue({ + pid: 0, + output: [], + stdout: "", + stderr: "", + status: 0, + signal: null, + ...overrides, + } as unknown as SpawnResult); +} + +beforeEach(() => { + vi.clearAllMocks(); + resolveOpenshellMock.mockReturnValue("/usr/local/bin/openshell"); +}); + +describe("checkMessagingBridgeHealth", () => { + it("returns empty and does not spawn when channels does not include telegram", () => { + const result = checkMessagingBridgeHealth("alpha", ["discord", "slack"]); + expect(result).toEqual([]); + expect(spawnSyncMock).not.toHaveBeenCalled(); + }); + + it("returns empty and does not spawn when channels is null/undefined", () => { + expect(checkMessagingBridgeHealth("alpha", null)).toEqual([]); + expect(checkMessagingBridgeHealth("alpha", undefined)).toEqual([]); + expect(spawnSyncMock).not.toHaveBeenCalled(); + }); + + it("returns empty when openshell binary cannot be resolved", () => { + resolveOpenshellMock.mockReturnValue(null); + const result = checkMessagingBridgeHealth("alpha", ["telegram"]); + expect(result).toEqual([]); + expect(spawnSyncMock).not.toHaveBeenCalled(); + }); + + it("returns a conflict entry when gateway log reports N conflicts", () => { + mockSpawn({ stdout: "32\n" }); + expect(checkMessagingBridgeHealth("alpha", ["telegram"])).toEqual([ + { channel: "telegram", conflicts: 32 }, + ]); + }); + + it("returns empty when the conflict count is zero", () => { + mockSpawn({ stdout: "0\n" }); + expect(checkMessagingBridgeHealth("alpha", ["telegram"])).toEqual([]); + }); + + it("returns empty when the count is non-numeric", () => { + mockSpawn({ stdout: "\n" }); + expect(checkMessagingBridgeHealth("alpha", ["telegram"])).toEqual([]); + }); + + it("returns empty when spawnSync throws", () => { + spawnSyncMock.mockImplementation(() => { + throw new Error("spawn EPIPE"); + }); + expect(checkMessagingBridgeHealth("alpha", ["telegram"])).toEqual([]); + }); + + it("returns empty when spawnSync reports a non-zero exit status (exec failed)", () => { + mockSpawn({ stderr: "/bin/bash: alpha: command not found\n", status: 127 }); + expect(checkMessagingBridgeHealth("alpha", ["telegram"])).toEqual([]); + }); + + it("returns empty when spawnSync returns an error object (e.g. timeout)", () => { + mockSpawn({ + stdout: null as unknown as string, + stderr: null as unknown as string, + status: null, + signal: "SIGTERM", + error: new Error("spawnSync timed out"), + }); + expect(checkMessagingBridgeHealth("alpha", ["telegram"])).toEqual([]); + }); + + // Regression for #2018: `openshell sandbox exec` requires the --name/-n + // flag. Passing the sandbox name as a positional causes the name to be + // interpreted as the first word of the command and fails with exit 127. + describe("argv shape (#2018 regression)", () => { + beforeEach(() => mockSpawn({ stdout: "5\n" })); + + it("passes the sandbox name via --name/-n, not as a positional", () => { + checkMessagingBridgeHealth("tele-brev", ["telegram"]); + + expect(spawnSyncMock).toHaveBeenCalledTimes(1); + const [binary, args] = spawnSyncMock.mock.calls[0]; + expect(binary).toBe("/usr/local/bin/openshell"); + + const argsArray = args as string[]; + const nameFlagIdx = argsArray.indexOf("-n"); + expect(nameFlagIdx).toBeGreaterThan(-1); + expect(argsArray[nameFlagIdx + 1]).toBe("tele-brev"); + + // Pre-fix, args were ["sandbox", "exec", "", "sh", "-c",