llm-mock-server 1.0.1 → 1.0.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.
Files changed (129) hide show
  1. package/.claude/skills/desloppify/SKILL.md +308 -0
  2. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/canonical_import_20260315_000801.json +242 -0
  3. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/canonical_import_20260315_000905.json +248 -0
  4. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/canonical_import_20260315_000917.json +248 -0
  5. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/canonical_import_20260315_000950.json +311 -0
  6. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/claude_launch_prompt.md +17 -0
  7. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/review_result.json +255 -0
  8. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/review_result.template.json +22 -0
  9. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/reviewer_instructions.md +20 -0
  10. package/.desloppify/external_review_sessions/ext_20260315_000339_a6cdc3e6/session.json +20 -0
  11. package/.desloppify/query.json +284 -0
  12. package/.desloppify/review_packet_blind.json +1303 -0
  13. package/.desloppify/review_packets/holistic_packet_20260315_000339.json +1471 -0
  14. package/.desloppify/state-typescript.json +5114 -0
  15. package/.desloppify/state-typescript.json.bak +5108 -0
  16. package/.editorconfig +12 -0
  17. package/.github/workflows/test.yml +3 -0
  18. package/.oxfmtrc.json +9 -0
  19. package/dist/cli.js +5 -2
  20. package/dist/cli.js.map +1 -1
  21. package/dist/formats/anthropic/index.js +1 -1
  22. package/dist/formats/anthropic/index.js.map +1 -1
  23. package/dist/formats/anthropic/parse.d.ts +1 -1
  24. package/dist/formats/anthropic/parse.d.ts.map +1 -1
  25. package/dist/formats/anthropic/parse.js +1 -1
  26. package/dist/formats/anthropic/parse.js.map +1 -1
  27. package/dist/formats/anthropic/serialize.d.ts +2 -2
  28. package/dist/formats/anthropic/serialize.d.ts.map +1 -1
  29. package/dist/formats/anthropic/serialize.js +6 -3
  30. package/dist/formats/anthropic/serialize.js.map +1 -1
  31. package/dist/formats/openai/index.js +1 -1
  32. package/dist/formats/openai/index.js.map +1 -1
  33. package/dist/formats/openai/parse.d.ts +1 -1
  34. package/dist/formats/openai/parse.d.ts.map +1 -1
  35. package/dist/formats/openai/parse.js +1 -1
  36. package/dist/formats/openai/parse.js.map +1 -1
  37. package/dist/formats/openai/serialize.d.ts +2 -2
  38. package/dist/formats/openai/serialize.d.ts.map +1 -1
  39. package/dist/formats/openai/serialize.js +12 -15
  40. package/dist/formats/openai/serialize.js.map +1 -1
  41. package/dist/formats/request-helpers.d.ts +13 -0
  42. package/dist/formats/request-helpers.d.ts.map +1 -0
  43. package/dist/formats/request-helpers.js +28 -0
  44. package/dist/formats/request-helpers.js.map +1 -0
  45. package/dist/formats/responses/index.js +1 -1
  46. package/dist/formats/responses/index.js.map +1 -1
  47. package/dist/formats/responses/parse.d.ts +1 -1
  48. package/dist/formats/responses/parse.d.ts.map +1 -1
  49. package/dist/formats/responses/parse.js +1 -1
  50. package/dist/formats/responses/parse.js.map +1 -1
  51. package/dist/formats/responses/schema.d.ts +1 -20
  52. package/dist/formats/responses/schema.d.ts.map +1 -1
  53. package/dist/formats/responses/schema.js.map +1 -1
  54. package/dist/formats/responses/serialize.d.ts +2 -2
  55. package/dist/formats/responses/serialize.d.ts.map +1 -1
  56. package/dist/formats/responses/serialize.js +6 -3
  57. package/dist/formats/responses/serialize.js.map +1 -1
  58. package/dist/formats/serialize-helpers.d.ts +14 -0
  59. package/dist/formats/serialize-helpers.d.ts.map +1 -0
  60. package/dist/formats/serialize-helpers.js +25 -0
  61. package/dist/formats/serialize-helpers.js.map +1 -0
  62. package/dist/formats/types.d.ts +3 -3
  63. package/dist/formats/types.d.ts.map +1 -1
  64. package/dist/loader.d.ts +3 -2
  65. package/dist/loader.d.ts.map +1 -1
  66. package/dist/loader.js +6 -9
  67. package/dist/loader.js.map +1 -1
  68. package/dist/logger.d.ts +1 -0
  69. package/dist/logger.d.ts.map +1 -1
  70. package/dist/logger.js +17 -23
  71. package/dist/logger.js.map +1 -1
  72. package/dist/mock-server.d.ts.map +1 -1
  73. package/dist/mock-server.js +8 -15
  74. package/dist/mock-server.js.map +1 -1
  75. package/dist/route-handler.d.ts +2 -1
  76. package/dist/route-handler.d.ts.map +1 -1
  77. package/dist/rule-engine.d.ts +12 -1
  78. package/dist/rule-engine.d.ts.map +1 -1
  79. package/dist/rule-engine.js +14 -0
  80. package/dist/rule-engine.js.map +1 -1
  81. package/dist/types/reply.d.ts +6 -10
  82. package/dist/types/reply.d.ts.map +1 -1
  83. package/dist/types/request.d.ts +7 -11
  84. package/dist/types/request.d.ts.map +1 -1
  85. package/dist/types/rule.d.ts +3 -10
  86. package/dist/types/rule.d.ts.map +1 -1
  87. package/dist/types.d.ts +3 -1
  88. package/dist/types.d.ts.map +1 -1
  89. package/package.json +5 -2
  90. package/scorecard.png +0 -0
  91. package/src/cli-validators.ts +12 -4
  92. package/src/cli.ts +27 -7
  93. package/src/formats/anthropic/index.ts +1 -1
  94. package/src/formats/anthropic/parse.ts +25 -6
  95. package/src/formats/anthropic/schema.ts +16 -8
  96. package/src/formats/anthropic/serialize.ts +116 -28
  97. package/src/formats/openai/index.ts +1 -1
  98. package/src/formats/openai/parse.ts +13 -3
  99. package/src/formats/openai/schema.ts +43 -30
  100. package/src/formats/openai/serialize.ts +84 -30
  101. package/src/formats/{parse-helpers.ts → request-helpers.ts} +4 -32
  102. package/src/formats/responses/index.ts +1 -1
  103. package/src/formats/responses/parse.ts +18 -4
  104. package/src/formats/responses/schema.ts +34 -22
  105. package/src/formats/responses/serialize.ts +237 -38
  106. package/src/formats/serialize-helpers.ts +38 -0
  107. package/src/formats/types.ts +18 -5
  108. package/src/index.ts +3 -1
  109. package/src/loader.ts +43 -20
  110. package/src/logger.ts +31 -19
  111. package/src/mock-server.ts +38 -21
  112. package/src/route-handler.ts +50 -15
  113. package/src/rule-engine.ts +64 -11
  114. package/src/types/reply.ts +12 -12
  115. package/src/types/request.ts +7 -11
  116. package/src/types/rule.ts +3 -10
  117. package/src/types.ts +23 -4
  118. package/test/cli-validators.test.ts +16 -4
  119. package/test/formats/anthropic.test.ts +84 -23
  120. package/test/formats/openai.test.ts +85 -24
  121. package/test/formats/parse-helpers.test.ts +315 -0
  122. package/test/formats/responses.test.ts +99 -34
  123. package/test/helpers/make-req.ts +18 -0
  124. package/test/history.test.ts +361 -0
  125. package/test/loader.test.ts +44 -45
  126. package/test/logger.test.ts +344 -0
  127. package/test/mock-server.test.ts +77 -23
  128. package/test/rule-engine.test.ts +57 -41
  129. package/src/types/index.ts +0 -4
