@bryan-thompson/inspector-assessment-cli 1.43.1 → 1.43.3

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,439 @@
1
+ /**
2
+ * Static-Only Mode Unit Tests
3
+ *
4
+ * Tests for Issue #213: Static/manifest-only validation mode.
5
+ *
6
+ * Tests cover:
7
+ * - CLI flag parsing for --static-only and --fallback-static
8
+ * - Mutual exclusivity validation
9
+ * - Static module configuration
10
+ * - Config builder static mode handling
11
+ */
12
+ import { describe, it, expect, jest, beforeEach, afterEach, } from "@jest/globals";
13
+ import { parseArgs } from "../lib/cli-parser.js";
14
+ import { STATIC_MODULES, RUNTIME_MODULES, isStaticModule, isRuntimeModule, filterToStaticModules, filterToRuntimeModules, } from "../lib/static-modules.js";
15
+ import { buildConfig } from "../lib/assessment-runner/config-builder.js";
16
+ describe("Static-Only Mode CLI Parsing", () => {
17
+ // Suppress console output during tests
18
+ let consoleErrorSpy;
19
+ let consoleWarnSpy;
20
+ let consoleLogSpy;
21
+ let processExitSpy;
22
+ beforeEach(() => {
23
+ consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => { });
24
+ consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation(() => { });
25
+ consoleLogSpy = jest.spyOn(console, "log").mockImplementation(() => { });
26
+ processExitSpy = jest
27
+ .spyOn(process, "exit")
28
+ .mockImplementation(() => undefined);
29
+ });
30
+ afterEach(() => {
31
+ jest.restoreAllMocks();
32
+ });
33
+ describe("--static-only flag", () => {
34
+ it("should parse --static-only flag", () => {
35
+ const result = parseArgs([
36
+ "--server",
37
+ "test-server",
38
+ "--source",
39
+ "/path/to/source",
40
+ "--static-only",
41
+ ]);
42
+ expect(result.staticOnly).toBe(true);
43
+ });
44
+ it("should set staticOnly to undefined when not provided", () => {
45
+ const result = parseArgs([
46
+ "--server",
47
+ "test-server",
48
+ "--http",
49
+ "http://localhost:8080",
50
+ ]);
51
+ expect(result.staticOnly).toBeUndefined();
52
+ });
53
+ });
54
+ describe("--fallback-static flag", () => {
55
+ it("should parse --fallback-static flag", () => {
56
+ const result = parseArgs([
57
+ "--server",
58
+ "test-server",
59
+ "--http",
60
+ "http://localhost:8080",
61
+ "--source",
62
+ "/path/to/source",
63
+ "--fallback-static",
64
+ ]);
65
+ expect(result.fallbackStatic).toBe(true);
66
+ });
67
+ it("should set fallbackStatic to undefined when not provided", () => {
68
+ const result = parseArgs([
69
+ "--server",
70
+ "test-server",
71
+ "--http",
72
+ "http://localhost:8080",
73
+ ]);
74
+ expect(result.fallbackStatic).toBeUndefined();
75
+ });
76
+ });
77
+ describe("Mutual Exclusivity", () => {
78
+ it("should reject --static-only with --fallback-static", () => {
79
+ const result = parseArgs([
80
+ "--server",
81
+ "test-server",
82
+ "--source",
83
+ "/path/to/source",
84
+ "--static-only",
85
+ "--fallback-static",
86
+ ]);
87
+ expect(result.helpRequested).toBe(true);
88
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("mutually exclusive"));
89
+ });
90
+ it("should reject --static-only without --source", () => {
91
+ const result = parseArgs(["--server", "test-server", "--static-only"]);
92
+ expect(result.helpRequested).toBe(true);
93
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("requires --source"));
94
+ });
95
+ it("should reject --static-only with --http", () => {
96
+ const result = parseArgs([
97
+ "--server",
98
+ "test-server",
99
+ "--source",
100
+ "/path/to/source",
101
+ "--static-only",
102
+ "--http",
103
+ "http://localhost:8080",
104
+ ]);
105
+ expect(result.helpRequested).toBe(true);
106
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("cannot be used with --http"));
107
+ });
108
+ it("should reject --static-only with --sse", () => {
109
+ const result = parseArgs([
110
+ "--server",
111
+ "test-server",
112
+ "--source",
113
+ "/path/to/source",
114
+ "--static-only",
115
+ "--sse",
116
+ "http://localhost:8080/sse",
117
+ ]);
118
+ expect(result.helpRequested).toBe(true);
119
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("cannot be used with --http"));
120
+ });
121
+ it("should reject --static-only with --config", () => {
122
+ const result = parseArgs([
123
+ "--server",
124
+ "test-server",
125
+ "--source",
126
+ "/path/to/source",
127
+ "--static-only",
128
+ "--config",
129
+ "/path/to/config.json",
130
+ ]);
131
+ expect(result.helpRequested).toBe(true);
132
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("cannot be used with"));
133
+ });
134
+ it("should reject --static-only with --module", () => {
135
+ const result = parseArgs([
136
+ "--server",
137
+ "test-server",
138
+ "--source",
139
+ "/path/to/source",
140
+ "--static-only",
141
+ "--module",
142
+ "security",
143
+ ]);
144
+ expect(result.helpRequested).toBe(true);
145
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("cannot be used with --module"));
146
+ });
147
+ it("should allow --static-only with --only-modules", () => {
148
+ const result = parseArgs([
149
+ "--server",
150
+ "test-server",
151
+ "--source",
152
+ "/path/to/source",
153
+ "--static-only",
154
+ "--only-modules",
155
+ "manifestValidation,prohibitedLibraries",
156
+ ]);
157
+ // Should not error - this is allowed for filtering static modules
158
+ expect(result.staticOnly).toBe(true);
159
+ expect(result.onlyModules).toEqual([
160
+ "manifestValidation",
161
+ "prohibitedLibraries",
162
+ ]);
163
+ });
164
+ it("should allow --static-only with --skip-modules", () => {
165
+ const result = parseArgs([
166
+ "--server",
167
+ "test-server",
168
+ "--source",
169
+ "/path/to/source",
170
+ "--static-only",
171
+ "--skip-modules",
172
+ "portability",
173
+ ]);
174
+ // Should not error - this is allowed for filtering static modules
175
+ expect(result.staticOnly).toBe(true);
176
+ expect(result.skipModules).toEqual(["portability"]);
177
+ });
178
+ });
179
+ });
180
+ describe("Static Modules Definition", () => {
181
+ describe("STATIC_MODULES constant", () => {
182
+ it("should contain expected static-capable modules", () => {
183
+ expect(STATIC_MODULES).toContain("manifestValidation");
184
+ expect(STATIC_MODULES).toContain("documentation"); // Legacy name for DeveloperExperience
185
+ expect(STATIC_MODULES).toContain("usability"); // Legacy name for DeveloperExperience
186
+ expect(STATIC_MODULES).toContain("prohibitedLibraries");
187
+ expect(STATIC_MODULES).toContain("portability");
188
+ expect(STATIC_MODULES).toContain("externalAPIScanner");
189
+ expect(STATIC_MODULES).toContain("fileModularization");
190
+ expect(STATIC_MODULES).toContain("conformance");
191
+ expect(STATIC_MODULES).toContain("toolAnnotations");
192
+ expect(STATIC_MODULES).toContain("authentication");
193
+ expect(STATIC_MODULES).toContain("aupCompliance");
194
+ });
195
+ it("should not contain runtime-only modules", () => {
196
+ expect(STATIC_MODULES).not.toContain("functionality");
197
+ expect(STATIC_MODULES).not.toContain("security");
198
+ expect(STATIC_MODULES).not.toContain("temporal");
199
+ expect(STATIC_MODULES).not.toContain("protocolCompliance");
200
+ });
201
+ it("should have exactly 11 static modules", () => {
202
+ // documentation + usability (both legacy names for DeveloperExperience)
203
+ expect(STATIC_MODULES.length).toBe(11);
204
+ });
205
+ });
206
+ describe("RUNTIME_MODULES constant", () => {
207
+ it("should contain expected runtime-only modules", () => {
208
+ expect(RUNTIME_MODULES).toContain("functionality");
209
+ expect(RUNTIME_MODULES).toContain("security");
210
+ expect(RUNTIME_MODULES).toContain("temporal");
211
+ expect(RUNTIME_MODULES).toContain("protocolCompliance");
212
+ expect(RUNTIME_MODULES).toContain("resources");
213
+ expect(RUNTIME_MODULES).toContain("prompts");
214
+ expect(RUNTIME_MODULES).toContain("crossCapability");
215
+ expect(RUNTIME_MODULES).toContain("errorHandling");
216
+ expect(RUNTIME_MODULES).toContain("dependencyVulnerability");
217
+ });
218
+ it("should not contain static-capable modules", () => {
219
+ expect(RUNTIME_MODULES).not.toContain("manifestValidation");
220
+ expect(RUNTIME_MODULES).not.toContain("prohibitedLibraries");
221
+ expect(RUNTIME_MODULES).not.toContain("portability");
222
+ expect(RUNTIME_MODULES).not.toContain("documentation");
223
+ });
224
+ it("should have exactly 9 runtime modules", () => {
225
+ // Includes dependencyVulnerability (requires live server for npm/yarn audit)
226
+ expect(RUNTIME_MODULES.length).toBe(9);
227
+ });
228
+ });
229
+ describe("isStaticModule helper", () => {
230
+ it("should return true for static-capable modules", () => {
231
+ expect(isStaticModule("manifestValidation")).toBe(true);
232
+ expect(isStaticModule("prohibitedLibraries")).toBe(true);
233
+ expect(isStaticModule("toolAnnotations")).toBe(true);
234
+ });
235
+ it("should return false for runtime-only modules", () => {
236
+ expect(isStaticModule("functionality")).toBe(false);
237
+ expect(isStaticModule("security")).toBe(false);
238
+ expect(isStaticModule("temporal")).toBe(false);
239
+ });
240
+ it("should return false for unknown modules", () => {
241
+ expect(isStaticModule("unknownModule")).toBe(false);
242
+ });
243
+ });
244
+ describe("isRuntimeModule helper", () => {
245
+ it("should return true for runtime-only modules", () => {
246
+ expect(isRuntimeModule("functionality")).toBe(true);
247
+ expect(isRuntimeModule("security")).toBe(true);
248
+ expect(isRuntimeModule("temporal")).toBe(true);
249
+ });
250
+ it("should return false for static-capable modules", () => {
251
+ expect(isRuntimeModule("manifestValidation")).toBe(false);
252
+ expect(isRuntimeModule("prohibitedLibraries")).toBe(false);
253
+ });
254
+ });
255
+ describe("filterToStaticModules helper", () => {
256
+ it("should filter to only static-capable modules", () => {
257
+ const input = [
258
+ "manifestValidation",
259
+ "functionality",
260
+ "security",
261
+ "prohibitedLibraries",
262
+ ];
263
+ const result = filterToStaticModules(input);
264
+ expect(result).toEqual(["manifestValidation", "prohibitedLibraries"]);
265
+ });
266
+ it("should return empty array when no static modules present", () => {
267
+ const input = ["functionality", "security", "temporal"];
268
+ const result = filterToStaticModules(input);
269
+ expect(result).toEqual([]);
270
+ });
271
+ });
272
+ describe("filterToRuntimeModules helper", () => {
273
+ it("should filter to only runtime-only modules", () => {
274
+ const input = [
275
+ "manifestValidation",
276
+ "functionality",
277
+ "security",
278
+ "prohibitedLibraries",
279
+ ];
280
+ const result = filterToRuntimeModules(input);
281
+ expect(result).toEqual(["functionality", "security"]);
282
+ });
283
+ it("should return empty array when no runtime modules present", () => {
284
+ const input = [
285
+ "manifestValidation",
286
+ "prohibitedLibraries",
287
+ "portability",
288
+ ];
289
+ const result = filterToRuntimeModules(input);
290
+ expect(result).toEqual([]);
291
+ });
292
+ });
293
+ });
294
+ describe("Config Builder Static Mode", () => {
295
+ // Suppress console output during tests
296
+ let consoleWarnSpy;
297
+ let consoleLogSpy;
298
+ beforeEach(() => {
299
+ consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation(() => { });
300
+ consoleLogSpy = jest.spyOn(console, "log").mockImplementation(() => { });
301
+ });
302
+ afterEach(() => {
303
+ jest.restoreAllMocks();
304
+ });
305
+ describe("buildConfig with staticOnly option", () => {
306
+ it("should enable only static-capable modules", () => {
307
+ const config = buildConfig({
308
+ serverName: "test-server",
309
+ sourceCodePath: "/path/to/source",
310
+ staticOnly: true,
311
+ });
312
+ const categories = config.assessmentCategories;
313
+ // Static modules should be enabled (using legacy property names)
314
+ expect(categories.manifestValidation).toBe(true);
315
+ expect(categories.documentation).toBe(true); // Legacy name for DeveloperExperience
316
+ expect(categories.usability).toBe(true); // Legacy name for DeveloperExperience
317
+ expect(categories.prohibitedLibraries).toBe(true);
318
+ expect(categories.portability).toBe(true);
319
+ expect(categories.externalAPIScanner).toBe(true);
320
+ expect(categories.toolAnnotations).toBe(true);
321
+ expect(categories.authentication).toBe(true);
322
+ expect(categories.aupCompliance).toBe(true);
323
+ // Runtime modules should be disabled
324
+ expect(categories.functionality).toBe(false);
325
+ expect(categories.security).toBe(false);
326
+ expect(categories.temporal).toBe(false);
327
+ expect(categories.protocolCompliance).toBe(false);
328
+ });
329
+ it("should apply --only-modules filter to static modules", () => {
330
+ const config = buildConfig({
331
+ serverName: "test-server",
332
+ sourceCodePath: "/path/to/source",
333
+ staticOnly: true,
334
+ onlyModules: ["manifestValidation", "prohibitedLibraries"],
335
+ });
336
+ const categories = config.assessmentCategories;
337
+ // Only whitelisted static modules should be enabled
338
+ expect(categories.manifestValidation).toBe(true);
339
+ expect(categories.prohibitedLibraries).toBe(true);
340
+ // Other static modules should be disabled
341
+ expect(categories.portability).toBe(false);
342
+ expect(categories.toolAnnotations).toBe(false);
343
+ // Runtime modules still disabled
344
+ expect(categories.functionality).toBe(false);
345
+ expect(categories.security).toBe(false);
346
+ });
347
+ it("should apply --skip-modules filter to static modules", () => {
348
+ const config = buildConfig({
349
+ serverName: "test-server",
350
+ sourceCodePath: "/path/to/source",
351
+ staticOnly: true,
352
+ skipModules: ["portability", "externalAPIScanner"],
353
+ });
354
+ const categories = config.assessmentCategories;
355
+ // Skipped modules should be disabled
356
+ expect(categories.portability).toBe(false);
357
+ expect(categories.externalAPIScanner).toBe(false);
358
+ // Other static modules should still be enabled
359
+ expect(categories.manifestValidation).toBe(true);
360
+ expect(categories.prohibitedLibraries).toBe(true);
361
+ });
362
+ it("should ignore runtime modules in --only-modules with static mode", () => {
363
+ const config = buildConfig({
364
+ serverName: "test-server",
365
+ sourceCodePath: "/path/to/source",
366
+ staticOnly: true,
367
+ onlyModules: ["manifestValidation", "security", "functionality"],
368
+ });
369
+ const categories = config.assessmentCategories;
370
+ // Only static module from whitelist should be enabled
371
+ expect(categories.manifestValidation).toBe(true);
372
+ // Runtime modules from whitelist should still be disabled
373
+ expect(categories.security).toBe(false);
374
+ expect(categories.functionality).toBe(false);
375
+ });
376
+ it("should result in zero enabled modules if only runtime modules in --only-modules", () => {
377
+ const config = buildConfig({
378
+ serverName: "test-server",
379
+ sourceCodePath: "/path/to/source",
380
+ staticOnly: true,
381
+ onlyModules: ["functionality", "security", "temporal"],
382
+ });
383
+ const categories = config.assessmentCategories;
384
+ // All runtime modules should be disabled
385
+ expect(categories.functionality).toBe(false);
386
+ expect(categories.security).toBe(false);
387
+ expect(categories.temporal).toBe(false);
388
+ // Static modules should also be disabled (not in onlyModules)
389
+ expect(categories.manifestValidation).toBe(false);
390
+ expect(categories.prohibitedLibraries).toBe(false);
391
+ });
392
+ it("should result in zero enabled modules if all static modules skipped", () => {
393
+ const config = buildConfig({
394
+ serverName: "test-server",
395
+ sourceCodePath: "/path/to/source",
396
+ staticOnly: true,
397
+ skipModules: [
398
+ "manifestValidation",
399
+ "documentation",
400
+ "usability",
401
+ "prohibitedLibraries",
402
+ "portability",
403
+ "externalAPIScanner",
404
+ "fileModularization",
405
+ "conformance",
406
+ "toolAnnotations",
407
+ "authentication",
408
+ "aupCompliance",
409
+ ],
410
+ });
411
+ const categories = config.assessmentCategories;
412
+ // All static modules should be disabled
413
+ expect(categories.manifestValidation).toBe(false);
414
+ expect(categories.prohibitedLibraries).toBe(false);
415
+ expect(categories.portability).toBe(false);
416
+ expect(categories.toolAnnotations).toBe(false);
417
+ });
418
+ it("should log static mode info", () => {
419
+ buildConfig({
420
+ serverName: "test-server",
421
+ sourceCodePath: "/path/to/source",
422
+ staticOnly: true,
423
+ });
424
+ expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("Static-only mode"));
425
+ });
426
+ });
427
+ describe("buildConfig without staticOnly option", () => {
428
+ it("should enable runtime modules by default", () => {
429
+ const config = buildConfig({
430
+ serverName: "test-server",
431
+ sourceCodePath: "/path/to/source",
432
+ });
433
+ const categories = config.assessmentCategories;
434
+ // Runtime modules should be enabled by default
435
+ expect(categories.functionality).toBe(true);
436
+ expect(categories.security).toBe(true);
437
+ });
438
+ });
439
+ });
@@ -68,6 +68,147 @@ describe("Transport Creation", () => {
68
68
  expect(transport).toBeDefined();
69
69
  });
