@bryan-thompson/inspector-assessment-cli 1.26.5 → 1.26.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Assessment Executor Unit Tests
3
+ *
4
+ * Tests for runFullAssessment() orchestration logic.
5
+ */
6
+ import { jest, describe, it, expect, beforeEach, afterEach, } from "@jest/globals";
7
+ // Mock all dependencies with explicit any types for flexibility
8
+ const mockLoadServerConfig = jest.fn();
9
+ const mockConnectToServer = jest.fn();
10
+ const mockLoadSourceFiles = jest.fn();
11
+ const mockCreateCallToolWrapper = jest.fn();
12
+ const mockBuildConfig = jest.fn();
13
+ // Mock client with typed functions
14
+ const mockClient = {
15
+ listTools: jest.fn(),
16
+ listResources: jest.fn(),
17
+ listPrompts: jest.fn(),
18
+ readResource: jest.fn(),
19
+ getPrompt: jest.fn(),
20
+ getServerVersion: jest.fn(),
21
+ getServerCapabilities: jest.fn(),
22
+ close: jest.fn(),
23
+ };
24
+ // Mock orchestrator
25
+ const mockRunFullAssessment = jest.fn();
26
+ const mockIsClaudeEnabled = jest.fn();
27
+ const MockAssessmentOrchestrator = jest.fn().mockImplementation(() => ({
28
+ runFullAssessment: mockRunFullAssessment,
29
+ isClaudeEnabled: mockIsClaudeEnabled,
30
+ }));
31
+ // Mock state manager
32
+ const mockStateExists = jest.fn();
33
+ const mockStateGetSummary = jest.fn();
34
+ const mockStateClear = jest.fn();
35
+ const MockAssessmentStateManager = jest.fn().mockImplementation(() => ({
36
+ exists: mockStateExists,
37
+ getSummary: mockStateGetSummary,
38
+ clear: mockStateClear,
39
+ }));
40
+ // Mock JSONL event emitters
41
+ const mockEmitServerConnected = jest.fn();
42
+ const mockEmitToolDiscovered = jest.fn();
43
+ const mockEmitToolsDiscoveryComplete = jest.fn();
44
+ const mockEmitAssessmentComplete = jest.fn();
45
+ const mockEmitModulesConfigured = jest.fn();
46
+ jest.unstable_mockModule("../../lib/assessment-runner/server-config.js", () => ({
47
+ loadServerConfig: mockLoadServerConfig,
48
+ }));
49
+ jest.unstable_mockModule("../../lib/assessment-runner/server-connection.js", () => ({
50
+ connectToServer: mockConnectToServer,
51
+ }));
52
+ jest.unstable_mockModule("../../lib/assessment-runner/source-loader.js", () => ({
53
+ loadSourceFiles: mockLoadSourceFiles,
54
+ }));
55
+ jest.unstable_mockModule("../../lib/assessment-runner/tool-wrapper.js", () => ({
56
+ createCallToolWrapper: mockCreateCallToolWrapper,
57
+ }));
58
+ jest.unstable_mockModule("../../lib/assessment-runner/config-builder.js", () => ({
59
+ buildConfig: mockBuildConfig,
60
+ }));
61
+ jest.unstable_mockModule("../../../../client/lib/services/assessment/AssessmentOrchestrator.js", () => ({
62
+ AssessmentOrchestrator: MockAssessmentOrchestrator,
63
+ AssessmentContext: {},
64
+ }));
65
+ jest.unstable_mockModule("../../assessmentState.js", () => ({
66
+ AssessmentStateManager: MockAssessmentStateManager,
67
+ }));
68
+ jest.unstable_mockModule("../../lib/jsonl-events.js", () => ({
69
+ emitServerConnected: mockEmitServerConnected,
70
+ emitToolDiscovered: mockEmitToolDiscovered,
71
+ emitToolsDiscoveryComplete: mockEmitToolsDiscoveryComplete,
72
+ emitAssessmentComplete: mockEmitAssessmentComplete,
73
+ emitTestBatch: jest.fn(),
74
+ emitVulnerabilityFound: jest.fn(),
75
+ emitAnnotationMissing: jest.fn(),
76
+ emitAnnotationMisaligned: jest.fn(),
77
+ emitAnnotationReviewRecommended: jest.fn(),
78
+ emitAnnotationAligned: jest.fn(),
79
+ emitModulesConfigured: mockEmitModulesConfigured,
80
+ }));
81
+ jest.unstable_mockModule("fs", () => ({
82
+ existsSync: jest.fn().mockReturnValue(false),
83
+ readFileSync: jest.fn(),
84
+ }));
85
+ jest.unstable_mockModule("../../../../client/lib/lib/assessmentTypes.js", () => ({
86
+ MCPDirectoryAssessment: {},
87
+ ProgressEvent: {},
88
+ }));
89
+ // Import after mocking
90
+ const { runFullAssessment } = await import("../../lib/assessment-runner/assessment-executor.js");
91
+ describe("runFullAssessment", () => {
92
+ const defaultOptions = {
93
+ serverName: "test-server",
94
+ jsonOnly: true, // Suppress console output in tests
95
+ };
96
+ beforeEach(() => {
97
+ jest.clearAllMocks();
98
+ // Setup default mock returns
99
+ mockLoadServerConfig.mockReturnValue({
100
+ transport: "stdio",
101
+ command: "node",
102
+ args: ["server.js"],
103
+ });
104
+ mockConnectToServer.mockResolvedValue(mockClient);
105
+ mockClient.listTools.mockResolvedValue({
106
+ tools: [
107
+ { name: "tool1", description: "First tool" },
108
+ { name: "tool2", description: "Second tool" },
109
+ ],
110
+ });
111
+ mockClient.listResources.mockResolvedValue({ resources: [] });
112
+ mockClient.listPrompts.mockResolvedValue({ prompts: [] });
113
+ mockClient.getServerVersion.mockReturnValue({
114
+ name: "test-server",
115
+ version: "1.0.0",
116
+ });
117
+ mockClient.getServerCapabilities.mockReturnValue({});
118
+ mockClient.close.mockResolvedValue(undefined);
119
+ mockLoadSourceFiles.mockReturnValue({});
120
+ mockCreateCallToolWrapper.mockReturnValue(jest.fn());
121
+ mockBuildConfig.mockReturnValue({
122
+ assessmentCategories: { functionality: true, security: true },
123
+ });
124
+ mockRunFullAssessment.mockResolvedValue({
125
+ overallStatus: "PASS",
126
+ totalTestsRun: 10,
127
+ executionTime: 5000,
128
+ });
129
+ mockIsClaudeEnabled.mockReturnValue(false);
130
+ mockStateExists.mockReturnValue(false);
131
+ });
132
+ afterEach(() => {
133
+ jest.restoreAllMocks();
134
+ });
135
+ describe("orchestration flow", () => {
136
+ it("should load server config", async () => {
137
+ await runFullAssessment(defaultOptions);
138
+ expect(mockLoadServerConfig).toHaveBeenCalledWith("test-server", undefined);
139
+ });
140
+ it("should connect to server", async () => {
141
+ await runFullAssessment(defaultOptions);
142
+ expect(mockConnectToServer).toHaveBeenCalled();
143
+ expect(mockEmitServerConnected).toHaveBeenCalledWith("test-server", "stdio");
144
+ });
145
+ it("should discover tools via client.listTools()", async () => {
146
+ await runFullAssessment(defaultOptions);
147
+ expect(mockClient.listTools).toHaveBeenCalled();
148
+ expect(mockEmitToolDiscovered).toHaveBeenCalledTimes(2);
149
+ expect(mockEmitToolsDiscoveryComplete).toHaveBeenCalledWith(2);
150
+ });
151
+ it("should discover resources via client.listResources()", async () => {
152
+ mockClient.listResources.mockResolvedValue({
153
+ resources: [{ uri: "file://test.txt", name: "Test" }],
154
+ });
155
+ await runFullAssessment(defaultOptions);
156
+ expect(mockClient.listResources).toHaveBeenCalled();
157
+ });
158
+ it("should handle server with zero tools gracefully", async () => {
159
+ mockClient.listTools.mockResolvedValue({ tools: [] });
160
+ await runFullAssessment(defaultOptions);
161
+ expect(mockEmitToolsDiscoveryComplete).toHaveBeenCalledWith(0);
162
+ });
163
+ it("should handle resources not supported by server", async () => {
164
+ mockClient.listResources.mockRejectedValue(new Error("Resources not supported"));
165
+ // Should not throw
166
+ await expect(runFullAssessment(defaultOptions)).resolves.toBeDefined();
167
+ });
168
+ it("should handle prompts not supported by server", async () => {
169
+ mockClient.listPrompts.mockRejectedValue(new Error("Prompts not supported"));
170
+ // Should not throw
171
+ await expect(runFullAssessment(defaultOptions)).resolves.toBeDefined();
172
+ });
173
+ });
174
+ describe("configuration", () => {
175
+ it("should build config from options", async () => {
176
+ await runFullAssessment({
177
+ ...defaultOptions,
178
+ profile: "security",
179
+ });
180
+ expect(mockBuildConfig).toHaveBeenCalledWith(expect.objectContaining({ profile: "security" }));
181
+ });
182
+ it("should emit modules_configured event", async () => {
183
+ await runFullAssessment(defaultOptions);
184
+ expect(mockEmitModulesConfigured).toHaveBeenCalledWith(["functionality", "security"], [], "default");
185
+ });
186
+ it("should create AssessmentOrchestrator with config", async () => {
187
+ await runFullAssessment(defaultOptions);
188
+ expect(MockAssessmentOrchestrator).toHaveBeenCalledWith(expect.objectContaining({
189
+ assessmentCategories: { functionality: true, security: true },
190
+ }));
191
+ });
192
+ });
193
+ describe("source files", () => {
194
+ it("should load source files when sourceCodePath provided", async () => {
195
+ const fs = await import("fs");
196
+ fs.existsSync.mockReturnValue(true);
197
+ await runFullAssessment({
198
+ ...defaultOptions,
199
+ sourceCodePath: "/path/to/source",
200
+ });
201
+ expect(mockLoadSourceFiles).toHaveBeenCalledWith("/path/to/source");
202
+ });
203
+ it("should not load source files when path does not exist", async () => {
204
+ const fs = await import("fs");
205
+ fs.existsSync.mockReturnValue(false);
206
+ await runFullAssessment({
207
+ ...defaultOptions,
208
+ sourceCodePath: "/nonexistent",
209
+ });
210
+ expect(mockLoadSourceFiles).not.toHaveBeenCalled();
211
+ });
212
+ });
213
+ describe("cleanup", () => {
214
+ it("should close client connection on completion", async () => {
215
+ await runFullAssessment(defaultOptions);
216
+ expect(mockClient.close).toHaveBeenCalled();
217
+ });
218
+ it("should emit assessment complete event", async () => {
219
+ await runFullAssessment(defaultOptions);
220
+ expect(mockEmitAssessmentComplete).toHaveBeenCalledWith("PASS", 10, 5000, expect.stringContaining("inspector-full-assessment"));
221
+ });
222
+ });
223
+ describe("results", () => {
224
+ it("should return MCPDirectoryAssessment results", async () => {
225
+ const result = await runFullAssessment(defaultOptions);
226
+ expect(result).toEqual({
227
+ overallStatus: "PASS",
228
+ totalTestsRun: 10,
229
+ executionTime: 5000,
230
+ });
231
+ });
232
+ });
233
+ describe("state management", () => {
234
+ it("should check for existing state", async () => {
235
+ await runFullAssessment(defaultOptions);
236
+ expect(MockAssessmentStateManager).toHaveBeenCalledWith("test-server");
237
+ expect(mockStateExists).toHaveBeenCalled();
238
+ });
239
+ it("should clear state when --no-resume is set", async () => {
240
+ mockStateExists.mockReturnValue(true);
241
+ await runFullAssessment({
242
+ ...defaultOptions,
243
+ noResume: true,
244
+ });
245
+ expect(mockStateClear).toHaveBeenCalled();
246
+ });
247
+ });
248
+ });
@@ -0,0 +1,289 @@
1
+ /**
2
+ * Config Builder Unit Tests
3
+ *
4
+ * Tests for buildConfig() that transforms CLI options into AssessmentConfiguration.
5
+ */
6
+ import { jest, describe, it, expect, beforeEach, afterEach, } from "@jest/globals";
7
+ // Mock dependencies before importing
8
+ jest.unstable_mockModule("../../profiles.js", () => ({
9
+ getProfileModules: jest.fn(),
10
+ resolveModuleNames: jest.fn((names) => names),
11
+ modulesToLegacyConfig: jest.fn(),
12
+ }));
13
+ jest.unstable_mockModule("../../../../client/lib/lib/assessmentTypes.js", () => ({
14
+ DEFAULT_ASSESSMENT_CONFIG: {
15
+ enableExtendedAssessment: false,
16
+ parallelTesting: false,
17
+ testTimeout: 10000,
18
+ enableSourceCodeAnalysis: false,
19
+ },
20
+ getAllModulesConfig: jest.fn(),
21
+ LogLevel: {},
22
+ }));
23
+ jest.unstable_mockModule("../../../../client/lib/services/assessment/lib/claudeCodeBridge.js", () => ({
24
+ FULL_CLAUDE_CODE_CONFIG: {
25
+ timeout: 60000,
26
+ maxRetries: 2,
27
+ },
28
+ }));
29
+ jest.unstable_mockModule("../../../../client/lib/services/assessment/config/performanceConfig.js", () => ({
30
+ loadPerformanceConfig: jest.fn(),
31
+ }));
32
+ // Import after mocking
33
+ const { buildConfig } = await import("../../lib/assessment-runner/config-builder.js");
34
+ const { getProfileModules, resolveModuleNames, modulesToLegacyConfig } = await import("../../profiles.js");
35
+ const { getAllModulesConfig, DEFAULT_ASSESSMENT_CONFIG } = await import("../../../../client/lib/lib/assessmentTypes.js");
36
+ const { loadPerformanceConfig } = await import("../../../../client/lib/services/assessment/config/performanceConfig.js");
37
+ describe("buildConfig", () => {
38
+ const originalEnv = process.env;
39
+ beforeEach(() => {
40
+ jest.clearAllMocks();
41
+ process.env = { ...originalEnv };
42
+ delete process.env.INSPECTOR_CLAUDE;
43
+ delete process.env.INSPECTOR_MCP_AUDITOR_URL;
44
+ delete process.env.LOG_LEVEL;
45
+ // Default mock returns
46
+ getAllModulesConfig.mockReturnValue({
47
+ functionality: true,
48
+ security: true,
49
+ temporal: true,
50
+ });
51
+ getProfileModules.mockReturnValue([
52
+ "functionality",
53
+ "security",
54
+ ]);
55
+ modulesToLegacyConfig.mockReturnValue({
56
+ functionality: true,
57
+ security: true,
58
+ });
59
+ resolveModuleNames.mockImplementation((names) => names);
60
+ });
61
+ afterEach(() => {
62
+ process.env = originalEnv;
63
+ });
64
+ describe("default configuration", () => {
65
+ it("should spread DEFAULT_ASSESSMENT_CONFIG", () => {
66
+ const result = buildConfig({ serverName: "test" });
67
+ // Config should include properties from DEFAULT_ASSESSMENT_CONFIG
68
+ expect(result.testTimeout).toBe(30000); // Overridden in buildConfig
69
+ expect(result.parallelTesting).toBe(true); // Overridden in buildConfig
70
+ });
71
+ it("should set enableExtendedAssessment true by default", () => {
72
+ const result = buildConfig({ serverName: "test" });
73
+ expect(result.enableExtendedAssessment).toBe(true);
74
+ });
75
+ it("should set enableExtendedAssessment false when fullAssessment is false", () => {
76
+ const result = buildConfig({ serverName: "test", fullAssessment: false });
77
+ expect(result.enableExtendedAssessment).toBe(false);
78
+ });
79
+ });
80
+ describe("source code analysis", () => {
81
+ it("should set enableSourceCodeAnalysis false when no sourceCodePath", () => {
82
+ const result = buildConfig({ serverName: "test" });
83
+ expect(result.enableSourceCodeAnalysis).toBe(false);
84
+ });
85
+ it("should set enableSourceCodeAnalysis true when sourceCodePath provided", () => {
86
+ const result = buildConfig({
87
+ serverName: "test",
88
+ sourceCodePath: "/path/to/source",
89
+ });
90
+ expect(result.enableSourceCodeAnalysis).toBe(true);
91
+ });
92
+ });
93
+ describe("profile-based module selection", () => {
94
+ it("should use getProfileModules when profile option is set", () => {
95
+ buildConfig({ serverName: "test", profile: "security" });
96
+ expect(getProfileModules).toHaveBeenCalledWith("security", {
97
+ hasSourceCode: false,
98
+ skipTemporal: undefined,
99
+ });
100
+ });
101
+ it("should pass hasSourceCode to getProfileModules", () => {
102
+ buildConfig({
103
+ serverName: "test",
104
+ profile: "full",
105
+ sourceCodePath: "/path",
106
+ });
107
+ expect(getProfileModules).toHaveBeenCalledWith("full", {
108
+ hasSourceCode: true,
109
+ skipTemporal: undefined,
110
+ });
111
+ });
112
+ it("should pass skipTemporal to getProfileModules", () => {
113
+ buildConfig({
114
+ serverName: "test",
115
+ profile: "security",
116
+ skipTemporal: true,
117
+ });
118
+ expect(getProfileModules).toHaveBeenCalledWith("security", {
119
+ hasSourceCode: false,
120
+ skipTemporal: true,
121
+ });
122
+ });
123
+ it("should convert profile modules to legacy config", () => {
124
+ getProfileModules.mockReturnValue([
125
+ "functionality",
126
+ "security",
127
+ ]);
128
+ buildConfig({ serverName: "test", profile: "quick" });
129
+ expect(modulesToLegacyConfig).toHaveBeenCalledWith([
130
+ "functionality",
131
+ "security",
132
+ ]);
133
+ });
134
+ });
135
+ describe("module filtering", () => {
136
+ it("should apply --only-modules whitelist filter", () => {
137
+ getAllModulesConfig.mockReturnValue({
138
+ functionality: true,
139
+ security: true,
140
+ temporal: true,
141
+ errorHandling: true,
142
+ });
143
+ resolveModuleNames.mockReturnValue(["functionality"]);
144
+ const result = buildConfig({
145
+ serverName: "test",
146
+ onlyModules: ["functionality"],
147
+ });
148
+ expect(resolveModuleNames).toHaveBeenCalledWith(["functionality"]);
149
+ // Only functionality should be true
150
+ expect(result.assessmentCategories?.functionality).toBe(true);
151
+ expect(result.assessmentCategories?.security).toBe(false);
152
+ expect(result.assessmentCategories?.temporal).toBe(false);
153
+ });
154
+ it("should apply --skip-modules blacklist filter", () => {
155
+ getAllModulesConfig.mockReturnValue({
156
+ functionality: true,
157
+ security: true,
158
+ temporal: true,
159
+ });
160
+ resolveModuleNames.mockReturnValue(["temporal"]);
161
+ const result = buildConfig({
162
+ serverName: "test",
163
+ skipModules: ["temporal"],
164
+ });
165
+ expect(resolveModuleNames).toHaveBeenCalledWith(["temporal"]);
166
+ // temporal should be disabled
167
+ expect(result.assessmentCategories?.functionality).toBe(true);
168
+ expect(result.assessmentCategories?.security).toBe(true);
169
+ expect(result.assessmentCategories?.temporal).toBe(false);
170
+ });
171
+ });
172
+ describe("Claude Code configuration", () => {
173
+ it("should not set claudeCode when claudeEnabled is false", () => {
174
+ const result = buildConfig({ serverName: "test", claudeEnabled: false });
175
+ expect(result.claudeCode).toBeUndefined();
176
+ });
177
+ it("should set claudeCode config when claudeEnabled is true", () => {
178
+ const result = buildConfig({ serverName: "test", claudeEnabled: true });
179
+ expect(result.claudeCode).toBeDefined();
180
+ expect(result.claudeCode?.enabled).toBe(true);
181
+ expect(result.claudeCode?.timeout).toBe(60000);
182
+ expect(result.claudeCode?.maxRetries).toBe(2);
183
+ });
184
+ it("should use HTTP transport when claudeHttp flag is set", () => {
185
+ const result = buildConfig({
186
+ serverName: "test",
187
+ claudeEnabled: true,
188
+ claudeHttp: true,
189
+ });
190
+ expect(result.claudeCode?.transport).toBe("http");
191
+ expect(result.claudeCode?.httpConfig?.baseUrl).toBe("http://localhost:8085");
192
+ });
193
+ it("should use HTTP transport when INSPECTOR_CLAUDE env is true", () => {
194
+ process.env.INSPECTOR_CLAUDE = "true";
195
+ const result = buildConfig({ serverName: "test", claudeEnabled: true });
196
+ expect(result.claudeCode?.transport).toBe("http");
197
+ });
198
+ it("should use custom mcpAuditorUrl when provided", () => {
199
+ const result = buildConfig({
200
+ serverName: "test",
201
+ claudeEnabled: true,
202
+ claudeHttp: true,
203
+ mcpAuditorUrl: "http://custom:9000",
204
+ });
205
+ expect(result.claudeCode?.httpConfig?.baseUrl).toBe("http://custom:9000");
206
+ });
207
+ it("should use INSPECTOR_MCP_AUDITOR_URL env when mcpAuditorUrl not provided", () => {
208
+ process.env.INSPECTOR_MCP_AUDITOR_URL = "http://env-url:8000";
209
+ const result = buildConfig({
210
+ serverName: "test",
211
+ claudeEnabled: true,
212
+ claudeHttp: true,
213
+ });
214
+ expect(result.claudeCode?.httpConfig?.baseUrl).toBe("http://env-url:8000");
215
+ });
216
+ it("should enable Claude features when claudeEnabled is true", () => {
217
+ const result = buildConfig({ serverName: "test", claudeEnabled: true });
218
+ expect(result.claudeCode?.features?.intelligentTestGeneration).toBe(true);
219
+ expect(result.claudeCode?.features?.aupSemanticAnalysis).toBe(true);
220
+ expect(result.claudeCode?.features?.annotationInference).toBe(true);
221
+ expect(result.claudeCode?.features?.documentationQuality).toBe(true);
222
+ });
223
+ });
224
+ describe("temporal configuration", () => {
225
+ it("should set temporalInvocations when option provided", () => {
226
+ const result = buildConfig({
227
+ serverName: "test",
228
+ temporalInvocations: 5,
229
+ });
230
+ expect(result.temporalInvocations).toBe(5);
231
+ });
232
+ it("should not set temporalInvocations when option not provided", () => {
233
+ const result = buildConfig({ serverName: "test" });
234
+ expect(result.temporalInvocations).toBeUndefined();
235
+ });
236
+ });
237
+ describe("performance configuration", () => {
238
+ it("should load performance config when path provided", () => {
239
+ loadPerformanceConfig.mockReturnValue({
240
+ batchFlushIntervalMs: 100,
241
+ securityBatchSize: 10,
242
+ functionalityBatchSize: 5,
243
+ });
244
+ buildConfig({
245
+ serverName: "test",
246
+ performanceConfigPath: "/path/to/perf.json",
247
+ });
248
+ expect(loadPerformanceConfig).toHaveBeenCalledWith("/path/to/perf.json");
249
+ });
250
+ it("should throw on invalid performance config file", () => {
251
+ loadPerformanceConfig.mockImplementation(() => {
252
+ throw new Error("Invalid config");
253
+ });
254
+ expect(() => buildConfig({
255
+ serverName: "test",
256
+ performanceConfigPath: "/invalid/path.json",
257
+ })).toThrow("Invalid config");
258
+ });
259
+ });
260
+ describe("pattern configuration", () => {
261
+ it("should set patternConfigPath when option provided", () => {
262
+ const result = buildConfig({
263
+ serverName: "test",
264
+ patternConfigPath: "/path/to/patterns.json",
265
+ });
266
+ expect(result.patternConfigPath).toBe("/path/to/patterns.json");
267
+ });
268
+ });
269
+ describe("logging configuration", () => {
270
+ it("should use logLevel from options when provided", () => {
271
+ const result = buildConfig({ serverName: "test", logLevel: "debug" });
272
+ expect(result.logging?.level).toBe("debug");
273
+ });
274
+ it("should use LOG_LEVEL env when options.logLevel not provided", () => {
275
+ process.env.LOG_LEVEL = "warn";
276
+ const result = buildConfig({ serverName: "test" });
277
+ expect(result.logging?.level).toBe("warn");
278
+ });
279
+ it("should default to info when no logLevel specified", () => {
280
+ const result = buildConfig({ serverName: "test" });
281
+ expect(result.logging?.level).toBe("info");
282
+ });
283
+ it("should prioritize options.logLevel over LOG_LEVEL env", () => {
284
+ process.env.LOG_LEVEL = "warn";
285
+ const result = buildConfig({ serverName: "test", logLevel: "error" });
286
+ expect(result.logging?.level).toBe("error");
287
+ });
288
+ });
289
+ });
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Assessment Runner Index Unit Tests
3
+ *
4
+ * Tests for the facade module exports.
5
+ */
6
+ import { describe, it, expect } from "@jest/globals";
7
+ // Import the barrel/facade module
8
+ import * as assessmentRunner from "../../lib/assessment-runner/index.js";
9
+ describe("assessment-runner index exports", () => {
10
+ describe("function exports", () => {
11
+ it("should export all 6 public functions", () => {
12
+ expect(typeof assessmentRunner.loadServerConfig).toBe("function");
13
+ expect(typeof assessmentRunner.loadSourceFiles).toBe("function");
14
+ expect(typeof assessmentRunner.connectToServer).toBe("function");
15
+ expect(typeof assessmentRunner.createCallToolWrapper).toBe("function");
16
+ expect(typeof assessmentRunner.buildConfig).toBe("function");
17
+ expect(typeof assessmentRunner.runFullAssessment).toBe("function");
18
+ });
19
+ it("should export exactly 6 functions", () => {
20
+ const functionNames = Object.keys(assessmentRunner).filter((key) => typeof assessmentRunner[key] ===
21
+ "function");
22
+ expect(functionNames).toHaveLength(6);
23
+ expect(functionNames.sort()).toEqual([
24
+ "buildConfig",
25
+ "connectToServer",
26
+ "createCallToolWrapper",
27
+ "loadServerConfig",
28
+ "loadSourceFiles",
29
+ "runFullAssessment",
30
+ ]);
31
+ });
32
+ });
33
+ describe("type exports", () => {
34
+ // Type exports are compile-time only, but we can verify
35
+ // the module structure doesn't break TypeScript compilation
36
+ it("should export module without errors", () => {
37
+ // If we got here, the module exported successfully
38
+ expect(assessmentRunner).toBeDefined();
39
+ });
40
+ });
41
+ });