























@@ -38,6 +38,9 @@ const mocks = vi.hoisted(() => ({
3838resolveIsNixMode: vi.fn(() => false),
3939findExtraGatewayServices: vi.fn().mockResolvedValue([]),
4040renderGatewayServiceCleanupHints: vi.fn().mockReturnValue([]),
41+needsNodeRuntimeMigration: vi.fn(() => false),
42+renderSystemNodeWarning: vi.fn().mockReturnValue(undefined),
43+resolveSystemNodeInfo: vi.fn().mockResolvedValue(null),
4144isSystemdUnitActive: vi.fn().mockResolvedValue(false),
4245uninstallLegacySystemdUnits: vi.fn().mockResolvedValue([]),
4346note: vi.fn(),
@@ -62,13 +65,13 @@ vi.mock("../daemon/inspect.js", () => ({
6265}));
63666467vi.mock("../daemon/runtime-paths.js", () => ({
65-renderSystemNodeWarning: vi.fn().mockReturnValue(undefined),
66-resolveSystemNodeInfo: vi.fn().mockResolvedValue(null),
68+renderSystemNodeWarning: mocks.renderSystemNodeWarning,
69+resolveSystemNodeInfo: mocks.resolveSystemNodeInfo,
6770}));
68716972vi.mock("../daemon/service-audit.js", () => ({
7073auditGatewayServiceConfig: mocks.auditGatewayServiceConfig,
71-needsNodeRuntimeMigration: vi.fn(() => false),
74+needsNodeRuntimeMigration: mocks.needsNodeRuntimeMigration,
7275readEmbeddedGatewayToken: readEmbeddedGatewayTokenForTest,
7376SERVICE_AUDIT_CODES: {
7477gatewayCommandMissing: testServiceAuditCodes.gatewayCommandMissing,
@@ -246,6 +249,9 @@ describe("maybeRepairGatewayServiceConfig", () => {
246249vi.clearAllMocks();
247250fsMocks.realpath.mockImplementation(async (value: string) => value);
248251mocks.resolveGatewayPort.mockReturnValue(18789);
252+mocks.needsNodeRuntimeMigration.mockReturnValue(false);
253+mocks.renderSystemNodeWarning.mockReturnValue(undefined);
254+mocks.resolveSystemNodeInfo.mockResolvedValue(null);
249255mocks.isSystemdUnitActive.mockResolvedValue(false);
250256mocks.resolveGatewayAuthTokenForService.mockImplementation(async (cfg: OpenClawConfig, env) => {
251257const configToken =
@@ -303,6 +309,50 @@ describe("maybeRepairGatewayServiceConfig", () => {
303309expect(mocks.install).toHaveBeenCalledTimes(1);
304310});
305311312+it("does not duplicate gateway runtime warnings already emitted by the node install plan", async () => {
313+const nvmNode = "/home/orin/.nvm/versions/node/v22.22.2/bin/node";
314+mocks.readCommand.mockResolvedValue({
315+programArguments: [nvmNode, "/usr/local/bin/openclaw", "gateway", "--port", "18789"],
316+environment: {},
317+});
318+mocks.buildGatewayInstallPlan.mockImplementation(async ({ warn }) => {
319+warn?.(
320+"System Node 20.20.2 at /usr/bin/node is below the required Node 22.16+. Using /home/orin/.nvm/versions/node/v22.22.2/bin/node for the daemon.",
321+"Gateway runtime",
322+);
323+return {
324+programArguments: [nvmNode, "/usr/local/bin/openclaw", "gateway", "--port", "18789"],
325+workingDirectory: "/tmp",
326+environment: {},
327+};
328+});
329+mocks.auditGatewayServiceConfig.mockResolvedValue({
330+ok: true,
331+issues: [{ code: "runtime", message: "runtime migration", level: "recommended" }],
332+});
333+mocks.needsNodeRuntimeMigration.mockReturnValue(true);
334+mocks.resolveSystemNodeInfo.mockResolvedValue({
335+path: "/usr/bin/node",
336+version: "20.20.2",
337+supported: false,
338+});
339+mocks.renderSystemNodeWarning.mockReturnValue("duplicate doctor runtime warning");
340+341+await runRepair({ gateway: {} });
342+343+const runtimeNotes = mocks.note.mock.calls.filter(([, title]) => title === "Gateway runtime");
344+const runtimeMessages = runtimeNotes.map(([message]) => message);
345+expect(runtimeMessages).not.toContain("duplicate doctor runtime warning");
346+expect(runtimeMessages).not.toEqual(
347+expect.arrayContaining([expect.stringContaining("not found")]),
348+);
349+expect(runtimeMessages).toEqual(
350+expect.arrayContaining([
351+expect.stringContaining("Using /home/orin/.nvm/versions/node/v22.22.2/bin/node"),
352+]),
353+);
354+});
355+306356it("passes planned managed env keys into service audit for legacy inline secret detection", async () => {
307357mocks.readCommand.mockResolvedValue({
308358programArguments: gatewayProgramArguments,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。