70
70
  });
71
+ describe("Minimal Environment Variables (Issue #211)", () => {
72
+ let originalEnv;
73
+ afterEach(() => {
74
+ // Restore original environment after each test
75
+ process.env = originalEnv;
76
+ });
77
+ it("should pass minimal env vars (PATH, HOME) to spawned servers", () => {
78
+ originalEnv = process.env;
79
+ process.env = {
80
+ PATH: "/usr/bin:/usr/local/bin",
81
+ HOME: "/home/testuser",
82
+ };
83
+ const options = {
84
+ transportType: "stdio",
85
+ command: "node",
86
+ args: ["server.js"],
87
+ };
88
+ // Creating the transport should not throw
89
+ const transport = createTransport(options);
90
+ expect(transport).toBeDefined();
91
+ // The transport is created with minimal env (PATH, HOME, NODE_ENV)
92
+ // We can't directly inspect StdioClientTransport's env without mocking,
93
+ // but we verify the transport was created successfully
94
+ });
95
+ it("should default NODE_ENV to production when not set", () => {
96
+ originalEnv = process.env;
97
+ process.env = {
98
+ PATH: "/usr/bin",
99
+ HOME: "/home/user",
100
+ // NODE_ENV intentionally not set
101
+ };
102
+ const options = {
103
+ transportType: "stdio",
104
+ command: "python",
105
+ args: ["server.py"],
106
+ };
107
+ const transport = createTransport(options);
108
+ expect(transport).toBeDefined();
109
+ });
110
+ it("should preserve NODE_ENV when set", () => {
111
+ originalEnv = process.env;
112
+ process.env = {
113
+ PATH: "/usr/bin",
114
+ HOME: "/home/user",
115
+ NODE_ENV: "development",
116
+ };
117
+ const options = {
118
+ transportType: "stdio",
119
+ command: "node",
120
+ args: ["server.js"],
121
+ };
122
+ const transport = createTransport(options);
123
+ expect(transport).toBeDefined();
124
+ });
125
+ it("should NOT pass arbitrary process.env vars (Issue #211)", () => {
126
+ originalEnv = process.env;
127
+ process.env = {
128
+ PATH: "/usr/bin",
129
+ HOME: "/home/user",
130
+ SOME_RANDOM_VAR: "should-not-pass",
131
+ ENABLE_DYNAMIC_MAPS: "true", // This caused TomTom MCP issues
132
+ AWS_ACCESS_KEY_ID: "secret", // Sensitive vars should not leak
133
+ CUSTOM_CONFIG: "value",
134
+ };
135
+ const options = {
136
+ transportType: "stdio",
137
+ command: "node",
138
+ args: ["server.js"],
139
+ };
140
+ // Creating transport should succeed
141
+ const transport = createTransport(options);
142
+ expect(transport).toBeDefined();
143
+ // The arbitrary environment variables are filtered out by getMinimalEnv()
144
+ // We verify the transport was created without throwing errors
145
+ });
146
+ it("should pass platform-specific essential vars (USER, SHELL, LANG)", () => {
147
+ originalEnv = process.env;
148
+ process.env = {
149
+ PATH: "/usr/bin",
150
+ HOME: "/home/user",
151
+ USER: "testuser",
152
+ SHELL: "/bin/bash",
153
+ LANG: "en_US.UTF-8",
154
+ };
155
+ const options = {
156
+ transportType: "stdio",
157
+ command: "node",
158
+ args: ["server.js"],
159
+ };
160
+ const transport = createTransport(options);
161
+ expect(transport).toBeDefined();
162
+ });
163
+ it("should pass temp directory vars (TMPDIR, TMP, TEMP)", () => {
164
+ originalEnv = process.env;
165
+ process.env = {
166
+ PATH: "/usr/bin",
167
+ HOME: "/home/user",
168
+ TMPDIR: "/tmp",
169
+ TMP: "/tmp",
170
+ TEMP: "/tmp",
171
+ };
172
+ const options = {
173
+ transportType: "stdio",
174
+ command: "python",
175
+ args: ["-m", "server"],
176
+ };
177
+ const transport = createTransport(options);
178
+ expect(transport).toBeDefined();
179
+ });
180
+ it("should handle minimal env when optional vars are missing", () => {
181
+ originalEnv = process.env;
182
+ process.env = {
183
+ PATH: "/usr/bin",
184
+ // HOME, USER, SHELL, etc. intentionally not set
185
+ };
186
+ const options = {
187
+ transportType: "stdio",
188
+ command: "node",
189
+ args: ["server.js"],
190
+ };
191
+ // Should still work with just PATH
192
+ const transport = createTransport(options);
193
+ expect(transport).toBeDefined();
194
+ });
195
+ it("should combine SDK defaults with minimal env vars", () => {
196
+ originalEnv = process.env;
197
+ process.env = {
198
+ PATH: "/custom/path",
199
+ HOME: "/home/custom",
200
+ NODE_ENV: "test",
201
+ };
202
+ const options = {
203
+ transportType: "stdio",
204
+ command: "node",
205
+ args: ["server.js"],
206
+ };
207
+ // getDefaultEnvironment() from SDK is merged with getMinimalEnv()
208
+ const transport = createTransport(options);
209
+ expect(transport).toBeDefined();
210
+ });
211
+ });
71
212
  describe("HTTP Transport", () => {
72
213
  it("should create transport for valid HTTP options", () => {
73
214
  const options = {