@@ -0,0 +1,344 @@
1
+ import { describe, it, expect, vi, afterEach } from "vitest";
2
+ import { Logger, LEVEL_PRIORITY } from "../src/logger.js";
3
+ import type { LogLevel } from "../src/logger.js";
4
+
5
+ afterEach(() => {
6
+ vi.restoreAllMocks();
7
+ });
8
+
9
+ describe("LEVEL_PRIORITY", () => {
10
+ it("has the expected keys and ascending values", () => {
11
+ expect(LEVEL_PRIORITY).toEqual({
12
+ none: 0,
13
+ error: 1,
14
+ warning: 2,
15
+ info: 3,
16
+ debug: 4,
17
+ all: 5,
18
+ });
19
+ });
20
+
21
+ it("is ordered so that each named level is strictly higher than the previous", () => {
22
+ expect(LEVEL_PRIORITY.none).toBeLessThan(LEVEL_PRIORITY.error);
23
+ expect(LEVEL_PRIORITY.error).toBeLessThan(LEVEL_PRIORITY.warning);
24
+ expect(LEVEL_PRIORITY.warning).toBeLessThan(LEVEL_PRIORITY.info);
25
+ expect(LEVEL_PRIORITY.info).toBeLessThan(LEVEL_PRIORITY.debug);
26
+ expect(LEVEL_PRIORITY.debug).toBeLessThan(LEVEL_PRIORITY.all);
27
+ });
28
+ });
29
+
30
+ describe("Logger", () => {
31
+ describe("constructor", () => {
32
+ it("defaults to 'info' level when no argument is provided", () => {
33
+ const logger = new Logger();
34
+ expect(logger.level).toBe("info");
35
+ });
36
+
37
+ it("accepts an explicit level", () => {
38
+ const logger = new Logger("debug");
39
+ expect(logger.level).toBe("debug");
40
+ });
41
+
42
+ it("level property is readonly and accessible", () => {
43
+ const logger = new Logger("warning");
44
+ expect(logger.level).toBe("warning");
45
+ });
46
+ });
47
+
48
+ describe("error()", () => {
49
+ it("logs to console.error when level is 'error'", () => {
50
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
51
+ const logger = new Logger("error");
52
+ logger.error("something broke");
53
+ expect(spy).toHaveBeenCalledOnce();
54
+ expect(spy.mock.calls[0]![0]).toContain("something broke");
55
+ });
56
+
57
+ it("logs to console.error when level is 'info' (threshold above error)", () => {
58
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
59
+ const logger = new Logger("info");
60
+ logger.error("boom");
61
+ expect(spy).toHaveBeenCalledOnce();
62
+ });
63
+
64
+ it("logs to console.error when level is 'all'", () => {
65
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
66
+ const logger = new Logger("all");
67
+ logger.error("critical");
68
+ expect(spy).toHaveBeenCalledOnce();
69
+ });
70
+
71
+ it("is silent when level is 'none'", () => {
72
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
73
+ const logger = new Logger("none");
74
+ logger.error("should not appear");
75
+ expect(spy).not.toHaveBeenCalled();
76
+ });
77
+
78
+ it("passes extra arguments through to console.error", () => {
79
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
80
+ const logger = new Logger("error");
81
+ const extra = { code: 500 };
82
+ logger.error("fail", extra);
83
+ expect(spy).toHaveBeenCalledOnce();
84
+ expect(spy.mock.calls[0]).toContain(extra);
85
+ });
86
+ });
87
+
88
+ describe("warn()", () => {
89
+ it("logs to console.warn when level is 'warning'", () => {
90
+ const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
91
+ const logger = new Logger("warning");
92
+ logger.warn("heads up");
93
+ expect(spy).toHaveBeenCalledOnce();
94
+ expect(spy.mock.calls[0]![0]).toContain("heads up");
95
+ });
96
+
97
+ it("logs to console.warn when level is 'info' (threshold above warning)", () => {
98
+ const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
99
+ const logger = new Logger("info");
100
+ logger.warn("watch out");
101
+ expect(spy).toHaveBeenCalledOnce();
102
+ });
103
+
104
+ it("logs to console.warn when level is 'debug'", () => {
105
+ const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
106
+ const logger = new Logger("debug");
107
+ logger.warn("careful");
108
+ expect(spy).toHaveBeenCalledOnce();
109
+ });
110
+
111
+ it("is silent when level is 'error' (threshold below warning)", () => {
112
+ const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
113
+ const logger = new Logger("error");
114
+ logger.warn("should not appear");
115
+ expect(spy).not.toHaveBeenCalled();
116
+ });
117
+
118
+ it("is silent when level is 'none'", () => {
119
+ const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
120
+ const logger = new Logger("none");
121
+ logger.warn("nope");
122
+ expect(spy).not.toHaveBeenCalled();
123
+ });
124
+ });
125
+
126
+ describe("info()", () => {
127
+ it("logs to console.log when level is 'info'", () => {
128
+ const spy = vi.spyOn(console, "log").mockImplementation(() => {});
129
+ const logger = new Logger("info");
130
+ logger.info("status update");
131
+ expect(spy).toHaveBeenCalledOnce();
132
+ expect(spy.mock.calls[0]![0]).toContain("status update");
133
+ });
134
+
135
+ it("logs to console.log when level is 'debug' (threshold above info)", () => {
136
+ const spy = vi.spyOn(console, "log").mockImplementation(() => {});
137
+ const logger = new Logger("debug");
138
+ logger.info("still visible");
139
+ expect(spy).toHaveBeenCalledOnce();
140
+ });
141
+
142
+ it("logs to console.log when level is 'all'", () => {
143
+ const spy = vi.spyOn(console, "log").mockImplementation(() => {});
144
+ const logger = new Logger("all");
145
+ logger.info("everything mode");
146
+ expect(spy).toHaveBeenCalledOnce();
147
+ });
148
+
149
+ it("is silent when level is 'warning' (threshold below info)", () => {
150
+ const spy = vi.spyOn(console, "log").mockImplementation(() => {});
151
+ const logger = new Logger("warning");
152
+ logger.info("should not appear");
153
+ expect(spy).not.toHaveBeenCalled();
154
+ });
155
+
156
+ it("is silent when level is 'error'", () => {
157
+ const spy = vi.spyOn(console, "log").mockImplementation(() => {});
158
+ const logger = new Logger("error");
159
+ logger.info("nope");
160
+ expect(spy).not.toHaveBeenCalled();
161
+ });
162
+
163
+ it("is silent when level is 'none'", () => {
164
+ const spy = vi.spyOn(console, "log").mockImplementation(() => {});
165
+ const logger = new Logger("none");
166
+ logger.info("nothing");
167
+ expect(spy).not.toHaveBeenCalled();
168
+ });
169
+ });
170
+
171
+ describe("debug()", () => {
172
+ it("logs to console.log when level is 'debug'", () => {
173
+ const spy = vi.spyOn(console, "log").mockImplementation(() => {});
174
+ const logger = new Logger("debug");
175
+ logger.debug("trace data");
176
+ expect(spy).toHaveBeenCalledOnce();
177
+ expect(spy.mock.calls[0]![0]).toContain("trace data");
178
+ });
179
+
180
+ it("logs to console.log when level is 'all'", () => {
181
+ const spy = vi.spyOn(console, "log").mockImplementation(() => {});
182
+ const logger = new Logger("all");
183
+ logger.debug("everything");
184
+ expect(spy).toHaveBeenCalledOnce();
185
+ });
186
+
187
+ it("is silent when level is 'info' (threshold below debug)", () => {
188
+ const spy = vi.spyOn(console, "log").mockImplementation(() => {});
189
+ const logger = new Logger("info");
190
+ logger.debug("should not appear");
191
+ expect(spy).not.toHaveBeenCalled();
192
+ });
193
+
194
+ it("is silent when level is 'warning'", () => {
195
+ const spy = vi.spyOn(console, "log").mockImplementation(() => {});
196
+ const logger = new Logger("warning");
197
+ logger.debug("nope");
198
+ expect(spy).not.toHaveBeenCalled();
199
+ });
200
+
201
+ it("is silent when level is 'error'", () => {
202
+ const spy = vi.spyOn(console, "log").mockImplementation(() => {});
203
+ const logger = new Logger("error");
204
+ logger.debug("nope");
205
+ expect(spy).not.toHaveBeenCalled();
206
+ });
207
+
208
+ it("is silent when level is 'none'", () => {
209
+ const spy = vi.spyOn(console, "log").mockImplementation(() => {});
210
+ const logger = new Logger("none");
211
+ logger.debug("nothing");
212
+ expect(spy).not.toHaveBeenCalled();
213
+ });
214
+
215
+ it("passes extra arguments through to console.log", () => {
216
+ const spy = vi.spyOn(console, "log").mockImplementation(() => {});
217
+ const logger = new Logger("debug");
218
+ const obj = { detail: true };
219
+ logger.debug("check", obj);
220
+ expect(spy).toHaveBeenCalledOnce();
221
+ expect(spy.mock.calls[0]).toContain(obj);
222
+ });
223
+ });
224
+
225
+ describe("all methods silent at level 'none'", () => {
226
+ it("produces no output for any method", () => {
227
+ const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
228
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
229
+ const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
230
+
231
+ const logger = new Logger("none");
232
+ logger.error("e");
233
+ logger.warn("w");
234
+ logger.info("i");
235
+ logger.debug("d");
236
+
237
+ expect(errorSpy).not.toHaveBeenCalled();
238
+ expect(warnSpy).not.toHaveBeenCalled();
239
+ expect(logSpy).not.toHaveBeenCalled();
240
+ });
241
+ });
242
+
243
+ describe("all methods active at level 'all'", () => {
244
+ it("produces output for every method", () => {
245
+ const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
246
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
247
+ const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
248
+
249
+ const logger = new Logger("all");
250
+ logger.error("e");
251
+ logger.warn("w");
252
+ logger.info("i");
253
+ logger.debug("d");
254
+
255
+ expect(errorSpy).toHaveBeenCalledOnce();
256
+ expect(warnSpy).toHaveBeenCalledOnce();
257
+ // Info and debug both use console.log
258
+ expect(logSpy).toHaveBeenCalledTimes(2);
259
+ });
260
+ });
261
+
262
+ describe("each level as constructor argument", () => {
263
+ const cases: Array<{
264
+ level: LogLevel;
265
+ expectError: boolean;
266
+ expectWarn: boolean;
267
+ expectInfo: boolean;
268
+ expectDebug: boolean;
269
+ }> = [
270
+ {
271
+ level: "none",
272
+ expectError: false,
273
+ expectWarn: false,
274
+ expectInfo: false,
275
+ expectDebug: false,
276
+ },
277
+ {
278
+ level: "error",
279
+ expectError: true,
280
+ expectWarn: false,
281
+ expectInfo: false,
282
+ expectDebug: false,
283
+ },
284
+ {
285
+ level: "warning",
286
+ expectError: true,
287
+ expectWarn: true,
288
+ expectInfo: false,
289
+ expectDebug: false,
290
+ },
291
+ {
292
+ level: "info",
293
+ expectError: true,
294
+ expectWarn: true,
295
+ expectInfo: true,
296
+ expectDebug: false,
297
+ },
298
+ {
299
+ level: "debug",
300
+ expectError: true,
301
+ expectWarn: true,
302
+ expectInfo: true,
303
+ expectDebug: true,
304
+ },
305
+ {
306
+ level: "all",
307
+ expectError: true,
308
+ expectWarn: true,
309
+ expectInfo: true,
310
+ expectDebug: true,
311
+ },
312
+ ];
313
+
314
+ for (const {
315
+ level,
316
+ expectError,
317
+ expectWarn,
318
+ expectInfo,
319
+ expectDebug,
320
+ } of cases) {
321
+ it(`level '${level}' enables the correct methods`, () => {
322
+ const errorSpy = vi
323
+ .spyOn(console, "error")
324
+ .mockImplementation(() => {});
325
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
326
+ const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
327
+
328
+ const logger = new Logger(level);
329
+ logger.error("e");
330
+ logger.warn("w");
331
+ logger.info("i");
332
+ logger.debug("d");
333
+
334
+ expect(errorSpy).toHaveBeenCalledTimes(expectError ? 1 : 0);
335
+ expect(warnSpy).toHaveBeenCalledTimes(expectWarn ? 1 : 0);
336
+
337
+ let expectedLogCalls = 0;
338
+ if (expectInfo) expectedLogCalls++;
339
+ if (expectDebug) expectedLogCalls++;
340
+ expect(logSpy).toHaveBeenCalledTimes(expectedLogCalls);
341
+ });
342
+ }
343
+ });
344
+ });
@@ -2,7 +2,10 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
2
  import { createMock, MockServer } from "../src/index.js";
