llm-mock-server 1.0.2 → 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.
@@ -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.");
@@ -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();
@@ -18,7 +18,9 @@ describe("RuleEngine", () => {
18
18
 
19
19
  it("matches a regex", () => {
20
20
  engine.add(/explain (\w+)/i, "Here is an explanation.");
21
- const rule = engine.match(makeReq({ lastMessage: "Can you explain recursion?" }));
21
+ const rule = engine.match(
22
+ makeReq({ lastMessage: "Can you explain recursion?" }),
23
+ );
22
24
  expect(rule).toBeDefined();
23
25
  });
24
26
 
@@ -44,15 +46,25 @@ describe("RuleEngine", () => {
44
46
 
45
47
  it("matches a MatchObject with message + model", () => {
46
48
  engine.add({ model: "gpt-5.4", message: "hello" }, "Hi from GPT-5.4");
47
- expect(engine.match(makeReq({ model: "gpt-5.4", lastMessage: "hello" }))).toBeDefined();
48
- expect(engine.match(makeReq({ model: "gpt-5.4", lastMessage: "bye" }))).toBeUndefined();
49
- expect(engine.match(makeReq({ model: "claude", lastMessage: "hello" }))).toBeUndefined();
49
+ expect(
50
+ engine.match(makeReq({ model: "gpt-5.4", lastMessage: "hello" })),
51
+ ).toBeDefined();
52
+ expect(
53
+ engine.match(makeReq({ model: "gpt-5.4", lastMessage: "bye" })),
54
+ ).toBeUndefined();
55
+ expect(
56
+ engine.match(makeReq({ model: "claude", lastMessage: "hello" })),
57
+ ).toBeUndefined();
50
58
  });
51
59
 
52
60
  it("matches a MatchObject with system", () => {
53
61
  engine.add({ system: /pirate/i }, "Arrr!");
54
- expect(engine.match(makeReq({ systemMessage: "You are a pirate" }))).toBeDefined();
55
- expect(engine.match(makeReq({ systemMessage: "You are helpful" }))).toBeUndefined();
62
+ expect(
63
+ engine.match(makeReq({ systemMessage: "You are a pirate" })),
64
+ ).toBeDefined();
65
+ expect(
66
+ engine.match(makeReq({ systemMessage: "You are helpful" })),
67
+ ).toBeUndefined();
56
68
  });
57
69
 
58
70
  it("matches a MatchObject with format", () => {
@@ -115,13 +127,17 @@ describe("RuleEngine", () => {
115
127
  (req) => req.lastMessage.includes("test"),
116
128
  "Handler reply",
117
129
  );
118
- expect(engine.match(makeReq({ lastMessage: "this is a test" }))).toBeDefined();
130
+ expect(
131
+ engine.match(makeReq({ lastMessage: "this is a test" })),
132
+ ).toBeDefined();
119
133
  });
120
134
 
121
135
  describe("toolName matching", () => {
122
136
  it("matches when toolNames includes the specified tool", () => {
123
137
  engine.add({ toolName: "get_weather" }, "Weather tool present");
124
- expect(engine.match(makeReq({ toolNames: ["get_weather", "search"] }))).toBeDefined();
138
+ expect(
139
+ engine.match(makeReq({ toolNames: ["get_weather", "search"] })),
140
+ ).toBeDefined();
125
141
  expect(engine.match(makeReq({ toolNames: ["search"] }))).toBeUndefined();
126
142
  });
127
143
  });
@@ -129,8 +145,12 @@ describe("RuleEngine", () => {
129
145
  describe("toolCallId matching", () => {
130
146
  it("matches when lastToolCallId equals the specified id", () => {
131
147
  engine.add({ toolCallId: "call_abc" }, "Tool result");
132
- expect(engine.match(makeReq({ lastToolCallId: "call_abc" }))).toBeDefined();
133
- expect(engine.match(makeReq({ lastToolCallId: "call_xyz" }))).toBeUndefined();
148
+ expect(
149
+ engine.match(makeReq({ lastToolCallId: "call_abc" })),
150
+ ).toBeDefined();
151
+ expect(
152
+ engine.match(makeReq({ lastToolCallId: "call_xyz" })),
153
+ ).toBeUndefined();
134
154
  expect(engine.match(makeReq())).toBeUndefined();
135
155
  });
136
156
  });
@@ -154,20 +174,30 @@ describe("RuleEngine", () => {
154
174
  );
155
175
  expect(engine.match(makeReq({ model: "gpt-5.4" }))).toBeUndefined();
156
176
 
157
- expect(engine.match(makeReq({
158
- model: "gpt-5.4",
159
- messages: [
160
- { role: "system", content: "sys" },
161
- { role: "user", content: "a" },
162
- { role: "assistant", content: "b" },
163
- ],
164
- }))).toBeDefined();
177
+ expect(
178
+ engine.match(
179
+ makeReq({
180
+ model: "gpt-5.4",
181
+ messages: [
182
+ { role: "system", content: "sys" },
183
+ { role: "user", content: "a" },
184
+ { role: "assistant", content: "b" },
185
+ ],
186
+ }),
187
+ ),
188
+ ).toBeDefined();
165
189
  });
166
190
 
167
191
  it("predicate runs after other fields (short-circuits)", () => {
168
192
  let called = false;
169
193
  engine.add(
170
- { model: "claude", predicate: () => { called = true; return true; } },
194
+ {
195
+ model: "claude",
196
+ predicate: () => {
197
+ called = true;
198
+ return true;
199
+ },
200
+ },
171
201
  "Never reached",
172
202
  );
173
203
  engine.match(makeReq({ model: "gpt-5.4" }));