@bryan-thompson/inspector-assessment-cli 1.26.6 → 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,341 @@
1
+ /**
2
+ * Source Loader Unit Tests
3
+ *
4
+ * Tests for loadSourceFiles() that loads source files with gitignore support.
5
+ */
6
+ import { jest, describe, it, expect, beforeEach } from "@jest/globals";
7
+ import * as path from "path";
8
+ // Mock fs module
9
+ jest.unstable_mockModule("fs", () => ({
10
+ existsSync: jest.fn(),
11
+ readFileSync: jest.fn(),
12
+ readdirSync: jest.fn(),
13
+ }));
14
+ // Import after mocking
15
+ const fs = await import("fs");
16
+ const { loadSourceFiles } = await import("../../lib/assessment-runner/source-loader.js");
17
+ // Helper to create mock Dirent objects
18
+ function createDirent(name, isDirectory) {
19
+ return {
20
+ name,
21
+ isDirectory: () => isDirectory,
22
+ isFile: () => !isDirectory,
23
+ isBlockDevice: () => false,
24
+ isCharacterDevice: () => false,
25
+ isSymbolicLink: () => false,
26
+ isFIFO: () => false,
27
+ isSocket: () => false,
28
+ parentPath: "",
29
+ path: "",
30
+ };
31
+ }
32
+ describe("loadSourceFiles", () => {
33
+ const mockExistsSync = fs.existsSync;
34
+ const mockReadFileSync = fs.readFileSync;
35
+ const mockReaddirSync = fs.readdirSync;
36
+ beforeEach(() => {
37
+ jest.clearAllMocks();
38
+ mockExistsSync.mockReturnValue(false);
39
+ mockReaddirSync.mockReturnValue([]);
40
+ });
41
+ describe("README discovery", () => {
42
+ it("should find README.md in source directory", () => {
43
+ const sourcePath = "/project";
44
+ mockExistsSync.mockImplementation((p) => p === path.join(sourcePath, "README.md"));
45
+ mockReadFileSync.mockReturnValue("# Project README");
46
+ const result = loadSourceFiles(sourcePath);
47
+ expect(result.readmeContent).toBe("# Project README");
48
+ });
49
+ it("should find readme.md with lowercase", () => {
50
+ const sourcePath = "/project";
51
+ mockExistsSync.mockImplementation((p) => p === path.join(sourcePath, "readme.md"));
52
+ mockReadFileSync.mockReturnValue("# Lowercase README");
53
+ const result = loadSourceFiles(sourcePath);
54
+ expect(result.readmeContent).toBe("# Lowercase README");
55
+ });
56
+ it("should find Readme.md with mixed case", () => {
57
+ const sourcePath = "/project";
58
+ mockExistsSync.mockImplementation((p) => p === path.join(sourcePath, "Readme.md"));
59
+ mockReadFileSync.mockReturnValue("# Mixed Case README");
60
+ const result = loadSourceFiles(sourcePath);
61
+ expect(result.readmeContent).toBe("# Mixed Case README");
62
+ });
63
+ it("should search parent directories for README (up to 3 levels)", () => {
64
+ const sourcePath = "/project/packages/core/src";
65
+ // README is in /project (3 levels up)
66
+ mockExistsSync.mockImplementation((p) => p === "/project/README.md");
67
+ mockReadFileSync.mockReturnValue("# Root README");
68
+ const result = loadSourceFiles(sourcePath);
69
+ expect(result.readmeContent).toBe("# Root README");
70
+ });
71
+ it("should not search more than 3 levels up", () => {
72
+ const sourcePath = "/a/b/c/d/e";
73
+ // README is in /a (4 levels up) - should not be found
74
+ mockExistsSync.mockReturnValue(false);
75
+ const result = loadSourceFiles(sourcePath);
76
+ expect(result.readmeContent).toBeUndefined();
77
+ });
78
+ });
79
+ describe("package.json parsing", () => {
80
+ it("should parse package.json when present", () => {
81
+ const sourcePath = "/project";
82
+ mockExistsSync.mockImplementation((p) => p === path.join(sourcePath, "package.json"));
83
+ mockReadFileSync.mockReturnValue(JSON.stringify({
84
+ name: "test-package",
85
+ version: "1.0.0",
86
+ }));
87
+ const result = loadSourceFiles(sourcePath);
88
+ expect(result.packageJson).toEqual({
89
+ name: "test-package",
90
+ version: "1.0.0",
91
+ });
92
+ });
93
+ it("should not set packageJson when file does not exist", () => {
94
+ const sourcePath = "/project";
95
+ mockExistsSync.mockReturnValue(false);
96
+ const result = loadSourceFiles(sourcePath);
97
+ expect(result.packageJson).toBeUndefined();
98
+ });
99
+ });
100
+ describe("manifest.json parsing", () => {
101
+ it("should parse manifest.json when present", () => {
102
+ const sourcePath = "/project";
103
+ mockExistsSync.mockImplementation((p) => p === path.join(sourcePath, "manifest.json"));
104
+ const manifestContent = JSON.stringify({
105
+ name: "test-server",
106
+ tools: [],
107
+ });
108
+ mockReadFileSync.mockReturnValue(manifestContent);
109
+ const result = loadSourceFiles(sourcePath);
110
+ expect(result.manifestJson).toEqual({
111
+ name: "test-server",
112
+ tools: [],
113
+ });
114
+ expect(result.manifestRaw).toBe(manifestContent);
115
+ });
116
+ it("should set manifestRaw but warn on invalid JSON manifest", () => {
117
+ const sourcePath = "/project";
118
+ const consoleSpy = jest
119
+ .spyOn(console, "warn")
120
+ .mockImplementation(() => { });
121
+ mockExistsSync.mockImplementation((p) => p === path.join(sourcePath, "manifest.json"));
122
+ mockReadFileSync.mockReturnValue("{ invalid json }");
123
+ const result = loadSourceFiles(sourcePath);
124
+ expect(result.manifestRaw).toBe("{ invalid json }");
125
+ expect(result.manifestJson).toBeUndefined();
126
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Failed to parse manifest.json"));
127
+ consoleSpy.mockRestore();
128
+ });
129
+ });
130
+ describe("source file collection", () => {
131
+ it("should collect source files with supported extensions", () => {
132
+ const sourcePath = "/project";
133
+ mockExistsSync.mockReturnValue(false);
134
+ mockReaddirSync.mockReturnValue([
135
+ createDirent("index.ts", false),
136
+ createDirent("utils.js", false),
137
+ createDirent("config.json", false),
138
+ createDirent("setup.sh", false),
139
+ ]);
140
+ mockReadFileSync.mockImplementation((p) => {
141
+ if (p.endsWith(".ts"))
142
+ return "const x = 1;";
143
+ if (p.endsWith(".js"))
144
+ return "var y = 2;";
145
+ if (p.endsWith(".json"))
146
+ return "{}";
147
+ if (p.endsWith(".sh"))
148
+ return "#!/bin/bash";
149
+ return "";
150
+ });
151
+ const result = loadSourceFiles(sourcePath);
152
+ expect(result.sourceCodeFiles?.size).toBe(4);
153
+ expect(result.sourceCodeFiles?.has("index.ts")).toBe(true);
154
+ expect(result.sourceCodeFiles?.has("utils.js")).toBe(true);
155
+ expect(result.sourceCodeFiles?.has("config.json")).toBe(true);
156
+ expect(result.sourceCodeFiles?.has("setup.sh")).toBe(true);
157
+ });
158
+ it("should recursively load files from subdirectories", () => {
159
+ const sourcePath = "/project";
160
+ mockExistsSync.mockReturnValue(false);
161
+ mockReaddirSync.mockImplementation((dir) => {
162
+ if (dir === sourcePath) {
163
+ return [createDirent("src", true), createDirent("index.ts", false)];
164
+ }
165
+ if (dir === path.join(sourcePath, "src")) {
166
+ return [createDirent("main.ts", false)];
167
+ }
168
+ return [];
169
+ });
170
+ mockReadFileSync.mockReturnValue("code");
171
+ const result = loadSourceFiles(sourcePath);
172
+ expect(result.sourceCodeFiles?.has("index.ts")).toBe(true);
173
+ expect(result.sourceCodeFiles?.has("src/main.ts")).toBe(true);
174
+ });
175
+ it("should enforce 100KB file size limit", () => {
176
+ const sourcePath = "/project";
177
+ mockExistsSync.mockReturnValue(false);
178
+ mockReaddirSync.mockReturnValue([
179
+ createDirent("small.ts", false),
180
+ createDirent("large.ts", false),
181
+ ]);
182
+ mockReadFileSync.mockImplementation((p) => {
183
+ if (p.includes("small"))
184
+ return "small content";
185
+ if (p.includes("large"))
186
+ return "x".repeat(100_001); // > 100KB
187
+ return "";
188
+ });
189
+ const result = loadSourceFiles(sourcePath);
190
+ expect(result.sourceCodeFiles?.has("small.ts")).toBe(true);
191
+ expect(result.sourceCodeFiles?.has("large.ts")).toBe(false);
192
+ });
193
+ it("should skip dotfiles and directories", () => {
194
+ const sourcePath = "/project";
195
+ mockExistsSync.mockReturnValue(false);
196
+ mockReaddirSync.mockReturnValue([
197
+ createDirent(".git", true),
198
+ createDirent(".eslintrc.js", false),
199
+ createDirent("src.ts", false),
200
+ ]);
201
+ mockReadFileSync.mockReturnValue("code");
202
+ const result = loadSourceFiles(sourcePath);
203
+ expect(result.sourceCodeFiles?.has("src.ts")).toBe(true);
204
+ expect(result.sourceCodeFiles?.has(".eslintrc.js")).toBe(false);
205
+ expect(result.sourceCodeFiles?.has(".git")).toBe(false);
206
+ });
207
+ it("should skip node_modules directory", () => {
208
+ const sourcePath = "/project";
209
+ mockExistsSync.mockReturnValue(false);
210
+ mockReaddirSync.mockImplementation((dir) => {
211
+ if (dir === sourcePath) {
212
+ return [
213
+ createDirent("node_modules", true),
214
+ createDirent("src", true),
215
+ ];
216
+ }
217
+ if (dir === path.join(sourcePath, "src")) {
218
+ return [createDirent("index.ts", false)];
219
+ }
220
+ return [];
221
+ });
222
+ mockReadFileSync.mockReturnValue("code");
223
+ const result = loadSourceFiles(sourcePath);
224
+ expect(result.sourceCodeFiles?.has("src/index.ts")).toBe(true);
225
+ // node_modules should be skipped entirely
226
+ });
227
+ });
228
+ describe("gitignore support", () => {
229
+ it("should respect .gitignore patterns", () => {
230
+ const sourcePath = "/project";
231
+ mockExistsSync.mockImplementation((p) => p === path.join(sourcePath, ".gitignore"));
232
+ mockReadFileSync.mockImplementation((p) => {
233
+ if (p.endsWith(".gitignore")) {
234
+ return "dist/\n*.log\nbuild/**";
235
+ }
236
+ return "code";
237
+ });
238
+ mockReaddirSync.mockReturnValue([
239
+ createDirent("index.ts", false),
240
+ createDirent("debug.log", false),
241
+ createDirent("dist", true),
242
+ ]);
243
+ const result = loadSourceFiles(sourcePath);
244
+ expect(result.sourceCodeFiles?.has("index.ts")).toBe(true);
245
+ expect(result.sourceCodeFiles?.has("debug.log")).toBe(false);
246
+ });
247
+ it("should handle missing .gitignore gracefully", () => {
248
+ const sourcePath = "/project";
249
+ mockExistsSync.mockReturnValue(false);
250
+ mockReaddirSync.mockReturnValue([createDirent("index.ts", false)]);
251
+ mockReadFileSync.mockReturnValue("code");
252
+ const result = loadSourceFiles(sourcePath);
253
+ expect(result.sourceCodeFiles?.has("index.ts")).toBe(true);
254
+ });
255
+ it("should ignore comments and empty lines in .gitignore", () => {
256
+ const sourcePath = "/project";
257
+ mockExistsSync.mockImplementation((p) => p === path.join(sourcePath, ".gitignore"));
258
+ mockReadFileSync.mockImplementation((p) => {
259
+ if (p.endsWith(".gitignore")) {
260
+ return "# comment\n\n*.log\n \n# another comment";
261
+ }
262
+ return "code";
263
+ });
264
+ mockReaddirSync.mockReturnValue([
265
+ createDirent("index.ts", false),
266
+ createDirent("debug.log", false),
267
+ ]);
268
+ const result = loadSourceFiles(sourcePath);
269
+ expect(result.sourceCodeFiles?.has("index.ts")).toBe(true);
270
+ expect(result.sourceCodeFiles?.has("debug.log")).toBe(false);
271
+ });
272
+ });
273
+ describe("error handling", () => {
274
+ it("should return empty sourceCodeFiles when directory read fails", () => {
275
+ const sourcePath = "/project";
276
+ const consoleSpy = jest
277
+ .spyOn(console, "warn")
278
+ .mockImplementation(() => { });
279
+ mockExistsSync.mockReturnValue(false);
280
+ mockReaddirSync.mockImplementation(() => {
281
+ throw new Error("Permission denied");
282
+ });
283
+ const result = loadSourceFiles(sourcePath);
284
+ expect(result.sourceCodeFiles?.size).toBe(0);
285
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Could not load source files"), expect.any(Error));
286
+ consoleSpy.mockRestore();
287
+ });
288
+ it("should skip unreadable files silently", () => {
289
+ const sourcePath = "/project";
290
+ mockExistsSync.mockReturnValue(false);
291
+ mockReaddirSync.mockReturnValue([
292
+ createDirent("readable.ts", false),
293
+ createDirent("unreadable.ts", false),
294
+ ]);
295
+ mockReadFileSync.mockImplementation((p) => {
296
+ if (p.includes("unreadable")) {
297
+ throw new Error("Permission denied");
298
+ }
299
+ return "code";
300
+ });
301
+ const result = loadSourceFiles(sourcePath);
302
+ expect(result.sourceCodeFiles?.has("readable.ts")).toBe(true);
303
+ expect(result.sourceCodeFiles?.has("unreadable.ts")).toBe(false);
304
+ });
305
+ });
306
+ describe("supported file extensions", () => {
307
+ it("should support all defined extensions", () => {
308
+ const sourcePath = "/project";
309
+ const extensions = [
310
+ ".ts",
311
+ ".js",
312
+ ".py",
313
+ ".go",
314
+ ".rs",
315
+ ".json",
316
+ ".sh",
317
+ ".yaml",
318
+ ".yml",
319
+ ];
320
+ mockExistsSync.mockReturnValue(false);
321
+ mockReaddirSync.mockReturnValue(extensions.map((ext) => createDirent(`file${ext}`, false)));
322
+ mockReadFileSync.mockReturnValue("content");
323
+ const result = loadSourceFiles(sourcePath);
324
+ for (const ext of extensions) {
325
+ expect(result.sourceCodeFiles?.has(`file${ext}`)).toBe(true);
326
+ }
327
+ });
328
+ it("should not include unsupported file types", () => {
329
+ const sourcePath = "/project";
330
+ mockExistsSync.mockReturnValue(false);
331
+ mockReaddirSync.mockReturnValue([
332
+ createDirent("image.png", false),
333
+ createDirent("doc.pdf", false),
334
+ createDirent("data.csv", false),
335
+ ]);
336
+ mockReadFileSync.mockReturnValue("content");
337
+ const result = loadSourceFiles(sourcePath);
338
+ expect(result.sourceCodeFiles?.size).toBe(0);
339
+ });
340
+ });
341
+ });
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Tool Wrapper Unit Tests
3
+ *
4
+ * Tests for createCallToolWrapper() that wraps MCP client.callTool().
5
+ */
6
+ import { jest, describe, it, expect, beforeEach } from "@jest/globals";
7
+ // Import the actual function (no mocking needed for this module)
8
+ import { createCallToolWrapper } from "../../lib/assessment-runner/tool-wrapper.js";
9
+ describe("createCallToolWrapper", () => {
10
+ let mockClient;
11
+ let mockCallTool;
12
+ beforeEach(() => {
13
+ mockCallTool = jest.fn();
14
+ mockClient = {
15
+ callTool: mockCallTool,
16
+ };
17
+ });
18
+ describe("successful tool calls", () => {
19
+ it("should wrap successful tool response with content array", async () => {
20
+ mockCallTool.mockResolvedValue({
21
+ content: [{ type: "text", text: "Hello, World!" }],
22
+ });
23
+ const callTool = createCallToolWrapper(mockClient);
24
+ const result = await callTool("greet", { name: "World" });
25
+ expect(mockCallTool).toHaveBeenCalledWith({
26
+ name: "greet",
27
+ arguments: { name: "World" },
28
+ });
29
+ expect(result.content).toEqual([{ type: "text", text: "Hello, World!" }]);
30
+ });
31
+ it("should include structuredContent from response when present", async () => {
32
+ mockCallTool.mockResolvedValue({
33
+ content: [{ type: "text", text: "result" }],
34
+ structuredContent: { data: [1, 2, 3] },
35
+ });
36
+ const callTool = createCallToolWrapper(mockClient);
37
+ const result = await callTool("getData", {});
38
+ expect(result.structuredContent).toEqual({ data: [1, 2, 3] });
39
+ });
40
+ it("should set isError false by default", async () => {
41
+ mockCallTool.mockResolvedValue({
42
+ content: [{ type: "text", text: "success" }],
43
+ });
44
+ const callTool = createCallToolWrapper(mockClient);
45
+ const result = await callTool("tool", {});
46
+ expect(result.isError).toBe(false);
47
+ });
48
+ it("should preserve isError true when response has error flag", async () => {
49
+ mockCallTool.mockResolvedValue({
50
+ content: [{ type: "text", text: "Tool error occurred" }],
51
+ isError: true,
52
+ });
53
+ const callTool = createCallToolWrapper(mockClient);
54
+ const result = await callTool("failingTool", {});
55
+ expect(result.isError).toBe(true);
56
+ expect(result.content).toEqual([
57
+ { type: "text", text: "Tool error occurred" },
58
+ ]);
59
+ });
60
+ });
61
+ describe("exception handling", () => {
62
+ it("should catch exceptions and return error text content", async () => {
63
+ mockCallTool.mockRejectedValue(new Error("Network timeout"));
64
+ const callTool = createCallToolWrapper(mockClient);
65
+ const result = await callTool("networkTool", {});
66
+ expect(result.content).toEqual([
67
+ { type: "text", text: "Error: Network timeout" },
68
+ ]);
69
+ });
70
+ it("should set isError true on caught exceptions", async () => {
71
+ mockCallTool.mockRejectedValue(new Error("Something went wrong"));
72
+ const callTool = createCallToolWrapper(mockClient);
73
+ const result = await callTool("brokenTool", {});
74
+ expect(result.isError).toBe(true);
75
+ });
76
+ it("should handle non-Error exceptions", async () => {
77
+ mockCallTool.mockRejectedValue("String error");
78
+ const callTool = createCallToolWrapper(mockClient);
79
+ const result = await callTool("stringErrorTool", {});
80
+ expect(result.content).toEqual([
81
+ { type: "text", text: "Error: String error" },
82
+ ]);
83
+ expect(result.isError).toBe(true);
84
+ });
85
+ });
86
+ describe("argument passing", () => {
87
+ it("should pass parameters as arguments to callTool", async () => {
88
+ mockCallTool.mockResolvedValue({ content: [] });
89
+ const callTool = createCallToolWrapper(mockClient);
90
+ await callTool("complexTool", {
91
+ query: "search term",
92
+ limit: 10,
93
+ options: { deep: true },
94
+ });
95
+ expect(mockCallTool).toHaveBeenCalledWith({
96
+ name: "complexTool",
97
+ arguments: {
98
+ query: "search term",
99
+ limit: 10,
100
+ options: { deep: true },
101
+ },
102
+ });
103
+ });
104
+ it("should handle empty parameters", async () => {
105
+ mockCallTool.mockResolvedValue({ content: [] });
106
+ const callTool = createCallToolWrapper(mockClient);
107
+ await callTool("noArgsTool", {});
108
+ expect(mockCallTool).toHaveBeenCalledWith({
109
+ name: "noArgsTool",
110
+ arguments: {},
111
+ });
112
+ });
113
+ });
114
+ });
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Assessment Runner Facade Backward Compatibility Tests
3
+ *
4
+ * Verifies that the facade pattern maintains backward compatibility
5
+ * after the modularization in Issue #94.
6
+ *
7
+ * @see https://github.com/triepod-ai/inspector-assessment/issues/96
8
+ */
9
+ import { describe, it, expect } from "@jest/globals";
10
+ // Test named imports (the primary consumer pattern)
11
+ import { loadServerConfig, loadSourceFiles, connectToServer, createCallToolWrapper, buildConfig, runFullAssessment, } from "../lib/assessment-runner.js";
12
+ // Test namespace import
13
+ import * as AssessmentRunner from "../lib/assessment-runner.js";
14
+ describe("Assessment Runner Facade", () => {
15
+ describe("Function Exports", () => {
16
+ it("should export loadServerConfig function", () => {
17
+ expect(typeof loadServerConfig).toBe("function");
18
+ });
19
+ it("should export loadSourceFiles function", () => {
20
+ expect(typeof loadSourceFiles).toBe("function");
21
+ });
22
+ it("should export connectToServer function", () => {
23
+ expect(typeof connectToServer).toBe("function");
24
+ });
25
+ it("should export createCallToolWrapper function", () => {
26
+ expect(typeof createCallToolWrapper).toBe("function");
27
+ });
28
+ it("should export buildConfig function", () => {
29
+ expect(typeof buildConfig).toBe("function");
30
+ });
31
+ it("should export runFullAssessment function", () => {
32
+ expect(typeof runFullAssessment).toBe("function");
33
+ });
34
+ });
35
+ describe("Namespace Import Compatibility", () => {
36
+ it("should export all functions via namespace import", () => {
37
+ expect(typeof AssessmentRunner.loadServerConfig).toBe("function");
38
+ expect(typeof AssessmentRunner.loadSourceFiles).toBe("function");
39
+ expect(typeof AssessmentRunner.connectToServer).toBe("function");
40
+ expect(typeof AssessmentRunner.createCallToolWrapper).toBe("function");
41
+ expect(typeof AssessmentRunner.buildConfig).toBe("function");
42
+ expect(typeof AssessmentRunner.runFullAssessment).toBe("function");
43
+ });
44
+ it("should have consistent function references between import styles", () => {
45
+ // Verify that named imports and namespace imports reference the same functions
46
+ expect(loadServerConfig).toBe(AssessmentRunner.loadServerConfig);
47
+ expect(loadSourceFiles).toBe(AssessmentRunner.loadSourceFiles);
48
+ expect(connectToServer).toBe(AssessmentRunner.connectToServer);
49
+ expect(createCallToolWrapper).toBe(AssessmentRunner.createCallToolWrapper);
50
+ expect(buildConfig).toBe(AssessmentRunner.buildConfig);
51
+ expect(runFullAssessment).toBe(AssessmentRunner.runFullAssessment);
52
+ });
53
+ });
54
+ describe("Type Exports", () => {
55
+ it("should export SourceFiles type with expected shape", () => {
56
+ // TypeScript compile-time check - if this compiles, the type is exported correctly
57
+ const sourceFiles = {
58
+ readmeContent: "# Test",
59
+ packageJson: { name: "test" },
60
+ manifestJson: undefined,
61
+ manifestRaw: undefined,
62
+ sourceCodeFiles: new Map(),
63
+ };
64
+ // Runtime verification of the shape
65
+ expect(sourceFiles).toHaveProperty("readmeContent");
66
+ expect(sourceFiles).toHaveProperty("packageJson");
67
+ expect(sourceFiles).toHaveProperty("sourceCodeFiles");
68
+ });
69
+ it("should export CallToolFn type", () => {
70
+ // TypeScript compile-time check - if this compiles, the type is exported correctly
71
+ const mockCallTool = async (name, params) => {
72
+ return {
73
+ content: [{ type: "text", text: `Called ${name}` }],
74
+ isError: false,
75
+ };
76
+ };
77
+ // Verify the function signature works
78
+ expect(typeof mockCallTool).toBe("function");
79
+ });
80
+ });
81
+ describe("Export Completeness", () => {
82
+ it("should export exactly 6 functions", () => {
83
+ const exportedFunctions = Object.entries(AssessmentRunner).filter(([, value]) => typeof value === "function");
84
+ expect(exportedFunctions.length).toBe(6);
85
+ expect(exportedFunctions.map(([name]) => name).sort()).toEqual([
86
+ "buildConfig",
87
+ "connectToServer",
88
+ "createCallToolWrapper",
89
+ "loadServerConfig",
90
+ "loadSourceFiles",
91
+ "runFullAssessment",
92
+ ]);
93
+ });
94
+ it("should not have unexpected exports", () => {
95
+ const allExports = Object.keys(AssessmentRunner);
96
+ const expectedExports = [
97
+ "loadServerConfig",
98
+ "loadSourceFiles",
99
+ "connectToServer",
100
+ "createCallToolWrapper",
101
+ "buildConfig",
102
+ "runFullAssessment",
103
+ ];
104
+ // All exports should be in the expected list
105
+ for (const exportName of allExports) {
106
+ expect(expectedExports).toContain(exportName);
107
+ }
108
+ });
109
+ });
110
+ describe("Consumer Compatibility", () => {
111
+ it("should support the existing assess-full.ts import pattern", () => {
112
+ // This is the exact import pattern used in assess-full.ts:18
113
+ // import { runFullAssessment } from "./lib/assessment-runner.js";
114
+ expect(runFullAssessment).toBeDefined();
115
+ expect(typeof runFullAssessment).toBe("function");
116
+ });
117
+ });
118
+ });