|
1 | 1 | // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
2 | 2 | // SPDX-License-Identifier: Apache-2.0 |
3 | 3 |
|
4 | | -import { beforeEach, describe, expect, it, vi } from "vitest"; |
| 4 | +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; |
5 | 5 | import type { OpenClawPluginApi } from "./index.js"; |
6 | 6 |
|
7 | 7 | vi.mock("node:fs", async (importOriginal) => { |
@@ -32,6 +32,17 @@ const mockedReadFileSync = vi.mocked(readFileSync); |
32 | 32 | const mockedLoadOnboardConfig = vi.mocked(loadOnboardConfig); |
33 | 33 | const originalReadFileSync = (await vi.importActual<typeof import("node:fs")>("node:fs")) |
34 | 34 | .readFileSync; |
| 35 | +let stderrWrite: ReturnType<typeof vi.spyOn>; |
| 36 | + |
| 37 | +function mockStderrWrite(): void { |
| 38 | + stderrWrite = vi |
| 39 | + .spyOn(process.stderr, "write") |
| 40 | + .mockImplementation((() => true) as typeof process.stderr.write); |
| 41 | +} |
| 42 | + |
| 43 | +function stderrOutput(): string { |
| 44 | + return stderrWrite.mock.calls.map(([chunk]) => String(chunk)).join(""); |
| 45 | +} |
35 | 46 |
|
36 | 47 | function mockMissingOpenClawConfig(): void { |
37 | 48 | mockedReadFileSync.mockReset(); |
@@ -64,13 +75,18 @@ function createMockApi(): OpenClawPluginApi { |
64 | 75 | }; |
65 | 76 | } |
66 | 77 |
|
67 | | -describe("plugin registration", () => { |
68 | | - beforeEach(() => { |
69 | | - vi.clearAllMocks(); |
70 | | - mockMissingOpenClawConfig(); |
71 | | - mockedLoadOnboardConfig.mockReturnValue(null); |
72 | | - }); |
| 78 | +beforeEach(() => { |
| 79 | + vi.clearAllMocks(); |
| 80 | + mockStderrWrite(); |
| 81 | + mockMissingOpenClawConfig(); |
| 82 | + mockedLoadOnboardConfig.mockReturnValue(null); |
| 83 | +}); |
| 84 | + |
| 85 | +afterEach(() => { |
| 86 | + vi.restoreAllMocks(); |
| 87 | +}); |
73 | 88 |
|
| 89 | +describe("plugin registration", () => { |
74 | 90 | it("registers a slash command", () => { |
75 | 91 | const api = createMockApi(); |
76 | 92 | register(api); |
@@ -140,8 +156,15 @@ describe("plugin registration", () => { |
140 | 156 | expect(providerArg.models?.chat).toEqual([ |
141 | 157 | expect.objectContaining({ id: "inference/nvidia/live-model", label: "nvidia/live-model" }), |
142 | 158 | ]); |
143 | | - const logLines = vi.mocked(api.logger.info).mock.calls.map(([message]) => message); |
144 | | - expect(logLines.some((line) => line.includes("Model: nvidia/live-model"))).toBe(true); |
| 159 | + expect(stderrOutput()).toContain("Model: nvidia/live-model"); |
| 160 | + }); |
| 161 | + |
| 162 | + it("writes the registration banner to stderr instead of plugin info logs", () => { |
| 163 | + const api = createMockApi(); |
| 164 | + register(api); |
| 165 | + |
| 166 | + expect(stderrOutput()).toContain("NemoClaw registered"); |
| 167 | + expect(api.logger.info).not.toHaveBeenCalled(); |
145 | 168 | }); |
146 | 169 |
|
147 | 170 | it("falls back to onboard config when openclaw.json has no primary model", () => { |
@@ -178,22 +201,14 @@ describe("plugin registration", () => { |
178 | 201 | expect.objectContaining({ id: "nvidia/nemotron-3-nano-30b-a3b" }), |
179 | 202 | ]); |
180 | 203 |
|
181 | | - const logLines = vi.mocked(api.logger.info).mock.calls.map(([message]) => message); |
182 | | - expect(logLines.some((line) => line.includes("Endpoint: build.nvidia.com"))).toBe(true); |
183 | | - expect(logLines.some((line) => line.includes("Provider: NVIDIA Endpoints"))).toBe(true); |
184 | | - expect( |
185 | | - logLines.some((line) => line.includes("Model: nvidia/nemotron-3-super-120b-a12b")), |
186 | | - ).toBe(true); |
| 204 | + const stderr = stderrOutput(); |
| 205 | + expect(stderr).toContain("Endpoint: build.nvidia.com"); |
| 206 | + expect(stderr).toContain("Provider: NVIDIA Endpoints"); |
| 207 | + expect(stderr).toContain("Model: nvidia/nemotron-3-super-120b-a12b"); |
187 | 208 | }); |
188 | 209 | }); |
189 | 210 |
|
190 | 211 | describe("before_tool_call secret scanner hook (#1233)", () => { |
191 | | - beforeEach(() => { |
192 | | - vi.clearAllMocks(); |
193 | | - mockMissingOpenClawConfig(); |
194 | | - mockedLoadOnboardConfig.mockReturnValue(null); |
195 | | - }); |
196 | | - |
197 | 212 | function getHookHandler(api: OpenClawPluginApi) { |
198 | 213 | register(api); |
199 | 214 | const onCalls = vi.mocked(api.on).mock.calls; |
|
0 commit comments