3
3
 
4
4
  interface OpenAIResponse {
5
- choices: { message: { role: string; content: string }; finish_reason: string }[];
5
+ choices: {
6
+ message: { role: string; content: string };
7
+ finish_reason: string;
8
+ }[];
6
9
  error?: { type: string; message: string };
7
10
  }
8
11
 
@@ -34,7 +37,10 @@ describe("MockServer (end-to-end)", () => {
34
37
  });
35
38
  }
36
39
 
37
- async function postOpenAI(content: string, opts: Record<string, unknown> = {}): Promise<OpenAIResponse> {
40
+ async function postOpenAI(
41
+ content: string,
42
+ opts: Record<string, unknown> = {},
43
+ ): Promise<OpenAIResponse> {
38
44
  const res = await post("/v1/chat/completions", {
39
45
  model: "gpt-5.4",
40
46
  messages: [{ role: "user", content }],
@@ -44,7 +50,10 @@ describe("MockServer (end-to-end)", () => {
44
50
  return res.json() as Promise<OpenAIResponse>;
45
51
  }
46
52
 
47
- async function postAnthropic(content: string, opts: Record<string, unknown> = {}): Promise<AnthropicResponse> {
53
+ async function postAnthropic(
54
+ content: string,
55
+ opts: Record<string, unknown> = {},
56
+ ): Promise<AnthropicResponse> {
48
57
  const res = await post("/v1/messages", {
49
58
  model: "claude-sonnet-4-6",
50
59
  messages: [{ role: "user", content }],
@@ -55,7 +64,10 @@ describe("MockServer (end-to-end)", () => {
55
64
  return res.json() as Promise<AnthropicResponse>;
56
65
  }
57
66
 
58
- async function postResponses(input: string, opts: Record<string, unknown> = {}): Promise<ResponsesAPIResponse> {
67
+ async function postResponses(
68
+ input: string,
69
+ opts: Record<string, unknown> = {},
70
+ ): Promise<ResponsesAPIResponse> {
59
71
  const res = await post("/v1/responses", {
60
72
  model: "codex-mini",
61
73
  input,
@@ -216,7 +228,10 @@ describe("MockServer (end-to-end)", () => {
216
228
  server.when("hello").reply("Hi!");
217
229
  await fetch(`${server.url}/v1/chat/completions`, {
218
230
  method: "POST",
219
- headers: { "Content-Type": "application/json", "X-Custom": "test-value" },
231
+ headers: {
232
+ "Content-Type": "application/json",
233
+ "X-Custom": "test-value",
234
+ },
220
235
  body: JSON.stringify({
221
236
  model: "gpt-5.4",
222
237
  messages: [{ role: "user", content: "hello" }],
@@ -232,7 +247,9 @@ describe("MockServer (end-to-end)", () => {
232
247
 
233
248
  describe("request metadata in predicates", () => {
234
249
  it("matches on headers", async () => {
235
- server.when({ predicate: (req) => req.headers["x-team"] === "alpha" }).reply("Alpha team!");
250
+ server
251
+ .when({ predicate: (req) => req.headers["x-team"] === "alpha" })
252
+ .reply("Alpha team!");
236
253
  server.when("hello").reply("Default");
237
254
 
238
255
  const res = await fetch(`${server.url}/v1/chat/completions`, {
@@ -288,10 +305,12 @@ describe("MockServer (end-to-end)", () => {
288
305
  });
289
306
 
290
307
  it("supports per-step options", async () => {
291
- server.when("step").replySequence([
292
- "Plain.",
293
- { reply: { text: "With options." }, options: { chunkSize: 5 } },
294
- ]);
308
+ server
309
+ .when("step")
310
+ .replySequence([
311
+ "Plain.",
312
+ { reply: { text: "With options." }, options: { chunkSize: 5 } },
313
+ ]);
295
314
 
296
315
  const json = await postOpenAI("step");
297
316
  expect(json.choices[0]!.message.content).toBe("Plain.");
@@ -299,7 +318,7 @@ describe("MockServer (end-to-end)", () => {
299
318
 
300
319
  it("throws on empty sequence", () => {
301
320
  expect(() => server.when("step").replySequence([])).toThrow(
302
- "replySequence requires at least one entry",
321
+ "Sequence requires at least one entry",
303
322
  );
304
323
  });
305
324
  });
@@ -407,7 +426,9 @@ describe("MockServer (end-to-end)", () => {
407
426
  });
408
427
 
409
428
  it("error reply works as a normal rule", async () => {
410
- server.when("fail").reply({ error: { status: 500, message: "Internal error" } });
429
+ server
430
+ .when("fail")
431
+ .reply({ error: { status: 500, message: "Internal error" } });
411
432
  server.when("hello").reply("Hi!");
412
433
 
413
434
  const r1 = await post("/v1/chat/completions", {
@@ -437,11 +458,13 @@ describe("MockServer (end-to-end)", () => {
437
458
  const contentDeltas = data
438
459
  .filter((d) => d !== "[DONE]")
439
460
  .map((d) => JSON.parse(d))
440
- .filter((d: { choices?: { delta?: { content?: string } }[] }) =>
441
- d.choices?.[0]?.delta?.content !== undefined,
461
+ .filter(
462
+ (d: { choices?: { delta?: { content?: string } }[] }) =>
463
+ d.choices?.[0]?.delta?.content !== undefined,
442
464
  )
443
- .map((d: { choices: { delta: { content: string } }[] }) =>
444
- d.choices[0]!.delta.content,
465
+ .map(
466
+ (d: { choices: { delta: { content: string } }[] }) =>
467
+ d.choices[0]!.delta.content,
445
468
  );
446
469
  expect(contentDeltas.length).toBe(3);
447
470
  expect(contentDeltas.join("")).toBe("Hello, world!");
@@ -454,7 +477,12 @@ describe("MockServer (end-to-end)", () => {
454
477
  server.fallback("No match.");
455
478
 
456
479
  const j1 = await postOpenAI("what's the weather?", {
457
- tools: [{ type: "function", function: { name: "get_weather", parameters: {} } }],
480
+ tools: [
481
+ {
482
+ type: "function",
483
+ function: { name: "get_weather", parameters: {} },
484
+ },
485
+ ],
458
486
  });
459
487
  expect(j1.choices[0]!.message.content).toBe("Weather tool detected!");
460
488
 
@@ -471,7 +499,17 @@ describe("MockServer (end-to-end)", () => {
471
499
  const json = await postOpenAI("use the tool", {
472
500
  messages: [
473
501
  { role: "user", content: "use the tool" },
474
- { role: "assistant", content: null, tool_calls: [{ id: "call_abc", type: "function", function: { name: "test", arguments: "{}" } }] },
502
+ {
503
+ role: "assistant",
504
+ content: null,
505
+ tool_calls: [
506
+ {
507
+ id: "call_abc",
508
+ type: "function",
509
+ function: { name: "test", arguments: "{}" },
510
+ },
511
+ ],
512
+ },
475
513
  { role: "tool", tool_call_id: "call_abc", content: "result data" },
476
514
  ],
477
515
  });
@@ -505,7 +543,9 @@ describe("MockServer (end-to-end)", () => {
505
543
 
506
544
  describe("resolver error handling", () => {
507
545
  it("falls back when resolver throws", async () => {
508
- server.when("boom").reply(() => { throw new Error("resolver failed"); });
546
+ server.when("boom").reply(() => {
547
+ throw new Error("resolver failed");
548
+ });
509
549
  server.fallback("Safe fallback.");
510
550
 
511
551
  const json = await postOpenAI("boom");
@@ -530,20 +570,34 @@ describe("MockServer (end-to-end)", () => {
530
570
  await fetch(`${s.url}/v1/chat/completions`, {
531
571
  method: "POST",
532
572
  headers: { "Content-Type": "application/json" },
533
- body: JSON.stringify({ model: "gpt-5.4", messages: [{ role: "user", content: "test" }], stream: false }),
573
+ body: JSON.stringify({
574
+ model: "gpt-5.4",
575
+ messages: [{ role: "user", content: "test" }],
576
+ stream: false,
577
+ }),
534
578
  });
535
579
 
536
580
  await fetch(`${s.url}/v1/chat/completions`, {
537
581
  method: "POST",
538
582
  headers: { "Content-Type": "application/json" },
539
- body: JSON.stringify({ model: "gpt-5.4", messages: [{ role: "user", content: "unmatched" }], stream: false }),
583
+ body: JSON.stringify({
584
+ model: "gpt-5.4",
585
+ messages: [{ role: "user", content: "unmatched" }],
586
+ stream: false,
587
+ }),
540
588
  });
541
589
 
542
- s.when("throw").reply(() => { throw new Error("boom"); });
590
+ s.when("throw").reply(() => {
591
+ throw new Error("boom");
592
+ });
543
593
  await fetch(`${s.url}/v1/chat/completions`, {
544
594
  method: "POST",
545
595
  headers: { "Content-Type": "application/json" },
546
- body: JSON.stringify({ model: "gpt-5.4", messages: [{ role: "user", content: "throw" }], stream: false }),
596
+ body: JSON.stringify({
597
+ model: "gpt-5.4",
598
+ messages: [{ role: "user", content: "throw" }],
599
+ stream: false,
600
+ }),
547
601
  });
548
602
 
549
603
  await s.stop();