@bryan-thompson/inspector-assessment-cli 1.25.0 → 1.25.4
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,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assess-Full CLI Unit Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for CLI argument parsing concepts, config building, and validation logic.
|
|
5
|
+
* These tests focus on pure logic that can be tested without mocking complex
|
|
6
|
+
* external dependencies. For integration testing of the full CLI, use the
|
|
7
|
+
* actual CLI binary.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, expect } from "@jest/globals";
|
|
10
|
+
import * as path from "path";
|
|
11
|
+
/**
|
|
12
|
+
* Pure function tests - these test logic concepts used in the CLI
|
|
13
|
+
* without needing to import the actual module (which has side effects)
|
|
14
|
+
*/
|
|
15
|
+
describe("CLI Argument Parsing Concepts", () => {
|
|
16
|
+
describe("Profile Flag Parsing", () => {
|
|
17
|
+
const VALID_PROFILES = ["quick", "security", "compliance", "full"];
|
|
18
|
+
function parseProfile(args) {
|
|
19
|
+
const profileIndex = args.indexOf("--profile");
|
|
20
|
+
if (profileIndex === -1) {
|
|
21
|
+
return {}; // No profile specified
|
|
22
|
+
}
|
|
23
|
+
const profileValue = args[profileIndex + 1];
|
|
24
|
+
if (!profileValue || profileValue.startsWith("--")) {
|
|
25
|
+
return { error: "Missing profile value" };
|
|
26
|
+
}
|
|
27
|
+
if (!VALID_PROFILES.includes(profileValue)) {
|
|
28
|
+
return { error: `Invalid profile: ${profileValue}` };
|
|
29
|
+
}
|
|
30
|
+
return { profile: profileValue };
|
|
31
|
+
}
|
|
32
|
+
it("should parse valid profile flags", () => {
|
|
33
|
+
expect(parseProfile(["--profile", "quick"])).toEqual({
|
|
34
|
+
profile: "quick",
|
|
35
|
+
});
|
|
36
|
+
expect(parseProfile(["--profile", "security"])).toEqual({
|
|
37
|
+
profile: "security",
|
|
38
|
+
});
|
|
39
|
+
expect(parseProfile(["--profile", "compliance"])).toEqual({
|
|
40
|
+
profile: "compliance",
|
|
41
|
+
});
|
|
42
|
+
expect(parseProfile(["--profile", "full"])).toEqual({ profile: "full" });
|
|
43
|
+
});
|
|
44
|
+
it("should handle missing profile value", () => {
|
|
45
|
+
expect(parseProfile(["--profile"])).toEqual({
|
|
46
|
+
error: "Missing profile value",
|
|
47
|
+
});
|
|
48
|
+
expect(parseProfile(["--profile", "--other"])).toEqual({
|
|
49
|
+
error: "Missing profile value",
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
it("should reject invalid profile names", () => {
|
|
53
|
+
expect(parseProfile(["--profile", "invalid"])).toEqual({
|
|
54
|
+
error: "Invalid profile: invalid",
|
|
55
|
+
});
|
|
56
|
+
expect(parseProfile(["--profile", "QUICK"])).toEqual({
|
|
57
|
+
error: "Invalid profile: QUICK",
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
it("should return empty when no profile specified", () => {
|
|
61
|
+
expect(parseProfile([])).toEqual({});
|
|
62
|
+
expect(parseProfile(["--server", "test"])).toEqual({});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe("Module List Parsing", () => {
|
|
66
|
+
function parseModules(input) {
|
|
67
|
+
return input
|
|
68
|
+
.split(",")
|
|
69
|
+
.map((n) => n.trim())
|
|
70
|
+
.filter(Boolean);
|
|
71
|
+
}
|
|
72
|
+
it("should parse comma-separated modules", () => {
|
|
73
|
+
expect(parseModules("security,functionality")).toEqual([
|
|
74
|
+
"security",
|
|
75
|
+
"functionality",
|
|
76
|
+
]);
|
|
77
|
+
});
|
|
78
|
+
it("should trim whitespace", () => {
|
|
79
|
+
expect(parseModules("security , functionality , temporal")).toEqual([
|
|
80
|
+
"security",
|
|
81
|
+
"functionality",
|
|
82
|
+
"temporal",
|
|
83
|
+
]);
|
|
84
|
+
});
|
|
85
|
+
it("should filter empty values", () => {
|
|
86
|
+
expect(parseModules("security,,functionality")).toEqual([
|
|
87
|
+
"security",
|
|
88
|
+
"functionality",
|
|
89
|
+
]);
|
|
90
|
+
});
|
|
91
|
+
it("should handle single module", () => {
|
|
92
|
+
expect(parseModules("security")).toEqual(["security"]);
|
|
93
|
+
});
|
|
94
|
+
it("should handle empty input", () => {
|
|
95
|
+
expect(parseModules("")).toEqual([]);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
describe("Output Format Validation", () => {
|
|
99
|
+
const VALID_FORMATS = ["json", "markdown", "html"];
|
|
100
|
+
function validateFormat(format) {
|
|
101
|
+
return VALID_FORMATS.includes(format);
|
|
102
|
+
}
|
|
103
|
+
it("should accept valid formats", () => {
|
|
104
|
+
expect(validateFormat("json")).toBe(true);
|
|
105
|
+
expect(validateFormat("markdown")).toBe(true);
|
|
106
|
+
expect(validateFormat("html")).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
it("should reject invalid formats", () => {
|
|
109
|
+
expect(validateFormat("xml")).toBe(false);
|
|
110
|
+
expect(validateFormat("JSON")).toBe(false);
|
|
111
|
+
expect(validateFormat("pdf")).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
describe("Config File Handling", () => {
|
|
116
|
+
describe("Config Parsing", () => {
|
|
117
|
+
function validateConfig(config) {
|
|
118
|
+
const transport = config.transport || "stdio";
|
|
119
|
+
if (transport === "stdio") {
|
|
120
|
+
if (!config.command) {
|
|
121
|
+
return { valid: false, error: "STDIO transport requires command" };
|
|
122
|
+
}
|
|
123
|
+
return { valid: true };
|
|
124
|
+
}
|
|
125
|
+
if (transport === "http" || transport === "sse") {
|
|
126
|
+
if (!config.url) {
|
|
127
|
+
return {
|
|
128
|
+
valid: false,
|
|
129
|
+
error: `${transport.toUpperCase()} transport requires url`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return { valid: true };
|
|
133
|
+
}
|
|
134
|
+
return { valid: false, error: `Unknown transport: ${transport}` };
|
|
135
|
+
}
|
|
136
|
+
it("should validate STDIO config", () => {
|
|
137
|
+
expect(validateConfig({
|
|
138
|
+
transport: "stdio",
|
|
139
|
+
command: "python",
|
|
140
|
+
args: ["-m", "server"],
|
|
141
|
+
})).toEqual({ valid: true });
|
|
142
|
+
});
|
|
143
|
+
it("should require command for STDIO", () => {
|
|
144
|
+
expect(validateConfig({ transport: "stdio" })).toEqual({
|
|
145
|
+
valid: false,
|
|
146
|
+
error: "STDIO transport requires command",
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
it("should validate HTTP config", () => {
|
|
150
|
+
expect(validateConfig({ transport: "http", url: "http://localhost:3000/mcp" })).toEqual({ valid: true });
|
|
151
|
+
});
|
|
152
|
+
it("should require url for HTTP", () => {
|
|
153
|
+
expect(validateConfig({ transport: "http" })).toEqual({
|
|
154
|
+
valid: false,
|
|
155
|
+
error: "HTTP transport requires url",
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
it("should validate SSE config", () => {
|
|
159
|
+
expect(validateConfig({ transport: "sse", url: "http://localhost:3000/sse" })).toEqual({ valid: true });
|
|
160
|
+
});
|
|
161
|
+
it("should require url for SSE", () => {
|
|
162
|
+
expect(validateConfig({ transport: "sse" })).toEqual({
|
|
163
|
+
valid: false,
|
|
164
|
+
error: "SSE transport requires url",
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
it("should default to STDIO when transport not specified", () => {
|
|
168
|
+
expect(validateConfig({ command: "node" })).toEqual({ valid: true });
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
describe("Config File Reading", () => {
|
|
172
|
+
it("should detect JSON file by extension", () => {
|
|
173
|
+
const filePath = "/tmp/config.json";
|
|
174
|
+
expect(path.extname(filePath)).toBe(".json");
|
|
175
|
+
});
|
|
176
|
+
it("should handle paths with multiple dots", () => {
|
|
177
|
+
const filePath = "/tmp/my.server.config.json";
|
|
178
|
+
expect(path.extname(filePath)).toBe(".json");
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
describe("Exit Code Logic", () => {
|
|
183
|
+
function determineExitCode(result) {
|
|
184
|
+
if (result.overallStatus === "FAIL")
|
|
185
|
+
return 1;
|
|
186
|
+
if (result.security?.vulnerabilities &&
|
|
187
|
+
result.security.vulnerabilities.length > 0) {
|
|
188
|
+
return 1;
|
|
189
|
+
}
|
|
190
|
+
return 0;
|
|
191
|
+
}
|
|
192
|
+
it("should return 0 for PASS with no vulnerabilities", () => {
|
|
193
|
+
expect(determineExitCode({ overallStatus: "PASS" })).toBe(0);
|
|
194
|
+
});
|
|
195
|
+
it("should return 1 for FAIL status", () => {
|
|
196
|
+
expect(determineExitCode({ overallStatus: "FAIL" })).toBe(1);
|
|
197
|
+
});
|
|
198
|
+
it("should return 1 when vulnerabilities exist", () => {
|
|
199
|
+
expect(determineExitCode({
|
|
200
|
+
overallStatus: "PASS",
|
|
201
|
+
security: { vulnerabilities: [{ type: "injection" }] },
|
|
202
|
+
})).toBe(1);
|
|
203
|
+
});
|
|
204
|
+
it("should return 0 for PASS with empty vulnerabilities", () => {
|
|
205
|
+
expect(determineExitCode({
|
|
206
|
+
overallStatus: "PASS",
|
|
207
|
+
security: { vulnerabilities: [] },
|
|
208
|
+
})).toBe(0);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
describe("State Management Concepts", () => {
|
|
212
|
+
describe("Resume Flag Parsing", () => {
|
|
213
|
+
function getResumeMode(args) {
|
|
214
|
+
if (args.includes("--no-resume"))
|
|
215
|
+
return "no-resume";
|
|
216
|
+
if (args.includes("--resume"))
|
|
217
|
+
return "resume";
|
|
218
|
+
return "default";
|
|
219
|
+
}
|
|
220
|
+
it("should detect --resume flag", () => {
|
|
221
|
+
expect(getResumeMode(["--resume"])).toBe("resume");
|
|
222
|
+
expect(getResumeMode(["--server", "test", "--resume"])).toBe("resume");
|
|
223
|
+
});
|
|
224
|
+
it("should detect --no-resume flag", () => {
|
|
225
|
+
expect(getResumeMode(["--no-resume"])).toBe("no-resume");
|
|
226
|
+
});
|
|
227
|
+
it("should return default when neither specified", () => {
|
|
228
|
+
expect(getResumeMode([])).toBe("default");
|
|
229
|
+
expect(getResumeMode(["--server", "test"])).toBe("default");
|
|
230
|
+
});
|
|
231
|
+
it("should handle --no-resume even when --resume also present", () => {
|
|
232
|
+
// --no-resume takes precedence
|
|
233
|
+
expect(getResumeMode(["--resume", "--no-resume"])).toBe("no-resume");
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
describe("State File Path Generation", () => {
|
|
237
|
+
function getStateFilePath(serverName, outputDir) {
|
|
238
|
+
const dir = outputDir || "/tmp";
|
|
239
|
+
return path.join(dir, `.${serverName}-assessment-state.json`);
|
|
240
|
+
}
|
|
241
|
+
it("should generate state file path with server name", () => {
|
|
242
|
+
expect(getStateFilePath("test-server")).toBe("/tmp/.test-server-assessment-state.json");
|
|
243
|
+
});
|
|
244
|
+
it("should use custom output directory", () => {
|
|
245
|
+
expect(getStateFilePath("test-server", "/home/user/results")).toBe("/home/user/results/.test-server-assessment-state.json");
|
|
246
|
+
});
|
|
247
|
+
it("should handle server names with special characters", () => {
|
|
248
|
+
expect(getStateFilePath("my-mcp-server")).toBe("/tmp/.my-mcp-server-assessment-state.json");
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
describe("Output Path Generation", () => {
|
|
253
|
+
describe("Default Output Paths", () => {
|
|
254
|
+
function getDefaultOutputPath(serverName, format) {
|
|
255
|
+
const ext = format === "markdown" ? ".md" : `.${format}`;
|
|
256
|
+
return `/tmp/inspector-assessment-${serverName}${ext}`;
|
|
257
|
+
}
|
|
258
|
+
it("should generate JSON output path", () => {
|
|
259
|
+
expect(getDefaultOutputPath("my-server", "json")).toBe("/tmp/inspector-assessment-my-server.json");
|
|
260
|
+
});
|
|
261
|
+
it("should generate Markdown output path", () => {
|
|
262
|
+
expect(getDefaultOutputPath("my-server", "markdown")).toBe("/tmp/inspector-assessment-my-server.md");
|
|
263
|
+
});
|
|
264
|
+
it("should generate HTML output path", () => {
|
|
265
|
+
expect(getDefaultOutputPath("my-server", "html")).toBe("/tmp/inspector-assessment-my-server.html");
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
describe("Custom Output Paths", () => {
|
|
269
|
+
function resolveOutputPath(specified, serverName, format) {
|
|
270
|
+
if (specified) {
|
|
271
|
+
// If directory, append filename
|
|
272
|
+
if (!path.extname(specified)) {
|
|
273
|
+
const ext = format === "markdown" ? ".md" : `.${format}`;
|
|
274
|
+
return path.join(specified, `inspector-assessment-${serverName}${ext}`);
|
|
275
|
+
}
|
|
276
|
+
return specified;
|
|
277
|
+
}
|
|
278
|
+
const ext = format === "markdown" ? ".md" : `.${format}`;
|
|
279
|
+
return `/tmp/inspector-assessment-${serverName}${ext}`;
|
|
280
|
+
}
|
|
281
|
+
it("should use specified path directly if it has extension", () => {
|
|
282
|
+
expect(resolveOutputPath("/home/user/results.json", "server", "json")).toBe("/home/user/results.json");
|
|
283
|
+
});
|
|
284
|
+
it("should append filename if path is a directory", () => {
|
|
285
|
+
expect(resolveOutputPath("/home/user/results", "server", "json")).toBe("/home/user/results/inspector-assessment-server.json");
|
|
286
|
+
});
|
|
287
|
+
it("should use default when not specified", () => {
|
|
288
|
+
expect(resolveOutputPath(undefined, "server", "json")).toBe("/tmp/inspector-assessment-server.json");
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
describe("Boolean Flag Parsing", () => {
|
|
293
|
+
function parseBooleanFlags(args) {
|
|
294
|
+
const flags = {
|
|
295
|
+
verbose: false,
|
|
296
|
+
silent: false,
|
|
297
|
+
claudeEnabled: false,
|
|
298
|
+
includePolicy: false,
|
|
299
|
+
preflight: false,
|
|
300
|
+
};
|
|
301
|
+
if (args.includes("--verbose") || args.includes("-v"))
|
|
302
|
+
flags.verbose = true;
|
|
303
|
+
if (args.includes("--silent") || args.includes("-s"))
|
|
304
|
+
flags.silent = true;
|
|
305
|
+
if (args.includes("--claude-enabled"))
|
|
306
|
+
flags.claudeEnabled = true;
|
|
307
|
+
if (args.includes("--include-policy"))
|
|
308
|
+
flags.includePolicy = true;
|
|
309
|
+
if (args.includes("--preflight"))
|
|
310
|
+
flags.preflight = true;
|
|
311
|
+
return flags;
|
|
312
|
+
}
|
|
313
|
+
it("should parse verbose flag", () => {
|
|
314
|
+
expect(parseBooleanFlags(["--verbose"]).verbose).toBe(true);
|
|
315
|
+
expect(parseBooleanFlags(["-v"]).verbose).toBe(true);
|
|
316
|
+
});
|
|
317
|
+
it("should parse silent flag", () => {
|
|
318
|
+
expect(parseBooleanFlags(["--silent"]).silent).toBe(true);
|
|
319
|
+
expect(parseBooleanFlags(["-s"]).silent).toBe(true);
|
|
320
|
+
});
|
|
321
|
+
it("should parse claude-enabled flag", () => {
|
|
322
|
+
expect(parseBooleanFlags(["--claude-enabled"]).claudeEnabled).toBe(true);
|
|
323
|
+
});
|
|
324
|
+
it("should parse include-policy flag", () => {
|
|
325
|
+
expect(parseBooleanFlags(["--include-policy"]).includePolicy).toBe(true);
|
|
326
|
+
});
|
|
327
|
+
it("should parse preflight flag", () => {
|
|
328
|
+
expect(parseBooleanFlags(["--preflight"]).preflight).toBe(true);
|
|
329
|
+
});
|
|
330
|
+
it("should handle multiple flags", () => {
|
|
331
|
+
const flags = parseBooleanFlags([
|
|
332
|
+
"--verbose",
|
|
333
|
+
"--include-policy",
|
|
334
|
+
"--preflight",
|
|
335
|
+
]);
|
|
336
|
+
expect(flags.verbose).toBe(true);
|
|
337
|
+
expect(flags.includePolicy).toBe(true);
|
|
338
|
+
expect(flags.preflight).toBe(true);
|
|
339
|
+
expect(flags.silent).toBe(false);
|
|
340
|
+
});
|
|
341
|
+
it("should return all false when no flags", () => {
|
|
342
|
+
const flags = parseBooleanFlags([]);
|
|
343
|
+
expect(Object.values(flags).every((v) => v === false)).toBe(true);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSONL Events Module Unit Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for JSONL event emission functions used for real-time monitoring.
|
|
5
|
+
*/
|
|
6
|
+
import { jest, describe, it, expect, beforeEach, afterEach, } from "@jest/globals";
|
|
7
|
+
import { emitJSONL, emitServerConnected, emitToolDiscovered, emitToolsDiscoveryComplete, emitAssessmentComplete, emitTestBatch, emitVulnerabilityFound, emitAnnotationMissing, emitAnnotationMisaligned, emitAnnotationReviewRecommended, emitAnnotationAligned, emitModulesConfigured, extractToolParams, } from "../lib/jsonl-events.js";
|
|
8
|
+
describe("JSONL Event Emission", () => {
|
|
9
|
+
let consoleErrorSpy;
|
|
10
|
+
let emittedEvents;
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
emittedEvents = [];
|
|
13
|
+
consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(((msg) => {
|
|
14
|
+
try {
|
|
15
|
+
emittedEvents.push(JSON.parse(msg));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Not JSON, ignore
|
|
19
|
+
}
|
|
20
|
+
}));
|
|
21
|
+
});
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
consoleErrorSpy.mockRestore();
|
|
24
|
+
});
|
|
25
|
+
describe("emitJSONL", () => {
|
|
26
|
+
it("should emit event to stderr as JSON", () => {
|
|
27
|
+
emitJSONL({ event: "test", data: "value" });
|
|
28
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
29
|
+
expect(emittedEvents[0]).toHaveProperty("event", "test");
|
|
30
|
+
expect(emittedEvents[0]).toHaveProperty("data", "value");
|
|
31
|
+
});
|
|
32
|
+
it("should include version field", () => {
|
|
33
|
+
emitJSONL({ event: "test" });
|
|
34
|
+
expect(emittedEvents[0]).toHaveProperty("version");
|
|
35
|
+
});
|
|
36
|
+
it("should handle complex nested objects", () => {
|
|
37
|
+
emitJSONL({
|
|
38
|
+
event: "complex",
|
|
39
|
+
nested: { a: 1, b: [2, 3] },
|
|
40
|
+
});
|
|
41
|
+
expect(emittedEvents[0]).toHaveProperty("nested.a", 1);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
describe("emitServerConnected", () => {
|
|
45
|
+
it("should emit server_connected event", () => {
|
|
46
|
+
emitServerConnected("test-server", "http");
|
|
47
|
+
expect(emittedEvents[0]).toHaveProperty("event", "server_connected");
|
|
48
|
+
expect(emittedEvents[0]).toHaveProperty("serverName", "test-server");
|
|
49
|
+
expect(emittedEvents[0]).toHaveProperty("transport", "http");
|
|
50
|
+
});
|
|
51
|
+
it("should handle different transport types", () => {
|
|
52
|
+
emitServerConnected("server1", "stdio");
|
|
53
|
+
emitServerConnected("server2", "sse");
|
|
54
|
+
emitServerConnected("server3", "http");
|
|
55
|
+
expect(emittedEvents[0]).toHaveProperty("transport", "stdio");
|
|
56
|
+
expect(emittedEvents[1]).toHaveProperty("transport", "sse");
|
|
57
|
+
expect(emittedEvents[2]).toHaveProperty("transport", "http");
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe("emitToolDiscovered", () => {
|
|
61
|
+
it("should emit tool_discovered event with basic info", () => {
|
|
62
|
+
const tool = {
|
|
63
|
+
name: "test_tool",
|
|
64
|
+
description: "A test tool",
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: "object",
|
|
67
|
+
properties: {},
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
emitToolDiscovered(tool);
|
|
71
|
+
expect(emittedEvents[0]).toHaveProperty("event", "tool_discovered");
|
|
72
|
+
expect(emittedEvents[0]).toHaveProperty("name", "test_tool");
|
|
73
|
+
expect(emittedEvents[0]).toHaveProperty("description", "A test tool");
|
|
74
|
+
});
|
|
75
|
+
it("should handle tool without description", () => {
|
|
76
|
+
const tool = {
|
|
77
|
+
name: "no_desc_tool",
|
|
78
|
+
inputSchema: { type: "object" },
|
|
79
|
+
};
|
|
80
|
+
emitToolDiscovered(tool);
|
|
81
|
+
expect(emittedEvents[0]).toHaveProperty("name", "no_desc_tool");
|
|
82
|
+
expect(emittedEvents[0]).toHaveProperty("description", null);
|
|
83
|
+
});
|
|
84
|
+
it("should extract parameters from inputSchema", () => {
|
|
85
|
+
const tool = {
|
|
86
|
+
name: "param_tool",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: "object",
|
|
89
|
+
properties: {
|
|
90
|
+
query: { type: "string", description: "Search query" },
|
|
91
|
+
limit: { type: "number" },
|
|
92
|
+
},
|
|
93
|
+
required: ["query"],
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
emitToolDiscovered(tool);
|
|
97
|
+
const params = emittedEvents[0].params;
|
|
98
|
+
expect(params.length).toBe(2);
|
|
99
|
+
expect(params.find((p) => p.name === "query")).toMatchObject({
|
|
100
|
+
name: "query",
|
|
101
|
+
type: "string",
|
|
102
|
+
required: true,
|
|
103
|
+
description: "Search query",
|
|
104
|
+
});
|
|
105
|
+
expect(params.find((p) => p.name === "limit")).toMatchObject({
|
|
106
|
+
name: "limit",
|
|
107
|
+
type: "number",
|
|
108
|
+
required: false,
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
it("should include annotations when present", () => {
|
|
112
|
+
const tool = {
|
|
113
|
+
name: "annotated_tool",
|
|
114
|
+
inputSchema: { type: "object" },
|
|
115
|
+
annotations: {
|
|
116
|
+
readOnlyHint: true,
|
|
117
|
+
destructiveHint: false,
|
|
118
|
+
idempotentHint: true,
|
|
119
|
+
openWorldHint: false,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
emitToolDiscovered(tool);
|
|
123
|
+
const annotations = emittedEvents[0].annotations;
|
|
124
|
+
expect(annotations).not.toBeNull();
|
|
125
|
+
expect(annotations.readOnlyHint).toBe(true);
|
|
126
|
+
expect(annotations.destructiveHint).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
it("should have null annotations when not present", () => {
|
|
129
|
+
const tool = {
|
|
130
|
+
name: "no_annotations",
|
|
131
|
+
inputSchema: { type: "object" },
|
|
132
|
+
};
|
|
133
|
+
emitToolDiscovered(tool);
|
|
134
|
+
expect(emittedEvents[0].annotations).toBeNull();
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
describe("emitToolsDiscoveryComplete", () => {
|
|
138
|
+
it("should emit tools_discovery_complete with count", () => {
|
|
139
|
+
emitToolsDiscoveryComplete(15);
|
|
140
|
+
expect(emittedEvents[0]).toHaveProperty("event", "tools_discovery_complete");
|
|
141
|
+
expect(emittedEvents[0]).toHaveProperty("count", 15);
|
|
142
|
+
});
|
|
143
|
+
it("should handle zero tools", () => {
|
|
144
|
+
emitToolsDiscoveryComplete(0);
|
|
145
|
+
expect(emittedEvents[0]).toHaveProperty("count", 0);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
describe("emitAssessmentComplete", () => {
|
|
149
|
+
it("should emit assessment_complete with all fields", () => {
|
|
150
|
+
emitAssessmentComplete("PASS", 1500, 120000, "/tmp/results.json");
|
|
151
|
+
expect(emittedEvents[0]).toHaveProperty("event", "assessment_complete");
|
|
152
|
+
expect(emittedEvents[0]).toHaveProperty("overallStatus", "PASS");
|
|
153
|
+
expect(emittedEvents[0]).toHaveProperty("totalTests", 1500);
|
|
154
|
+
expect(emittedEvents[0]).toHaveProperty("executionTime", 120000);
|
|
155
|
+
expect(emittedEvents[0]).toHaveProperty("outputPath", "/tmp/results.json");
|
|
156
|
+
});
|
|
157
|
+
it("should handle FAIL status", () => {
|
|
158
|
+
emitAssessmentComplete("FAIL", 200, 60000, "/tmp/fail.json");
|
|
159
|
+
expect(emittedEvents[0]).toHaveProperty("overallStatus", "FAIL");
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
describe("emitTestBatch", () => {
|
|
163
|
+
it("should emit test_batch with progress info", () => {
|
|
164
|
+
emitTestBatch("security", 100, 500, 50, 30000);
|
|
165
|
+
expect(emittedEvents[0]).toHaveProperty("event", "test_batch");
|
|
166
|
+
expect(emittedEvents[0]).toHaveProperty("module", "security");
|
|
167
|
+
expect(emittedEvents[0]).toHaveProperty("completed", 100);
|
|
168
|
+
expect(emittedEvents[0]).toHaveProperty("total", 500);
|
|
169
|
+
expect(emittedEvents[0]).toHaveProperty("batchSize", 50);
|
|
170
|
+
expect(emittedEvents[0]).toHaveProperty("elapsed", 30000);
|
|
171
|
+
});
|
|
172
|
+
it("should handle final batch", () => {
|
|
173
|
+
emitTestBatch("functionality", 100, 100, 10, 5000);
|
|
174
|
+
expect(emittedEvents[0]).toHaveProperty("completed", 100);
|
|
175
|
+
expect(emittedEvents[0]).toHaveProperty("total", 100);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
describe("emitVulnerabilityFound", () => {
|
|
179
|
+
it("should emit vulnerability_found with all fields", () => {
|
|
180
|
+
emitVulnerabilityFound("exec_tool", "Command Injection", "high", "Response executed shell command", "HIGH", true, "; rm -rf /");
|
|
181
|
+
expect(emittedEvents[0]).toHaveProperty("event", "vulnerability_found");
|
|
182
|
+
expect(emittedEvents[0]).toHaveProperty("tool", "exec_tool");
|
|
183
|
+
expect(emittedEvents[0]).toHaveProperty("pattern", "Command Injection");
|
|
184
|
+
expect(emittedEvents[0]).toHaveProperty("confidence", "high");
|
|
185
|
+
expect(emittedEvents[0]).toHaveProperty("evidence", "Response executed shell command");
|
|
186
|
+
expect(emittedEvents[0]).toHaveProperty("riskLevel", "HIGH");
|
|
187
|
+
expect(emittedEvents[0]).toHaveProperty("requiresReview", true);
|
|
188
|
+
expect(emittedEvents[0]).toHaveProperty("payload", "; rm -rf /");
|
|
189
|
+
});
|
|
190
|
+
it("should omit payload when not provided", () => {
|
|
191
|
+
emitVulnerabilityFound("tool", "SQLi", "medium", "evidence", "MEDIUM", false);
|
|
192
|
+
expect(emittedEvents[0]).not.toHaveProperty("payload");
|
|
193
|
+
});
|
|
194
|
+
it("should handle different confidence levels", () => {
|
|
195
|
+
emitVulnerabilityFound("t1", "p1", "high", "e1", "HIGH", true);
|
|
196
|
+
emitVulnerabilityFound("t2", "p2", "medium", "e2", "MEDIUM", false);
|
|
197
|
+
emitVulnerabilityFound("t3", "p3", "low", "e3", "LOW", false);
|
|
198
|
+
expect(emittedEvents[0]).toHaveProperty("confidence", "high");
|
|
199
|
+
expect(emittedEvents[1]).toHaveProperty("confidence", "medium");
|
|
200
|
+
expect(emittedEvents[2]).toHaveProperty("confidence", "low");
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
describe("emitAnnotationMissing", () => {
|
|
204
|
+
it("should emit annotation_missing with tool info", () => {
|
|
205
|
+
const params = [
|
|
206
|
+
{ name: "file", type: "string", required: true },
|
|
207
|
+
];
|
|
208
|
+
emitAnnotationMissing("delete_file", "Delete File", "Deletes a file", params, {
|
|
209
|
+
expectedReadOnly: false,
|
|
210
|
+
expectedDestructive: true,
|
|
211
|
+
reason: "delete operation implies destructive",
|
|
212
|
+
});
|
|
213
|
+
expect(emittedEvents[0]).toHaveProperty("event", "annotation_missing");
|
|
214
|
+
expect(emittedEvents[0]).toHaveProperty("tool", "delete_file");
|
|
215
|
+
expect(emittedEvents[0]).toHaveProperty("title", "Delete File");
|
|
216
|
+
expect(emittedEvents[0]).toHaveProperty("description", "Deletes a file");
|
|
217
|
+
expect(emittedEvents[0]).toHaveProperty("parameters", params);
|
|
218
|
+
expect(emittedEvents[0]).toHaveProperty("inferredBehavior");
|
|
219
|
+
});
|
|
220
|
+
it("should omit optional fields when undefined", () => {
|
|
221
|
+
emitAnnotationMissing("tool", undefined, undefined, [], {
|
|
222
|
+
expectedReadOnly: true,
|
|
223
|
+
expectedDestructive: false,
|
|
224
|
+
reason: "test",
|
|
225
|
+
});
|
|
226
|
+
expect(emittedEvents[0]).not.toHaveProperty("title");
|
|
227
|
+
expect(emittedEvents[0]).not.toHaveProperty("description");
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
describe("emitAnnotationMisaligned", () => {
|
|
231
|
+
it("should emit annotation_misaligned with all fields", () => {
|
|
232
|
+
const params = [];
|
|
233
|
+
emitAnnotationMisaligned("write_tool", "Write Data", "Writes to disk", params, "readOnlyHint", true, false, 0.95, "Tool performs write operations");
|
|
234
|
+
expect(emittedEvents[0]).toHaveProperty("event", "annotation_misaligned");
|
|
235
|
+
expect(emittedEvents[0]).toHaveProperty("tool", "write_tool");
|
|
236
|
+
expect(emittedEvents[0]).toHaveProperty("field", "readOnlyHint");
|
|
237
|
+
expect(emittedEvents[0]).toHaveProperty("actual", true);
|
|
238
|
+
expect(emittedEvents[0]).toHaveProperty("expected", false);
|
|
239
|
+
expect(emittedEvents[0]).toHaveProperty("confidence", 0.95);
|
|
240
|
+
expect(emittedEvents[0]).toHaveProperty("reason", "Tool performs write operations");
|
|
241
|
+
});
|
|
242
|
+
it("should handle undefined actual value", () => {
|
|
243
|
+
emitAnnotationMisaligned("tool", undefined, undefined, [], "destructiveHint", undefined, true, 0.8, "reason");
|
|
244
|
+
// JSON.stringify omits undefined values, so the property won't exist
|
|
245
|
+
expect(emittedEvents[0]).not.toHaveProperty("actual");
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
describe("emitAnnotationReviewRecommended", () => {
|
|
249
|
+
it("should emit annotation_review_recommended with all fields", () => {
|
|
250
|
+
emitAnnotationReviewRecommended("cache_tool", "Cache Manager", "Manages cache", [], "readOnlyHint", undefined, false, "medium", true, "Cache operations are ambiguous");
|
|
251
|
+
expect(emittedEvents[0]).toHaveProperty("event", "annotation_review_recommended");
|
|
252
|
+
expect(emittedEvents[0]).toHaveProperty("tool", "cache_tool");
|
|
253
|
+
expect(emittedEvents[0]).toHaveProperty("confidence", "medium");
|
|
254
|
+
expect(emittedEvents[0]).toHaveProperty("isAmbiguous", true);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
describe("emitAnnotationAligned", () => {
|
|
258
|
+
it("should emit annotation_aligned with annotations", () => {
|
|
259
|
+
emitAnnotationAligned("read_tool", "high", {
|
|
260
|
+
readOnlyHint: true,
|
|
261
|
+
destructiveHint: false,
|
|
262
|
+
});
|
|
263
|
+
expect(emittedEvents[0]).toHaveProperty("event", "annotation_aligned");
|
|
264
|
+
expect(emittedEvents[0]).toHaveProperty("tool", "read_tool");
|
|
265
|
+
expect(emittedEvents[0]).toHaveProperty("confidence", "high");
|
|
266
|
+
expect(emittedEvents[0]).toHaveProperty("annotations");
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
describe("emitModulesConfigured", () => {
|
|
270
|
+
it("should emit modules_configured with enabled and skipped", () => {
|
|
271
|
+
emitModulesConfigured(["security", "functionality"], ["temporal"], "skip-modules");
|
|
272
|
+
expect(emittedEvents[0]).toHaveProperty("event", "modules_configured");
|
|
273
|
+
expect(emittedEvents[0]).toHaveProperty("enabled", [
|
|
274
|
+
"security",
|
|
275
|
+
"functionality",
|
|
276
|
+
]);
|
|
277
|
+
expect(emittedEvents[0]).toHaveProperty("skipped", ["temporal"]);
|
|
278
|
+
expect(emittedEvents[0]).toHaveProperty("reason", "skip-modules");
|
|
279
|
+
});
|
|
280
|
+
it("should handle only-modules reason", () => {
|
|
281
|
+
emitModulesConfigured(["security"], [], "only-modules");
|
|
282
|
+
expect(emittedEvents[0]).toHaveProperty("reason", "only-modules");
|
|
283
|
+
});
|
|
284
|
+
it("should handle default reason", () => {
|
|
285
|
+
emitModulesConfigured(["security", "functionality"], [], "default");
|
|
286
|
+
expect(emittedEvents[0]).toHaveProperty("reason", "default");
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
describe("extractToolParams", () => {
|
|
291
|
+
it("should return empty array for null schema", () => {
|
|
292
|
+
expect(extractToolParams(null)).toEqual([]);
|
|
293
|
+
});
|
|
294
|
+
it("should return empty array for undefined schema", () => {
|
|
295
|
+
expect(extractToolParams(undefined)).toEqual([]);
|
|
296
|
+
});
|
|
297
|
+
it("should return empty array for non-object schema", () => {
|
|
298
|
+
expect(extractToolParams("string")).toEqual([]);
|
|
299
|
+
});
|
|
300
|
+
it("should return empty array for schema without properties", () => {
|
|
301
|
+
expect(extractToolParams({ type: "object" })).toEqual([]);
|
|
302
|
+
});
|
|
303
|
+
it("should extract parameters with all fields", () => {
|
|
304
|
+
const schema = {
|
|
305
|
+
type: "object",
|
|
306
|
+
properties: {
|
|
307
|
+
name: { type: "string", description: "User name" },
|
|
308
|
+
age: { type: "number" },
|
|
309
|
+
},
|
|
310
|
+
required: ["name"],
|
|
311
|
+
};
|
|
312
|
+
const params = extractToolParams(schema);
|
|
313
|
+
expect(params.length).toBe(2);
|
|
314
|
+
const nameParam = params.find((p) => p.name === "name");
|
|
315
|
+
expect(nameParam).toEqual({
|
|
316
|
+
name: "name",
|
|
317
|
+
type: "string",
|
|
318
|
+
required: true,
|
|
319
|
+
description: "User name",
|
|
320
|
+
});
|
|
321
|
+
const ageParam = params.find((p) => p.name === "age");
|
|
322
|
+
expect(ageParam).toEqual({
|
|
323
|
+
name: "age",
|
|
324
|
+
type: "number",
|
|
325
|
+
required: false,
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
it("should handle schema without required array", () => {
|
|
329
|
+
const schema = {
|
|
330
|
+
type: "object",
|
|
331
|
+
properties: {
|
|
332
|
+
optional: { type: "string" },
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
const params = extractToolParams(schema);
|
|
336
|
+
expect(params[0].required).toBe(false);
|
|
337
|
+
});
|
|
338
|
+
it("should default to 'any' type when not specified", () => {
|
|
339
|
+
const schema = {
|
|
340
|
+
type: "object",
|
|
341
|
+
properties: {
|
|
342
|
+
unknown: {},
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
const params = extractToolParams(schema);
|
|
346
|
+
expect(params[0].type).toBe("any");
|
|
347
|
+
});
|
|
348
|
+
it("should not include description when not present", () => {
|
|
349
|
+
const schema = {
|
|
350
|
+
type: "object",
|
|
351
|
+
properties: {
|
|
352
|
+
simple: { type: "boolean" },
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
const params = extractToolParams(schema);
|
|
356
|
+
expect(params[0]).not.toHaveProperty("description");
|
|
357
|
+
});
|
|
358
|
+
});
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile Module Unit Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for profile definitions, module resolution, and legacy config mapping.
|
|
5
|
+
*/
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
7
|
+
import { jest, describe, it, expect } from "@jest/globals";
|
|
8
|
+
import { ASSESSMENT_PROFILES, PROFILE_METADATA, MODULE_ALIASES, DEPRECATED_MODULES, TIER_1_CORE_SECURITY, TIER_2_COMPLIANCE, TIER_3_CAPABILITY, TIER_4_EXTENDED, ALL_MODULES, resolveModuleNames, getProfileModules, isValidProfileName, getProfileHelpText, mapLegacyConfigToModules, modulesToLegacyConfig, } from "../profiles.js";
|
|
9
|
+
describe("Profile Definitions", () => {
|
|
10
|
+
describe("Profile Constants", () => {
|
|
11
|
+
it("should have four profiles defined", () => {
|
|
12
|
+
const profiles = Object.keys(ASSESSMENT_PROFILES);
|
|
13
|
+
expect(profiles).toEqual(["quick", "security", "compliance", "full"]);
|
|
14
|
+
});
|
|
15
|
+
it("should have metadata for all profiles", () => {
|
|
16
|
+
const profileNames = Object.keys(ASSESSMENT_PROFILES);
|
|
17
|
+
const metadataNames = Object.keys(PROFILE_METADATA);
|
|
18
|
+
expect(metadataNames).toEqual(profileNames);
|
|
19
|
+
});
|
|
20
|
+
it("should have correct module counts in metadata", () => {
|
|
21
|
+
for (const [name, meta] of Object.entries(PROFILE_METADATA)) {
|
|
22
|
+
const profile = ASSESSMENT_PROFILES[name];
|
|
23
|
+
expect(meta.moduleCount).toBe(profile.length);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
describe("Tier Definitions", () => {
|
|
28
|
+
it("should have Tier 1 core security modules", () => {
|
|
29
|
+
expect(TIER_1_CORE_SECURITY).toContain("functionality");
|
|
30
|
+
expect(TIER_1_CORE_SECURITY).toContain("security");
|
|
31
|
+
expect(TIER_1_CORE_SECURITY).toContain("temporal");
|
|
32
|
+
expect(TIER_1_CORE_SECURITY).toContain("errorHandling");
|
|
33
|
+
expect(TIER_1_CORE_SECURITY).toContain("protocolCompliance");
|
|
34
|
+
expect(TIER_1_CORE_SECURITY).toContain("aupCompliance");
|
|
35
|
+
});
|
|
36
|
+
it("should have Tier 2 compliance modules", () => {
|
|
37
|
+
expect(TIER_2_COMPLIANCE).toContain("toolAnnotations");
|
|
38
|
+
expect(TIER_2_COMPLIANCE).toContain("prohibitedLibraries");
|
|
39
|
+
expect(TIER_2_COMPLIANCE).toContain("manifestValidation");
|
|
40
|
+
expect(TIER_2_COMPLIANCE).toContain("authentication");
|
|
41
|
+
});
|
|
42
|
+
it("should have Tier 3 capability modules", () => {
|
|
43
|
+
expect(TIER_3_CAPABILITY).toContain("resources");
|
|
44
|
+
expect(TIER_3_CAPABILITY).toContain("prompts");
|
|
45
|
+
expect(TIER_3_CAPABILITY).toContain("crossCapability");
|
|
46
|
+
});
|
|
47
|
+
it("should have Tier 4 extended modules", () => {
|
|
48
|
+
expect(TIER_4_EXTENDED).toContain("developerExperience");
|
|
49
|
+
expect(TIER_4_EXTENDED).toContain("portability");
|
|
50
|
+
expect(TIER_4_EXTENDED).toContain("externalAPIScanner");
|
|
51
|
+
});
|
|
52
|
+
it("should combine all tiers in ALL_MODULES", () => {
|
|
53
|
+
const expectedLength = TIER_1_CORE_SECURITY.length +
|
|
54
|
+
TIER_2_COMPLIANCE.length +
|
|
55
|
+
TIER_3_CAPABILITY.length +
|
|
56
|
+
TIER_4_EXTENDED.length;
|
|
57
|
+
expect(ALL_MODULES.length).toBe(expectedLength);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe("Profile Compositions", () => {
|
|
61
|
+
it("quick profile should have functionality and security only", () => {
|
|
62
|
+
expect(ASSESSMENT_PROFILES.quick).toEqual(["functionality", "security"]);
|
|
63
|
+
});
|
|
64
|
+
it("security profile should include all Tier 1 modules", () => {
|
|
65
|
+
for (const module of TIER_1_CORE_SECURITY) {
|
|
66
|
+
expect(ASSESSMENT_PROFILES.security).toContain(module);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
it("compliance profile should include Tier 1 and Tier 2", () => {
|
|
70
|
+
for (const module of TIER_1_CORE_SECURITY) {
|
|
71
|
+
expect(ASSESSMENT_PROFILES.compliance).toContain(module);
|
|
72
|
+
}
|
|
73
|
+
for (const module of TIER_2_COMPLIANCE) {
|
|
74
|
+
expect(ASSESSMENT_PROFILES.compliance).toContain(module);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
it("full profile should include all tiers", () => {
|
|
78
|
+
for (const module of ALL_MODULES) {
|
|
79
|
+
expect(ASSESSMENT_PROFILES.full).toContain(module);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
describe("Module Aliases", () => {
|
|
85
|
+
it("should map deprecated mcpSpecCompliance to protocolCompliance", () => {
|
|
86
|
+
expect(MODULE_ALIASES.mcpSpecCompliance).toBe("protocolCompliance");
|
|
87
|
+
});
|
|
88
|
+
it("should map deprecated protocolConformance to protocolCompliance", () => {
|
|
89
|
+
expect(MODULE_ALIASES.protocolConformance).toBe("protocolCompliance");
|
|
90
|
+
});
|
|
91
|
+
it("should map deprecated documentation to developerExperience", () => {
|
|
92
|
+
expect(MODULE_ALIASES.documentation).toBe("developerExperience");
|
|
93
|
+
});
|
|
94
|
+
it("should map deprecated usability to developerExperience", () => {
|
|
95
|
+
expect(MODULE_ALIASES.usability).toBe("developerExperience");
|
|
96
|
+
});
|
|
97
|
+
it("should have DEPRECATED_MODULES match MODULE_ALIASES keys", () => {
|
|
98
|
+
const aliasKeys = Object.keys(MODULE_ALIASES);
|
|
99
|
+
for (const key of aliasKeys) {
|
|
100
|
+
expect(DEPRECATED_MODULES.has(key)).toBe(true);
|
|
101
|
+
}
|
|
102
|
+
expect(DEPRECATED_MODULES.size).toBe(aliasKeys.length);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
describe("resolveModuleNames", () => {
|
|
106
|
+
it("should return unchanged names for non-deprecated modules", () => {
|
|
107
|
+
const modules = ["functionality", "security", "temporal"];
|
|
108
|
+
const result = resolveModuleNames(modules, false);
|
|
109
|
+
expect(result).toEqual(modules);
|
|
110
|
+
});
|
|
111
|
+
it("should replace deprecated module names with aliases", () => {
|
|
112
|
+
const modules = ["mcpSpecCompliance", "documentation"];
|
|
113
|
+
const result = resolveModuleNames(modules, false);
|
|
114
|
+
expect(result).toContain("protocolCompliance");
|
|
115
|
+
expect(result).toContain("developerExperience");
|
|
116
|
+
expect(result).not.toContain("mcpSpecCompliance");
|
|
117
|
+
expect(result).not.toContain("documentation");
|
|
118
|
+
});
|
|
119
|
+
it("should deduplicate when deprecated and replacement both specified", () => {
|
|
120
|
+
const modules = ["mcpSpecCompliance", "protocolCompliance"];
|
|
121
|
+
const result = resolveModuleNames(modules, false);
|
|
122
|
+
expect(result.length).toBe(1);
|
|
123
|
+
expect(result).toContain("protocolCompliance");
|
|
124
|
+
});
|
|
125
|
+
it("should emit warnings for deprecated modules when warn=true", () => {
|
|
126
|
+
const consoleSpy = jest.spyOn(console, "warn").mockImplementation(() => { });
|
|
127
|
+
const modules = ["documentation"];
|
|
128
|
+
resolveModuleNames(modules, true);
|
|
129
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("deprecated"));
|
|
130
|
+
consoleSpy.mockRestore();
|
|
131
|
+
});
|
|
132
|
+
it("should not emit warnings when warn=false", () => {
|
|
133
|
+
const consoleSpy = jest.spyOn(console, "warn").mockImplementation(() => { });
|
|
134
|
+
const modules = ["documentation"];
|
|
135
|
+
resolveModuleNames(modules, false);
|
|
136
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
137
|
+
consoleSpy.mockRestore();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
describe("getProfileModules", () => {
|
|
141
|
+
it("should return modules for quick profile", () => {
|
|
142
|
+
const modules = getProfileModules("quick");
|
|
143
|
+
expect(modules).toEqual(["functionality", "security"]);
|
|
144
|
+
});
|
|
145
|
+
it("should return modules for security profile", () => {
|
|
146
|
+
const modules = getProfileModules("security");
|
|
147
|
+
expect(modules.length).toBe(TIER_1_CORE_SECURITY.length);
|
|
148
|
+
for (const mod of TIER_1_CORE_SECURITY) {
|
|
149
|
+
expect(modules).toContain(mod);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
it("should exclude temporal when skipTemporal=true", () => {
|
|
153
|
+
const modules = getProfileModules("security", { skipTemporal: true });
|
|
154
|
+
expect(modules).not.toContain("temporal");
|
|
155
|
+
});
|
|
156
|
+
it("should exclude externalAPIScanner when hasSourceCode=false", () => {
|
|
157
|
+
const modules = getProfileModules("full", { hasSourceCode: false });
|
|
158
|
+
expect(modules).not.toContain("externalAPIScanner");
|
|
159
|
+
});
|
|
160
|
+
it("should include externalAPIScanner when hasSourceCode=true", () => {
|
|
161
|
+
const modules = getProfileModules("full", { hasSourceCode: true });
|
|
162
|
+
expect(modules).toContain("externalAPIScanner");
|
|
163
|
+
});
|
|
164
|
+
it("should handle multiple options together", () => {
|
|
165
|
+
const modules = getProfileModules("full", {
|
|
166
|
+
skipTemporal: true,
|
|
167
|
+
hasSourceCode: false,
|
|
168
|
+
});
|
|
169
|
+
expect(modules).not.toContain("temporal");
|
|
170
|
+
expect(modules).not.toContain("externalAPIScanner");
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
describe("isValidProfileName", () => {
|
|
174
|
+
it("should return true for valid profile names", () => {
|
|
175
|
+
expect(isValidProfileName("quick")).toBe(true);
|
|
176
|
+
expect(isValidProfileName("security")).toBe(true);
|
|
177
|
+
expect(isValidProfileName("compliance")).toBe(true);
|
|
178
|
+
expect(isValidProfileName("full")).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
it("should return false for invalid profile names", () => {
|
|
181
|
+
expect(isValidProfileName("invalid")).toBe(false);
|
|
182
|
+
expect(isValidProfileName("")).toBe(false);
|
|
183
|
+
expect(isValidProfileName("QUICK")).toBe(false);
|
|
184
|
+
expect(isValidProfileName("fast")).toBe(false);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
describe("getProfileHelpText", () => {
|
|
188
|
+
it("should return non-empty string", () => {
|
|
189
|
+
const help = getProfileHelpText();
|
|
190
|
+
expect(help.length).toBeGreaterThan(0);
|
|
191
|
+
});
|
|
192
|
+
it("should contain all profile names", () => {
|
|
193
|
+
const help = getProfileHelpText();
|
|
194
|
+
expect(help).toContain("quick");
|
|
195
|
+
expect(help).toContain("security");
|
|
196
|
+
expect(help).toContain("compliance");
|
|
197
|
+
expect(help).toContain("full");
|
|
198
|
+
});
|
|
199
|
+
it("should contain module counts", () => {
|
|
200
|
+
const help = getProfileHelpText();
|
|
201
|
+
expect(help).toContain("Modules:");
|
|
202
|
+
});
|
|
203
|
+
it("should contain time estimates", () => {
|
|
204
|
+
const help = getProfileHelpText();
|
|
205
|
+
expect(help).toContain("Time:");
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
describe("mapLegacyConfigToModules", () => {
|
|
209
|
+
it("should return empty array for empty config", () => {
|
|
210
|
+
const result = mapLegacyConfigToModules({});
|
|
211
|
+
expect(result).toEqual([]);
|
|
212
|
+
});
|
|
213
|
+
it("should return enabled modules", () => {
|
|
214
|
+
const config = {
|
|
215
|
+
functionality: true,
|
|
216
|
+
security: true,
|
|
217
|
+
temporal: false,
|
|
218
|
+
};
|
|
219
|
+
const result = mapLegacyConfigToModules(config);
|
|
220
|
+
expect(result).toContain("functionality");
|
|
221
|
+
expect(result).toContain("security");
|
|
222
|
+
expect(result).not.toContain("temporal");
|
|
223
|
+
});
|
|
224
|
+
it("should apply aliases for deprecated names", () => {
|
|
225
|
+
const config = {
|
|
226
|
+
documentation: true,
|
|
227
|
+
mcpSpecCompliance: true,
|
|
228
|
+
};
|
|
229
|
+
const result = mapLegacyConfigToModules(config);
|
|
230
|
+
expect(result).toContain("developerExperience");
|
|
231
|
+
expect(result).toContain("protocolCompliance");
|
|
232
|
+
expect(result).not.toContain("documentation");
|
|
233
|
+
expect(result).not.toContain("mcpSpecCompliance");
|
|
234
|
+
});
|
|
235
|
+
it("should deduplicate when both deprecated and new names enabled", () => {
|
|
236
|
+
const config = {
|
|
237
|
+
documentation: true,
|
|
238
|
+
developerExperience: true,
|
|
239
|
+
};
|
|
240
|
+
const result = mapLegacyConfigToModules(config);
|
|
241
|
+
const devExpCount = result.filter((m) => m === "developerExperience").length;
|
|
242
|
+
expect(devExpCount).toBe(1);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
describe("modulesToLegacyConfig", () => {
|
|
246
|
+
it("should return config with all modules disabled by default", () => {
|
|
247
|
+
const result = modulesToLegacyConfig([]);
|
|
248
|
+
expect(result.functionality).toBe(false);
|
|
249
|
+
expect(result.security).toBe(false);
|
|
250
|
+
});
|
|
251
|
+
it("should enable specified modules", () => {
|
|
252
|
+
const result = modulesToLegacyConfig(["functionality", "security"]);
|
|
253
|
+
expect(result.functionality).toBe(true);
|
|
254
|
+
expect(result.security).toBe(true);
|
|
255
|
+
expect(result.temporal).toBe(false);
|
|
256
|
+
});
|
|
257
|
+
it("should map protocolCompliance to both old modules", () => {
|
|
258
|
+
const result = modulesToLegacyConfig(["protocolCompliance"]);
|
|
259
|
+
expect(result.mcpSpecCompliance).toBe(true);
|
|
260
|
+
expect(result.protocolConformance).toBe(true);
|
|
261
|
+
});
|
|
262
|
+
it("should map developerExperience to both old modules", () => {
|
|
263
|
+
const result = modulesToLegacyConfig(["developerExperience"]);
|
|
264
|
+
expect(result.documentation).toBe(true);
|
|
265
|
+
expect(result.usability).toBe(true);
|
|
266
|
+
});
|
|
267
|
+
it("should handle full profile modules", () => {
|
|
268
|
+
const modules = getProfileModules("full", { hasSourceCode: true });
|
|
269
|
+
const result = modulesToLegacyConfig(modules);
|
|
270
|
+
expect(result.functionality).toBe(true);
|
|
271
|
+
expect(result.security).toBe(true);
|
|
272
|
+
expect(result.temporal).toBe(true);
|
|
273
|
+
expect(result.toolAnnotations).toBe(true);
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
describe("Profile Metadata", () => {
|
|
277
|
+
it("should have descriptions for all profiles", () => {
|
|
278
|
+
for (const meta of Object.values(PROFILE_METADATA)) {
|
|
279
|
+
expect(meta.description).toBeTruthy();
|
|
280
|
+
expect(typeof meta.description).toBe("string");
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
it("should have estimated times for all profiles", () => {
|
|
284
|
+
for (const meta of Object.values(PROFILE_METADATA)) {
|
|
285
|
+
expect(meta.estimatedTime).toBeTruthy();
|
|
286
|
+
expect(meta.estimatedTime).toMatch(/~\d+-?\d*\s*minutes?/);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
it("should have tier information for all profiles", () => {
|
|
290
|
+
for (const meta of Object.values(PROFILE_METADATA)) {
|
|
291
|
+
expect(Array.isArray(meta.tiers)).toBe(true);
|
|
292
|
+
expect(meta.tiers.length).toBeGreaterThan(0);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
it("should have correct tier counts", () => {
|
|
296
|
+
expect(PROFILE_METADATA.quick.tiers.length).toBe(1);
|
|
297
|
+
expect(PROFILE_METADATA.security.tiers.length).toBe(1);
|
|
298
|
+
expect(PROFILE_METADATA.compliance.tiers.length).toBe(2);
|
|
299
|
+
expect(PROFILE_METADATA.full.tiers.length).toBe(4);
|
|
300
|
+
});
|
|
301
|
+
});
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport Module Unit Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for transport creation and configuration validation.
|
|
5
|
+
* Tests focus on input validation and error handling rather than
|
|
6
|
+
* mocked transport implementations due to ESM limitations.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect } from "@jest/globals";
|
|
9
|
+
import { createTransport } from "../transport.js";
|
|
10
|
+
describe("Transport Creation", () => {
|
|
11
|
+
describe("Input Validation", () => {
|
|
12
|
+
it("should throw error when URL is missing for HTTP transport", () => {
|
|
13
|
+
const options = {
|
|
14
|
+
transportType: "http",
|
|
15
|
+
};
|
|
16
|
+
expect(() => createTransport(options)).toThrow(/URL must be provided for SSE or HTTP transport types/);
|
|
17
|
+
});
|
|
18
|
+
it("should throw error when URL is missing for SSE transport", () => {
|
|
19
|
+
const options = {
|
|
20
|
+
transportType: "sse",
|
|
21
|
+
};
|
|
22
|
+
expect(() => createTransport(options)).toThrow(/URL must be provided for SSE or HTTP transport types/);
|
|
23
|
+
});
|
|
24
|
+
it("should throw error for invalid URL format", () => {
|
|
25
|
+
const options = {
|
|
26
|
+
transportType: "http",
|
|
27
|
+
url: ":::invalid-url",
|
|
28
|
+
};
|
|
29
|
+
expect(() => createTransport(options)).toThrow(/Failed to create transport/);
|
|
30
|
+
});
|
|
31
|
+
it("should throw error for unsupported transport type", () => {
|
|
32
|
+
const options = {
|
|
33
|
+
transportType: "websocket",
|
|
34
|
+
url: "ws://localhost:3000",
|
|
35
|
+
};
|
|
36
|
+
expect(() => createTransport(options)).toThrow(/Unsupported transport type/);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe("STDIO Transport", () => {
|
|
40
|
+
it("should create transport for valid stdio options", () => {
|
|
41
|
+
const options = {
|
|
42
|
+
transportType: "stdio",
|
|
43
|
+
command: "python",
|
|
44
|
+
args: ["server.py"],
|
|
45
|
+
};
|
|
46
|
+
// Should not throw
|
|
47
|
+
const transport = createTransport(options);
|
|
48
|
+
expect(transport).toBeDefined();
|
|
49
|
+
});
|
|
50
|
+
it("should handle missing args", () => {
|
|
51
|
+
const options = {
|
|
52
|
+
transportType: "stdio",
|
|
53
|
+
command: "node",
|
|
54
|
+
};
|
|
55
|
+
const transport = createTransport(options);
|
|
56
|
+
expect(transport).toBeDefined();
|
|
57
|
+
});
|
|
58
|
+
it("should handle empty command", () => {
|
|
59
|
+
const options = {
|
|
60
|
+
transportType: "stdio",
|
|
61
|
+
command: "",
|
|
62
|
+
};
|
|
63
|
+
// Should not throw - empty command is handled by findActualExecutable
|
|
64
|
+
const transport = createTransport(options);
|
|
65
|
+
expect(transport).toBeDefined();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
describe("HTTP Transport", () => {
|
|
69
|
+
it("should create transport for valid HTTP options", () => {
|
|
70
|
+
const options = {
|
|
71
|
+
transportType: "http",
|
|
72
|
+
url: "http://localhost:3000/mcp",
|
|
73
|
+
};
|
|
74
|
+
const transport = createTransport(options);
|
|
75
|
+
expect(transport).toBeDefined();
|
|
76
|
+
});
|
|
77
|
+
it("should create transport with headers", () => {
|
|
78
|
+
const options = {
|
|
79
|
+
transportType: "http",
|
|
80
|
+
url: "http://localhost:3000/mcp",
|
|
81
|
+
headers: {
|
|
82
|
+
Authorization: "Bearer token",
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
const transport = createTransport(options);
|
|
86
|
+
expect(transport).toBeDefined();
|
|
87
|
+
});
|
|
88
|
+
it("should handle HTTPS URLs", () => {
|
|
89
|
+
const options = {
|
|
90
|
+
transportType: "http",
|
|
91
|
+
url: "https://api.example.com/mcp",
|
|
92
|
+
};
|
|
93
|
+
const transport = createTransport(options);
|
|
94
|
+
expect(transport).toBeDefined();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe("SSE Transport", () => {
|
|
98
|
+
it("should create transport for valid SSE options", () => {
|
|
99
|
+
const options = {
|
|
100
|
+
transportType: "sse",
|
|
101
|
+
url: "http://localhost:3000/sse",
|
|
102
|
+
};
|
|
103
|
+
const transport = createTransport(options);
|
|
104
|
+
expect(transport).toBeDefined();
|
|
105
|
+
});
|
|
106
|
+
it("should create transport with headers", () => {
|
|
107
|
+
const options = {
|
|
108
|
+
transportType: "sse",
|
|
109
|
+
url: "http://localhost:3000/sse",
|
|
110
|
+
headers: {
|
|
111
|
+
"X-API-Key": "secret",
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
const transport = createTransport(options);
|
|
115
|
+
expect(transport).toBeDefined();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
describe("TransportOptions Type", () => {
|
|
120
|
+
it("should accept all valid transport types", () => {
|
|
121
|
+
const stdioOptions = {
|
|
122
|
+
transportType: "stdio",
|
|
123
|
+
command: "node",
|
|
124
|
+
args: ["server.js"],
|
|
125
|
+
};
|
|
126
|
+
const httpOptions = {
|
|
127
|
+
transportType: "http",
|
|
128
|
+
url: "http://localhost:3000/mcp",
|
|
129
|
+
};
|
|
130
|
+
const sseOptions = {
|
|
131
|
+
transportType: "sse",
|
|
132
|
+
url: "http://localhost:3000/sse",
|
|
133
|
+
};
|
|
134
|
+
// Type checking - these should compile without errors
|
|
135
|
+
expect(stdioOptions.transportType).toBe("stdio");
|
|
136
|
+
expect(httpOptions.transportType).toBe("http");
|
|
137
|
+
expect(sseOptions.transportType).toBe("sse");
|
|
138
|
+
});
|
|
139
|
+
it("should allow optional headers for HTTP/SSE", () => {
|
|
140
|
+
const withHeaders = {
|
|
141
|
+
transportType: "http",
|
|
142
|
+
url: "http://localhost:3000/mcp",
|
|
143
|
+
headers: { "Content-Type": "application/json" },
|
|
144
|
+
};
|
|
145
|
+
const withoutHeaders = {
|
|
146
|
+
transportType: "http",
|
|
147
|
+
url: "http://localhost:3000/mcp",
|
|
148
|
+
};
|
|
149
|
+
expect(withHeaders.headers).toBeDefined();
|
|
150
|
+
expect(withoutHeaders.headers).toBeUndefined();
|
|
151
|
+
});
|
|
152
|
+
it("should allow optional command args", () => {
|
|
153
|
+
const withArgs = {
|
|
154
|
+
transportType: "stdio",
|
|
155
|
+
command: "python",
|
|
156
|
+
args: ["-m", "server"],
|
|
157
|
+
};
|
|
158
|
+
const withoutArgs = {
|
|
159
|
+
transportType: "stdio",
|
|
160
|
+
command: "python",
|
|
161
|
+
};
|
|
162
|
+
expect(withArgs.args).toEqual(["-m", "server"]);
|
|
163
|
+
expect(withoutArgs.args).toBeUndefined();
|
|
164
|
+
});
|
|
165
|
+
});
|
package/build/assess-full.js
CHANGED
|
@@ -1010,7 +1010,8 @@ function parseArgs() {
|
|
|
1010
1010
|
}
|
|
1011
1011
|
}
|
|
1012
1012
|
// Validate mutual exclusivity of --profile, --skip-modules, and --only-modules
|
|
1013
|
-
if (options.profile &&
|
|
1013
|
+
if (options.profile &&
|
|
1014
|
+
(options.skipModules?.length || options.onlyModules?.length)) {
|
|
1014
1015
|
console.error("Error: --profile cannot be used with --skip-modules or --only-modules");
|
|
1015
1016
|
setTimeout(() => process.exit(1), 10);
|
|
1016
1017
|
options.helpRequested = true;
|
package/build/profiles.js
CHANGED
|
@@ -86,30 +86,34 @@ export const ALL_MODULES = [
|
|
|
86
86
|
/**
|
|
87
87
|
* Assessment profile definitions
|
|
88
88
|
* Each profile includes a specific set of modules optimized for the use case.
|
|
89
|
+
*
|
|
90
|
+
* Note: Time estimates are based on testing a server with ~30 tools.
|
|
91
|
+
* The SecurityAssessor runs 23 attack patterns per tool (~3400+ tests),
|
|
92
|
+
* which dominates runtime across all profiles that include it.
|
|
89
93
|
*/
|
|
90
94
|
export const ASSESSMENT_PROFILES = {
|
|
91
95
|
/**
|
|
92
96
|
* Quick profile: Minimal testing for fast CI/CD checks
|
|
93
97
|
* Use when: Pre-commit hooks, quick validation
|
|
94
|
-
* Time: ~
|
|
98
|
+
* Time: ~3-4 minutes (security module dominates)
|
|
95
99
|
*/
|
|
96
100
|
quick: ["functionality", "security"],
|
|
97
101
|
/**
|
|
98
102
|
* Security profile: Core security modules (Tier 1)
|
|
99
103
|
* Use when: Security-focused audits, vulnerability scanning
|
|
100
|
-
* Time: ~
|
|
104
|
+
* Time: ~8-10 minutes
|
|
101
105
|
*/
|
|
102
106
|
security: [...TIER_1_CORE_SECURITY],
|
|
103
107
|
/**
|
|
104
108
|
* Compliance profile: Security + Directory compliance (Tier 1 + 2)
|
|
105
109
|
* Use when: Pre-submission validation for MCP Directory
|
|
106
|
-
* Time: ~
|
|
110
|
+
* Time: ~8-10 minutes
|
|
107
111
|
*/
|
|
108
112
|
compliance: [...TIER_1_CORE_SECURITY, ...TIER_2_COMPLIANCE],
|
|
109
113
|
/**
|
|
110
114
|
* Full profile: All modules (Tier 1 + 2 + 3 + 4)
|
|
111
115
|
* Use when: Comprehensive audits, initial server review
|
|
112
|
-
* Time: ~
|
|
116
|
+
* Time: ~8-12 minutes
|
|
113
117
|
*/
|
|
114
118
|
full: [
|
|
115
119
|
...TIER_1_CORE_SECURITY,
|
|
@@ -121,25 +125,25 @@ export const ASSESSMENT_PROFILES = {
|
|
|
121
125
|
export const PROFILE_METADATA = {
|
|
122
126
|
quick: {
|
|
123
127
|
description: "Fast validation (functionality + security only)",
|
|
124
|
-
estimatedTime: "~
|
|
128
|
+
estimatedTime: "~3-4 minutes",
|
|
125
129
|
moduleCount: ASSESSMENT_PROFILES.quick.length,
|
|
126
130
|
tiers: ["Tier 1 (partial)"],
|
|
127
131
|
},
|
|
128
132
|
security: {
|
|
129
133
|
description: "Core security modules for vulnerability scanning",
|
|
130
|
-
estimatedTime: "~
|
|
134
|
+
estimatedTime: "~8-10 minutes",
|
|
131
135
|
moduleCount: ASSESSMENT_PROFILES.security.length,
|
|
132
136
|
tiers: ["Tier 1 (Core Security)"],
|
|
133
137
|
},
|
|
134
138
|
compliance: {
|
|
135
139
|
description: "Security + MCP Directory compliance validation",
|
|
136
|
-
estimatedTime: "~
|
|
140
|
+
estimatedTime: "~8-10 minutes",
|
|
137
141
|
moduleCount: ASSESSMENT_PROFILES.compliance.length,
|
|
138
142
|
tiers: ["Tier 1 (Core Security)", "Tier 2 (Compliance)"],
|
|
139
143
|
},
|
|
140
144
|
full: {
|
|
141
145
|
description: "Comprehensive audit with all assessment modules",
|
|
142
|
-
estimatedTime: "~
|
|
146
|
+
estimatedTime: "~8-12 minutes",
|
|
143
147
|
moduleCount: ASSESSMENT_PROFILES.full.length,
|
|
144
148
|
tiers: [
|
|
145
149
|
"Tier 1 (Core Security)",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bryan-thompson/inspector-assessment-cli",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.4",
|
|
4
4
|
"description": "CLI for the Enhanced MCP Inspector with assessment capabilities",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bryan Thompson <bryan@triepod.ai>",
|
|
@@ -30,13 +30,19 @@
|
|
|
30
30
|
"scripts": {
|
|
31
31
|
"build": "tsc",
|
|
32
32
|
"postbuild": "node scripts/make-executable.js",
|
|
33
|
-
"test": "
|
|
33
|
+
"test": "npm run test:unit && npm run test:integration",
|
|
34
|
+
"test:unit": "node --experimental-vm-modules ../node_modules/.bin/jest --config jest.config.cjs",
|
|
35
|
+
"test:integration": "node scripts/cli-tests.js && node scripts/cli-tool-tests.js && node scripts/cli-header-tests.js && node scripts/cli-validation-tests.js",
|
|
34
36
|
"test:cli": "node scripts/cli-tests.js",
|
|
35
37
|
"test:cli-tools": "node scripts/cli-tool-tests.js",
|
|
36
38
|
"test:cli-headers": "node scripts/cli-header-tests.js",
|
|
37
39
|
"test:cli-validation": "node scripts/cli-validation-tests.js"
|
|
38
40
|
},
|
|
39
|
-
"devDependencies": {
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/jest": "^29.5.14",
|
|
43
|
+
"jest": "^29.7.0",
|
|
44
|
+
"ts-jest": "^29.2.0"
|
|
45
|
+
},
|
|
40
46
|
"dependencies": {
|
|
41
47
|
"@bryan-thompson/inspector-assessment-client": "^1.5.0",
|
|
42
48
|
"@modelcontextprotocol/sdk": "^1.24.3",
|