@bryan-thompson/inspector-assessment-cli 1.30.1 → 1.32.0

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,282 @@
1
+ /**
2
+ * Tests for Zod Error Formatting Utilities
3
+ *
4
+ * Validates the error formatting functions for CLI-friendly output.
5
+ *
6
+ * @module cli/lib/__tests__/zodErrorFormatter
7
+ */
8
+ // Uses Jest globals (describe, test, expect)
9
+ import { jest } from "@jest/globals";
10
+ import { z } from "zod";
11
+ import { formatZodIssue, formatZodError, formatZodErrorIndented, printZodErrorForCli, zodErrorToArray, formatZodErrorForJson, formatUserFriendlyError, } from "../zodErrorFormatter.js";
12
+ // Helper to create a ZodError with specific issues
13
+ function createZodError(issues) {
14
+ // Create a schema that will fail and then manipulate the error
15
+ const schema = z.object({ dummy: z.string() });
16
+ const result = schema.safeParse({ dummy: 123 });
17
+ if (!result.success) {
18
+ // Replace the errors with our custom issues
19
+ result.error.issues = issues.map((issue) => ({
20
+ code: issue.code || "custom",
21
+ path: issue.path,
22
+ message: issue.message,
23
+ }));
24
+ return result.error;
25
+ }
26
+ throw new Error("Failed to create ZodError");
27
+ }
28
+ describe("zodErrorFormatter", () => {
29
+ describe("formatZodIssue", () => {
30
+ test("formats issue with empty path (message only)", () => {
31
+ const issue = { code: "custom", path: [], message: "Required" };
32
+ const result = formatZodIssue(issue);
33
+ expect(result).toBe("Required");
34
+ });
35
+ test("formats issue with single-level path", () => {
36
+ const issue = {
37
+ code: "custom",
38
+ path: ["field"],
39
+ message: "Invalid value",
40
+ };
41
+ const result = formatZodIssue(issue);
42
+ expect(result).toBe("field: Invalid value");
43
+ });
44
+ test("formats issue with nested path", () => {
45
+ const issue = {
46
+ code: "custom",
47
+ path: ["config", "nested", "field"],
48
+ message: "Must be a string",
49
+ };
50
+ const result = formatZodIssue(issue);
51
+ expect(result).toBe("config.nested.field: Must be a string");
52
+ });
53
+ test("formats issue with array index in path", () => {
54
+ const issue = {
55
+ code: "custom",
56
+ path: ["items", 0, "name"],
57
+ message: "Required",
58
+ };
59
+ const result = formatZodIssue(issue);
60
+ expect(result).toBe("items.0.name: Required");
61
+ });
62
+ });
63
+ describe("formatZodError", () => {
64
+ test("formats single error", () => {
65
+ const error = createZodError([
66
+ { path: ["field"], message: "Invalid value" },
67
+ ]);
68
+ const result = formatZodError(error);
69
+ expect(result).toBe("field: Invalid value");
70
+ });
71
+ test("formats multiple errors with newlines", () => {
72
+ const error = createZodError([
73
+ { path: ["field1"], message: "Error 1" },
74
+ { path: ["field2"], message: "Error 2" },
75
+ { path: ["field3"], message: "Error 3" },
76
+ ]);
77
+ const result = formatZodError(error);
78
+ expect(result).toBe("field1: Error 1\nfield2: Error 2\nfield3: Error 3");
79
+ });
80
+ test("handles error with empty path", () => {
81
+ const error = createZodError([{ path: [], message: "Global error" }]);
82
+ const result = formatZodError(error);
83
+ expect(result).toBe("Global error");
84
+ });
85
+ });
86
+ describe("formatZodErrorIndented", () => {
87
+ test("uses default indentation (two spaces)", () => {
88
+ const error = createZodError([
89
+ { path: ["field"], message: "Invalid value" },
90
+ ]);
91
+ const result = formatZodErrorIndented(error);
92
+ expect(result).toBe(" field: Invalid value");
93
+ });
94
+ test("uses custom indentation", () => {
95
+ const error = createZodError([
96
+ { path: ["field"], message: "Invalid value" },
97
+ ]);
98
+ const result = formatZodErrorIndented(error, "\t");
99
+ expect(result).toBe("\tfield: Invalid value");
100
+ });
101
+ test("uses four spaces indentation", () => {
102
+ const error = createZodError([
103
+ { path: ["field"], message: "Invalid value" },
104
+ ]);
105
+ const result = formatZodErrorIndented(error, " ");
106
+ expect(result).toBe(" field: Invalid value");
107
+ });
108
+ test("applies indent to each line for multiple errors", () => {
109
+ const error = createZodError([
110
+ { path: ["field1"], message: "Error 1" },
111
+ { path: ["field2"], message: "Error 2" },
112
+ ]);
113
+ const result = formatZodErrorIndented(error, ">> ");
114
+ expect(result).toBe(">> field1: Error 1\n>> field2: Error 2");
115
+ });
116
+ test("uses empty indentation", () => {
117
+ const error = createZodError([
118
+ { path: ["field"], message: "Invalid value" },
119
+ ]);
120
+ const result = formatZodErrorIndented(error, "");
121
+ expect(result).toBe("field: Invalid value");
122
+ });
123
+ });
124
+ describe("printZodErrorForCli", () => {
125
+ let consoleSpy;
126
+ beforeEach(() => {
127
+ consoleSpy = jest.spyOn(console, "error").mockImplementation(() => { });
128
+ });
129
+ afterEach(() => {
130
+ consoleSpy.mockRestore();
131
+ });
132
+ test("prints with context prefix", () => {
133
+ const error = createZodError([
134
+ { path: ["field"], message: "Invalid value" },
135
+ ]);
136
+ printZodErrorForCli(error, "config file");
137
+ expect(consoleSpy).toHaveBeenCalledWith("Error in config file:\n field: Invalid value");
138
+ });
139
+ test("prints without context (default prefix)", () => {
140
+ const error = createZodError([
141
+ { path: ["field"], message: "Invalid value" },
142
+ ]);
143
+ printZodErrorForCli(error);
144
+ expect(consoleSpy).toHaveBeenCalledWith("Validation error:\n field: Invalid value");
145
+ });
146
+ test("outputs multiple errors with indentation", () => {
147
+ const error = createZodError([
148
+ { path: ["field1"], message: "Error 1" },
149
+ { path: ["field2"], message: "Error 2" },
150
+ ]);
151
+ printZodErrorForCli(error, "CLI arguments");
152
+ expect(consoleSpy).toHaveBeenCalledWith("Error in CLI arguments:\n field1: Error 1\n field2: Error 2");
153
+ });
154
+ });
155
+ describe("zodErrorToArray", () => {
156
+ test("converts single error to array", () => {
157
+ const error = createZodError([
158
+ { path: ["field"], message: "Invalid value" },
159
+ ]);
160
+ const result = zodErrorToArray(error);
161
+ expect(result).toEqual(["field: Invalid value"]);
162
+ });
163
+ test("converts multiple errors to array", () => {
164
+ const error = createZodError([
165
+ { path: ["field1"], message: "Error 1" },
166
+ { path: ["field2"], message: "Error 2" },
167
+ { path: ["field3"], message: "Error 3" },
168
+ ]);
169
+ const result = zodErrorToArray(error);
170
+ expect(result).toEqual([
171
+ "field1: Error 1",
172
+ "field2: Error 2",
173
+ "field3: Error 3",
174
+ ]);
175
+ });
176
+ test("returns array preserving order", () => {
177
+ const error = createZodError([
178
+ { path: ["z"], message: "Z error" },
179
+ { path: ["a"], message: "A error" },
180
+ { path: ["m"], message: "M error" },
181
+ ]);
182
+ const result = zodErrorToArray(error);
183
+ expect(result).toEqual(["z: Z error", "a: A error", "m: M error"]);
184
+ });
185
+ });
186
+ describe("formatZodErrorForJson", () => {
187
+ test("includes message field", () => {
188
+ const error = createZodError([
189
+ { path: ["field"], message: "Invalid value" },
190
+ ]);
191
+ const result = formatZodErrorForJson(error);
192
+ expect(result.message).toBe("Validation failed");
193
+ });
194
+ test("includes errors array with path, message, code", () => {
195
+ const error = createZodError([
196
+ { path: ["field"], message: "Invalid value", code: "invalid_type" },
197
+ ]);
198
+ const result = formatZodErrorForJson(error);
199
+ expect(result.errors).toHaveLength(1);
200
+ expect(result.errors[0]).toEqual({
201
+ path: ["field"],
202
+ message: "Invalid value",
203
+ code: "invalid_type",
204
+ });
205
+ });
206
+ test("preserves path as array with numbers and strings", () => {
207
+ const error = createZodError([
208
+ { path: ["items", 0, "nested", 1, "field"], message: "Error" },
209
+ ]);
210
+ const result = formatZodErrorForJson(error);
211
+ expect(result.errors[0].path).toEqual(["items", 0, "nested", 1, "field"]);
212
+ });
213
+ test("handles multiple errors", () => {
214
+ const error = createZodError([
215
+ { path: ["field1"], message: "Error 1", code: "too_small" },
216
+ { path: ["field2"], message: "Error 2", code: "invalid_type" },
217
+ ]);
218
+ const result = formatZodErrorForJson(error);
219
+ expect(result.errors).toHaveLength(2);
220
+ expect(result.errors[0].code).toBe("too_small");
221
+ expect(result.errors[1].code).toBe("invalid_type");
222
+ });
223
+ });
224
+ describe("formatUserFriendlyError", () => {
225
+ test("single error returns plain message", () => {
226
+ const error = createZodError([
227
+ { path: ["username"], message: "Required" },
228
+ ]);
229
+ const result = formatUserFriendlyError(error);
230
+ expect(result).toBe("username: Required");
231
+ });
232
+ test("single error with empty path returns message only", () => {
233
+ const error = createZodError([{ path: [], message: "Invalid input" }]);
234
+ const result = formatUserFriendlyError(error);
235
+ expect(result).toBe("Invalid input");
236
+ });
237
+ test("multiple errors returns bulleted list", () => {
238
+ const error = createZodError([
239
+ { path: ["field1"], message: "Error 1" },
240
+ { path: ["field2"], message: "Error 2" },
241
+ ]);
242
+ const result = formatUserFriendlyError(error);
243
+ expect(result).toBe("Multiple validation errors:\n - field1: Error 1\n - field2: Error 2");
244
+ });
245
+ test("applies field label mapping", () => {
246
+ const error = createZodError([
247
+ { path: ["serverName"], message: "Required" },
248
+ ]);
249
+ const labels = { serverName: "Server Name" };
250
+ const result = formatUserFriendlyError(error, labels);
251
+ expect(result).toBe("Server Name: Required");
252
+ });
253
+ test("applies labels for multiple errors", () => {
254
+ const error = createZodError([
255
+ { path: ["serverName"], message: "Required" },
256
+ { path: ["configPath"], message: "Invalid path" },
257
+ ]);
258
+ const labels = {
259
+ serverName: "Server Name",
260
+ configPath: "Configuration Path",
261
+ };
262
+ const result = formatUserFriendlyError(error, labels);
263
+ expect(result).toBe("Multiple validation errors:\n - Server Name: Required\n - Configuration Path: Invalid path");
264
+ });
265
+ test("falls back to path when no matching label", () => {
266
+ const error = createZodError([
267
+ { path: ["unknownField"], message: "Error" },
268
+ ]);
269
+ const labels = { otherField: "Other Field" };
270
+ const result = formatUserFriendlyError(error, labels);
271
+ expect(result).toBe("unknownField: Error");
272
+ });
273
+ test("handles nested path with labels", () => {
274
+ const error = createZodError([
275
+ { path: ["config", "nested"], message: "Invalid" },
276
+ ]);
277
+ const labels = { "config.nested": "Nested Config" };
278
+ const result = formatUserFriendlyError(error, labels);
279
+ expect(result).toBe("Nested Config: Invalid");
280
+ });
281
+ });
282
+ });
@@ -0,0 +1,394 @@
1
+ /**
2
+ * Tests for Server Configuration Zod Schemas
3
+ *
4
+ * Validates the schema definitions used for server config file parsing.
5
+ *
6
+ * @module cli/lib/assessment-runner/__tests__/server-configSchemas
7
+ */
8
+ // Uses Jest globals (describe, test, expect)
9
+ import { ZodError } from "zod";
10
+ import { HttpSseServerConfigSchema, StdioServerConfigSchema, ServerEntrySchema, ClaudeDesktopConfigSchema, StandaloneConfigSchema, ConfigFileSchema, parseConfigFile, safeParseConfigFile, validateServerEntry, isHttpSseConfig, isStdioConfig, TransportTypeSchema, } from "../server-configSchemas.js";
11
+ describe("server-configSchemas", () => {
12
+ describe("Re-exported schemas", () => {
13
+ test("exports TransportTypeSchema", () => {
14
+ expect(TransportTypeSchema.safeParse("stdio").success).toBe(true);
15
+ expect(TransportTypeSchema.safeParse("http").success).toBe(true);
16
+ expect(TransportTypeSchema.safeParse("sse").success).toBe(true);
17
+ });
18
+ });
19
+ describe("HttpSseServerConfigSchema", () => {
20
+ describe("valid configs", () => {
21
+ test("accepts url only", () => {
22
+ const result = HttpSseServerConfigSchema.safeParse({
23
+ url: "http://localhost:3000/mcp",
24
+ });
25
+ expect(result.success).toBe(true);
26
+ });
27
+ test("accepts url with transport http", () => {
28
+ const result = HttpSseServerConfigSchema.safeParse({
29
+ transport: "http",
30
+ url: "http://localhost:3000/mcp",
31
+ });
32
+ expect(result.success).toBe(true);
33
+ });
34
+ test("accepts url with transport sse", () => {
35
+ const result = HttpSseServerConfigSchema.safeParse({
36
+ transport: "sse",
37
+ url: "http://localhost:3000/sse",
38
+ });
39
+ expect(result.success).toBe(true);
40
+ });
41
+ test("accepts https url", () => {
42
+ const result = HttpSseServerConfigSchema.safeParse({
43
+ url: "https://api.example.com/mcp",
44
+ });
45
+ expect(result.success).toBe(true);
46
+ });
47
+ });
48
+ describe("invalid configs", () => {
49
+ test("rejects missing url", () => {
50
+ const result = HttpSseServerConfigSchema.safeParse({
51
+ transport: "http",
52
+ });
53
+ expect(result.success).toBe(false);
54
+ });
55
+ test("rejects invalid URL format", () => {
56
+ const result = HttpSseServerConfigSchema.safeParse({
57
+ url: "not-a-valid-url",
58
+ });
59
+ expect(result.success).toBe(false);
60
+ });
61
+ test("rejects empty url", () => {
62
+ const result = HttpSseServerConfigSchema.safeParse({
63
+ url: "",
64
+ });
65
+ expect(result.success).toBe(false);
66
+ });
67
+ test("rejects invalid transport value", () => {
68
+ const result = HttpSseServerConfigSchema.safeParse({
69
+ transport: "stdio", // stdio not allowed in this schema
70
+ url: "http://localhost:3000",
71
+ });
72
+ expect(result.success).toBe(false);
73
+ });
74
+ });
75
+ });
76
+ describe("StdioServerConfigSchema", () => {
77
+ describe("valid configs", () => {
78
+ test("accepts command only (minimum valid)", () => {
79
+ const result = StdioServerConfigSchema.safeParse({
80
+ command: "python3",
81
+ });
82
+ expect(result.success).toBe(true);
83
+ });
84
+ test("accepts command with args", () => {
85
+ const result = StdioServerConfigSchema.safeParse({
86
+ command: "python3",
87
+ args: ["server.py", "--port", "8080"],
88
+ });
89
+ expect(result.success).toBe(true);
90
+ });
91
+ test("accepts command with env record", () => {
92
+ const result = StdioServerConfigSchema.safeParse({
93
+ command: "python3",
94
+ env: { DEBUG: "true", PATH: "/usr/bin" },
95
+ });
96
+ expect(result.success).toBe(true);
97
+ });
98
+ test("accepts command with cwd", () => {
99
+ const result = StdioServerConfigSchema.safeParse({
100
+ command: "python3",
101
+ cwd: "/home/user/project",
102
+ });
103
+ expect(result.success).toBe(true);
104
+ });
105
+ test("accepts full config with all fields", () => {
106
+ const result = StdioServerConfigSchema.safeParse({
107
+ transport: "stdio",
108
+ command: "python3",
109
+ args: ["server.py"],
110
+ env: { DEBUG: "1" },
111
+ cwd: "/home/user/project",
112
+ });
113
+ expect(result.success).toBe(true);
114
+ });
115
+ });
116
+ describe("invalid configs", () => {
117
+ test("rejects missing command", () => {
118
+ const result = StdioServerConfigSchema.safeParse({
119
+ transport: "stdio",
120
+ });
121
+ expect(result.success).toBe(false);
122
+ });
123
+ test("rejects empty command", () => {
124
+ const result = StdioServerConfigSchema.safeParse({
125
+ command: "",
126
+ });
127
+ expect(result.success).toBe(false);
128
+ if (!result.success) {
129
+ expect(result.error.errors[0].message).toContain("command is required");
130
+ }
131
+ });
132
+ test("rejects non-string array for args", () => {
133
+ const result = StdioServerConfigSchema.safeParse({
134
+ command: "python3",
135
+ args: [1, 2, 3],
136
+ });
137
+ expect(result.success).toBe(false);
138
+ });
139
+ test("rejects non-string values in env", () => {
140
+ const result = StdioServerConfigSchema.safeParse({
141
+ command: "python3",
142
+ env: { DEBUG: 123 },
143
+ });
144
+ expect(result.success).toBe(false);
145
+ });
146
+ });
147
+ describe("default values", () => {
148
+ test("args defaults to empty array", () => {
149
+ const result = StdioServerConfigSchema.safeParse({
150
+ command: "python3",
151
+ });
152
+ expect(result.success).toBe(true);
153
+ if (result.success) {
154
+ expect(result.data.args).toEqual([]);
155
+ }
156
+ });
157
+ test("env defaults to empty object", () => {
158
+ const result = StdioServerConfigSchema.safeParse({
159
+ command: "python3",
160
+ });
161
+ expect(result.success).toBe(true);
162
+ if (result.success) {
163
+ expect(result.data.env).toEqual({});
164
+ }
165
+ });
166
+ });
167
+ });
168
+ describe("ServerEntrySchema", () => {
169
+ test("accepts HTTP config", () => {
170
+ const result = ServerEntrySchema.safeParse({
171
+ url: "http://localhost:3000/mcp",
172
+ });
173
+ expect(result.success).toBe(true);
174
+ });
175
+ test("accepts SSE config", () => {
176
+ const result = ServerEntrySchema.safeParse({
177
+ transport: "sse",
178
+ url: "http://localhost:3000/sse",
179
+ });
180
+ expect(result.success).toBe(true);
181
+ });
182
+ test("accepts stdio config", () => {
183
+ const result = ServerEntrySchema.safeParse({
184
+ command: "python3",
185
+ args: ["server.py"],
186
+ });
187
+ expect(result.success).toBe(true);
188
+ });
189
+ test("rejects config matching neither schema", () => {
190
+ const result = ServerEntrySchema.safeParse({
191
+ transport: "unknown",
192
+ });
193
+ expect(result.success).toBe(false);
194
+ });
195
+ test("rejects empty object", () => {
196
+ const result = ServerEntrySchema.safeParse({});
197
+ expect(result.success).toBe(false);
198
+ });
199
+ });
200
+ describe("ClaudeDesktopConfigSchema", () => {
201
+ test("accepts {mcpServers: {...}} format", () => {
202
+ const result = ClaudeDesktopConfigSchema.safeParse({
203
+ mcpServers: {
204
+ "my-server": { url: "http://localhost:3000/mcp" },
205
+ "another-server": { command: "python3", args: ["server.py"] },
206
+ },
207
+ });
208
+ expect(result.success).toBe(true);
209
+ });
210
+ test("accepts empty mcpServers object", () => {
211
+ const result = ClaudeDesktopConfigSchema.safeParse({
212
+ mcpServers: {},
213
+ });
214
+ expect(result.success).toBe(true);
215
+ });
216
+ test("accepts config without mcpServers (undefined)", () => {
217
+ const result = ClaudeDesktopConfigSchema.safeParse({});
218
+ expect(result.success).toBe(true);
219
+ });
220
+ test("rejects invalid server entry within mcpServers", () => {
221
+ const result = ClaudeDesktopConfigSchema.safeParse({
222
+ mcpServers: {
223
+ "invalid-server": { transport: "invalid" },
224
+ },
225
+ });
226
+ expect(result.success).toBe(false);
227
+ });
228
+ });
229
+ describe("StandaloneConfigSchema", () => {
230
+ test("accepts HTTP config directly", () => {
231
+ const result = StandaloneConfigSchema.safeParse({
232
+ url: "http://localhost:3000/mcp",
233
+ });
234
+ expect(result.success).toBe(true);
235
+ });
236
+ test("accepts stdio config directly", () => {
237
+ const result = StandaloneConfigSchema.safeParse({
238
+ command: "python3",
239
+ });
240
+ expect(result.success).toBe(true);
241
+ });
242
+ test("is equivalent to ServerEntrySchema", () => {
243
+ expect(StandaloneConfigSchema).toBe(ServerEntrySchema);
244
+ });
245
+ });
246
+ describe("ConfigFileSchema", () => {
247
+ test("accepts Claude Desktop format", () => {
248
+ const result = ConfigFileSchema.safeParse({
249
+ mcpServers: {
250
+ "test-server": { url: "http://localhost:3000/mcp" },
251
+ },
252
+ });
253
+ expect(result.success).toBe(true);
254
+ });
255
+ test("accepts standalone HTTP config", () => {
256
+ const result = ConfigFileSchema.safeParse({
257
+ url: "http://localhost:3000/mcp",
258
+ });
259
+ expect(result.success).toBe(true);
260
+ });
261
+ test("accepts standalone stdio config", () => {
262
+ const result = ConfigFileSchema.safeParse({
263
+ command: "python3",
264
+ args: ["server.py"],
265
+ });
266
+ expect(result.success).toBe(true);
267
+ });
268
+ test("accepts unknown keys (Zod passthrough by default)", () => {
269
+ // ClaudeDesktopConfigSchema has mcpServers as optional
270
+ // Extra keys are allowed by default in Zod (non-strict mode)
271
+ const result = ConfigFileSchema.safeParse({
272
+ invalid: "config",
273
+ });
274
+ // This passes because it matches ClaudeDesktopConfigSchema with mcpServers undefined
275
+ expect(result.success).toBe(true);
276
+ });
277
+ test("rejects truly invalid values", () => {
278
+ // null is not a valid object
279
+ const result = ConfigFileSchema.safeParse(null);
280
+ expect(result.success).toBe(false);
281
+ });
282
+ });
283
+ describe("parseConfigFile", () => {
284
+ test("parses valid Claude Desktop config", () => {
285
+ const config = parseConfigFile({
286
+ mcpServers: {
287
+ "test-server": { url: "http://localhost:3000/mcp" },
288
+ },
289
+ });
290
+ expect(config).toBeDefined();
291
+ });
292
+ test("parses valid standalone config", () => {
293
+ const config = parseConfigFile({
294
+ url: "http://localhost:3000/mcp",
295
+ });
296
+ expect(config).toBeDefined();
297
+ });
298
+ test("throws ZodError for null input", () => {
299
+ expect(() => parseConfigFile(null)).toThrow(ZodError);
300
+ });
301
+ test("throws ZodError for non-object input", () => {
302
+ expect(() => parseConfigFile("string")).toThrow(ZodError);
303
+ });
304
+ });
305
+ describe("safeParseConfigFile", () => {
306
+ test("returns success for valid config", () => {
307
+ const result = safeParseConfigFile({
308
+ url: "http://localhost:3000/mcp",
309
+ });
310
+ expect(result.success).toBe(true);
311
+ });
312
+ test("returns error for null input", () => {
313
+ const result = safeParseConfigFile(null);
314
+ expect(result.success).toBe(false);
315
+ if (!result.success) {
316
+ expect(result.error).toBeInstanceOf(ZodError);
317
+ }
318
+ });
319
+ test("returns error for non-object input", () => {
320
+ const result = safeParseConfigFile("string");
321
+ expect(result.success).toBe(false);
322
+ });
323
+ });
324
+ describe("validateServerEntry", () => {
325
+ test("returns empty array for valid HTTP entry", () => {
326
+ const errors = validateServerEntry({
327
+ url: "http://localhost:3000/mcp",
328
+ });
329
+ expect(errors).toEqual([]);
330
+ });
331
+ test("returns empty array for valid stdio entry", () => {
332
+ const errors = validateServerEntry({
333
+ command: "python3",
334
+ });
335
+ expect(errors).toEqual([]);
336
+ });
337
+ test("returns errors for invalid entry", () => {
338
+ const errors = validateServerEntry({});
339
+ expect(errors.length).toBeGreaterThan(0);
340
+ });
341
+ test("includes path in error messages", () => {
342
+ const errors = validateServerEntry({
343
+ url: "not-a-url",
344
+ });
345
+ expect(errors.length).toBeGreaterThan(0);
346
+ // Should contain error about invalid URL
347
+ expect(errors.some((e) => e.includes("url"))).toBe(true);
348
+ });
349
+ });
350
+ describe("isHttpSseConfig", () => {
351
+ test("returns true for config with url", () => {
352
+ const config = { url: "http://localhost:3000" };
353
+ expect(isHttpSseConfig(config)).toBe(true);
354
+ });
355
+ test("returns true for config with transport http", () => {
356
+ const config = {
357
+ transport: "http",
358
+ url: "http://localhost:3000",
359
+ };
360
+ expect(isHttpSseConfig(config)).toBe(true);
361
+ });
362
+ test("returns true for config with transport sse", () => {
363
+ const config = {
364
+ transport: "sse",
365
+ url: "http://localhost:3000",
366
+ };
367
+ expect(isHttpSseConfig(config)).toBe(true);
368
+ });
369
+ test("returns false for stdio config without url", () => {
370
+ const config = { command: "python3", args: [], env: {} };
371
+ expect(isHttpSseConfig(config)).toBe(false);
372
+ });
373
+ });
374
+ describe("isStdioConfig", () => {
375
+ test("returns true for config with command (no url)", () => {
376
+ const config = { command: "python3", args: [], env: {} };
377
+ expect(isStdioConfig(config)).toBe(true);
378
+ });
379
+ test("returns false for HTTP config", () => {
380
+ const config = { url: "http://localhost:3000" };
381
+ expect(isStdioConfig(config)).toBe(false);
382
+ });
383
+ test("returns false for config with both url and command", () => {
384
+ // If both are present, isStdioConfig returns false because url takes precedence
385
+ const config = {
386
+ url: "http://localhost:3000",
387
+ command: "python3",
388
+ args: [],
389
+ env: {},
390
+ };
391
+ expect(isStdioConfig(config)).toBe(false);
392
+ });
393
+ });
394
+ });
@@ -154,6 +154,10 @@ export async function runFullAssessment(options) {
154
154
  return {};
155
155
  }
156
156
  const config = buildConfig(options);
157
+ // Set serverUrl for conformance tests when HTTP/SSE transport is used
158
+ if (serverConfig.url && !config.serverUrl) {
159
+ config.serverUrl = serverConfig.url;
160
+ }
157
161
  // Emit modules_configured event for consumer progress tracking
158
162
  if (config.assessmentCategories) {
159
163
  const enabled = [];