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.
- package/.editorconfig +12 -0
- package/.github/workflows/test.yml +3 -0
- package/.oxfmtrc.json +9 -0
- package/package.json +5 -2
- package/src/cli-validators.ts +12 -4
- package/src/cli.ts +22 -6
- package/src/formats/anthropic/parse.ts +24 -5
- package/src/formats/anthropic/schema.ts +16 -8
- package/src/formats/anthropic/serialize.ts +111 -27
- package/src/formats/openai/parse.ts +12 -2
- package/src/formats/openai/schema.ts +43 -30
- package/src/formats/openai/serialize.ts +73 -17
- package/src/formats/request-helpers.ts +2 -1
- package/src/formats/responses/parse.ts +17 -3
- package/src/formats/responses/schema.ts +34 -20
- package/src/formats/responses/serialize.ts +233 -38
- package/src/formats/serialize-helpers.ts +10 -2
- package/src/formats/types.ts +16 -3
- package/src/index.ts +3 -1
- package/src/loader.ts +36 -9
- package/src/logger.ts +25 -7
- package/src/mock-server.ts +28 -7
- package/src/route-handler.ts +49 -14
- package/src/rule-engine.ts +43 -12
- package/src/types/reply.ts +6 -2
- package/src/types.ts +24 -3
- package/test/cli-validators.test.ts +16 -4
- package/test/formats/anthropic.test.ts +80 -19
- package/test/formats/openai.test.ts +85 -24
- package/test/formats/parse-helpers.test.ts +47 -7
- package/test/formats/responses.test.ts +95 -30
- package/test/history.test.ts +18 -5
- package/test/loader.test.ts +33 -18
- package/test/logger.test.ts +59 -9
- package/test/mock-server.test.ts +76 -22
- package/test/rule-engine.test.ts +49 -19
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
import { responsesFormat } from "../../src/formats/responses/index.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
ResponsesEvent,
|
|
5
|
+
ResponsesComplete,
|
|
6
|
+
ResponsesError,
|
|
7
|
+
} from "../../src/formats/responses/schema.js";
|
|
4
8
|
|
|
5
9
|
function parse<T>(chunk: { data: string }): T {
|
|
6
10
|
return JSON.parse(chunk.data) as T;
|
|
@@ -9,7 +13,10 @@ function parse<T>(chunk: { data: string }): T {
|
|
|
9
13
|
describe("Responses Format", () => {
|
|
10
14
|
describe("parseRequest", () => {
|
|
11
15
|
it("parses string input", () => {
|
|
12
|
-
const req = responsesFormat.parseRequest({
|
|
16
|
+
const req = responsesFormat.parseRequest({
|
|
17
|
+
model: "codex-mini",
|
|
18
|
+
input: "Hello world",
|
|
19
|
+
});
|
|
13
20
|
expect(req.format).toBe("responses");
|
|
14
21
|
expect(req.model).toBe("codex-mini");
|
|
15
22
|
expect(req.lastMessage).toBe("Hello world");
|
|
@@ -40,7 +47,12 @@ describe("Responses Format", () => {
|
|
|
40
47
|
it("parses content block arrays", () => {
|
|
41
48
|
const req = responsesFormat.parseRequest({
|
|
42
49
|
model: "codex-mini",
|
|
43
|
-
input: [
|
|
50
|
+
input: [
|
|
51
|
+
{
|
|
52
|
+
role: "user",
|
|
53
|
+
content: [{ type: "input_text", text: "Hello there" }],
|
|
54
|
+
},
|
|
55
|
+
],
|
|
44
56
|
});
|
|
45
57
|
expect(req.lastMessage).toBe("Hello there");
|
|
46
58
|
});
|
|
@@ -49,7 +61,14 @@ describe("Responses Format", () => {
|
|
|
49
61
|
const req = responsesFormat.parseRequest({
|
|
50
62
|
model: "codex-mini",
|
|
51
63
|
input: "read file",
|
|
52
|
-
tools: [
|
|
64
|
+
tools: [
|
|
65
|
+
{
|
|
66
|
+
type: "function",
|
|
67
|
+
name: "read_file",
|
|
68
|
+
description: "Read",
|
|
69
|
+
parameters: {},
|
|
70
|
+
},
|
|
71
|
+
],
|
|
53
72
|
});
|
|
54
73
|
expect(req.tools).toHaveLength(1);
|
|
55
74
|
expect(req.toolNames).toEqual(["read_file"]);
|
|
@@ -60,7 +79,11 @@ describe("Responses Format", () => {
|
|
|
60
79
|
model: "codex-mini",
|
|
61
80
|
input: [
|
|
62
81
|
{ role: "user", content: "hi" },
|
|
63
|
-
{
|
|
82
|
+
{
|
|
83
|
+
type: "function_call_output",
|
|
84
|
+
call_id: "call_abc",
|
|
85
|
+
output: "result",
|
|
86
|
+
},
|
|
64
87
|
],
|
|
65
88
|
});
|
|
66
89
|
expect(req.lastToolCallId).toBe("call_abc");
|
|
@@ -69,10 +92,15 @@ describe("Responses Format", () => {
|
|
|
69
92
|
it("handles content blocks with non-text types (image, etc.)", () => {
|
|
70
93
|
const req = responsesFormat.parseRequest({
|
|
71
94
|
model: "codex-mini",
|
|
72
|
-
input: [
|
|
73
|
-
{
|
|
74
|
-
|
|
75
|
-
|
|
95
|
+
input: [
|
|
96
|
+
{
|
|
97
|
+
role: "user",
|
|
98
|
+
content: [
|
|
99
|
+
{ type: "image_url", url: "https://example.com/img.png" },
|
|
100
|
+
{ type: "input_text", text: "describe this" },
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
],
|
|
76
104
|
});
|
|
77
105
|
expect(req.lastMessage).toBe("describe this");
|
|
78
106
|
});
|
|
@@ -100,10 +128,7 @@ describe("Responses Format", () => {
|
|
|
100
128
|
const req = responsesFormat.parseRequest({
|
|
101
129
|
model: "codex-mini",
|
|
102
130
|
input: "hi",
|
|
103
|
-
tools: [
|
|
104
|
-
{ type: "function", name: "run_code" },
|
|
105
|
-
{ type: "web_search" },
|
|
106
|
-
],
|
|
131
|
+
tools: [{ type: "function", name: "run_code" }, { type: "web_search" }],
|
|
107
132
|
});
|
|
108
133
|
expect(req.tools).toHaveLength(1);
|
|
109
134
|
expect(req.tools![0]!.name).toBe("run_code");
|
|
@@ -114,17 +139,23 @@ describe("Responses Format", () => {
|
|
|
114
139
|
it("starts with response.created and response.in_progress", () => {
|
|
115
140
|
const chunks = responsesFormat.serialize({ text: "Hello" }, "codex-mini");
|
|
116
141
|
expect(parse<ResponsesEvent>(chunks[0]!).type).toBe("response.created");
|
|
117
|
-
expect(parse<ResponsesEvent>(chunks[1]!).type).toBe(
|
|
142
|
+
expect(parse<ResponsesEvent>(chunks[1]!).type).toBe(
|
|
143
|
+
"response.in_progress",
|
|
144
|
+
);
|
|
118
145
|
});
|
|
119
146
|
|
|
120
147
|
it("ends with response.completed", () => {
|
|
121
148
|
const chunks = responsesFormat.serialize({ text: "Hello" }, "codex-mini");
|
|
122
|
-
expect(parse<ResponsesEvent>(chunks.at(-1)!).type).toBe(
|
|
149
|
+
expect(parse<ResponsesEvent>(chunks.at(-1)!).type).toBe(
|
|
150
|
+
"response.completed",
|
|
151
|
+
);
|
|
123
152
|
});
|
|
124
153
|
|
|
125
154
|
it("assigns incrementing sequence_number to every event", () => {
|
|
126
155
|
const chunks = responsesFormat.serialize({ text: "Hello" }, "codex-mini");
|
|
127
|
-
const seqNumbers = chunks.map(
|
|
156
|
+
const seqNumbers = chunks.map(
|
|
157
|
+
(c) => parse<ResponsesEvent>(c).sequence_number!,
|
|
158
|
+
);
|
|
128
159
|
for (let i = 1; i < seqNumbers.length; i++) {
|
|
129
160
|
expect(seqNumbers[i]).toBe(seqNumbers[i - 1]! + 1);
|
|
130
161
|
}
|
|
@@ -135,14 +166,17 @@ describe("Responses Format", () => {
|
|
|
135
166
|
const chunks = responsesFormat.serialize({ text: "Hello" }, "codex-mini");
|
|
136
167
|
const created = parse<ResponsesEvent>(chunks[0]!).response?.created_at;
|
|
137
168
|
const inProgress = parse<ResponsesEvent>(chunks[1]!).response?.created_at;
|
|
138
|
-
const completed = parse<ResponsesEvent>(chunks.at(-1)!).response
|
|
169
|
+
const completed = parse<ResponsesEvent>(chunks.at(-1)!).response
|
|
170
|
+
?.created_at;
|
|
139
171
|
expect(created).toBe(inProgress);
|
|
140
172
|
expect(created).toBe(completed);
|
|
141
173
|
});
|
|
142
174
|
|
|
143
175
|
it("produces text delta events with item_id", () => {
|
|
144
176
|
const chunks = responsesFormat.serialize({ text: "Hello" }, "codex-mini");
|
|
145
|
-
const delta = chunks.find(
|
|
177
|
+
const delta = chunks.find(
|
|
178
|
+
(c) => parse<ResponsesEvent>(c).type === "response.output_text.delta",
|
|
179
|
+
);
|
|
146
180
|
expect(delta).toBeDefined();
|
|
147
181
|
const data = parse<ResponsesEvent>(delta!);
|
|
148
182
|
expect(data.delta).toBe("Hello");
|
|
@@ -153,26 +187,34 @@ describe("Responses Format", () => {
|
|
|
153
187
|
const chunks = responsesFormat.serialize({ text: "Hello" }, "codex-mini");
|
|
154
188
|
const added = chunks.find((c) => {
|
|
155
189
|
const d = parse<ResponsesEvent>(c);
|
|
156
|
-
return
|
|
190
|
+
return (
|
|
191
|
+
d.type === "response.output_item.added" && d.item?.type === "message"
|
|
192
|
+
);
|
|
157
193
|
});
|
|
158
194
|
expect(parse<ResponsesEvent>(added!).item?.status).toBe("in_progress");
|
|
159
195
|
|
|
160
196
|
const done = chunks.find((c) => {
|
|
161
197
|
const d = parse<ResponsesEvent>(c);
|
|
162
|
-
return
|
|
198
|
+
return (
|
|
199
|
+
d.type === "response.output_item.done" && d.item?.type === "message"
|
|
200
|
+
);
|
|
163
201
|
});
|
|
164
202
|
expect(parse<ResponsesEvent>(done!).item?.status).toBe("completed");
|
|
165
203
|
});
|
|
166
204
|
|
|
167
205
|
it("includes annotations on output_text parts", () => {
|
|
168
206
|
const chunks = responsesFormat.serialize({ text: "Hello" }, "codex-mini");
|
|
169
|
-
const partAdded = chunks.find(
|
|
207
|
+
const partAdded = chunks.find(
|
|
208
|
+
(c) => parse<ResponsesEvent>(c).type === "response.content_part.added",
|
|
209
|
+
);
|
|
170
210
|
expect(parse<ResponsesEvent>(partAdded!).part?.annotations).toEqual([]);
|
|
171
211
|
});
|
|
172
212
|
|
|
173
213
|
it("includes content_part.done event with full text", () => {
|
|
174
214
|
const chunks = responsesFormat.serialize({ text: "Hello" }, "codex-mini");
|
|
175
|
-
const partDone = chunks.find(
|
|
215
|
+
const partDone = chunks.find(
|
|
216
|
+
(c) => parse<ResponsesEvent>(c).type === "response.content_part.done",
|
|
217
|
+
);
|
|
176
218
|
expect(partDone).toBeDefined();
|
|
177
219
|
expect(parse<ResponsesEvent>(partDone!).part?.text).toBe("Hello");
|
|
178
220
|
});
|
|
@@ -189,7 +231,9 @@ describe("Responses Format", () => {
|
|
|
189
231
|
expect(types).toContain("response.reasoning_summary_text.done");
|
|
190
232
|
expect(types).toContain("response.reasoning_summary_part.done");
|
|
191
233
|
|
|
192
|
-
const reasoningDone = types.indexOf(
|
|
234
|
+
const reasoningDone = types.indexOf(
|
|
235
|
+
"response.reasoning_summary_text.done",
|
|
236
|
+
);
|
|
193
237
|
const textDelta = types.indexOf("response.output_text.delta");
|
|
194
238
|
expect(reasoningDone).toBeLessThan(textDelta);
|
|
195
239
|
});
|
|
@@ -205,9 +249,14 @@ describe("Responses Format", () => {
|
|
|
205
249
|
});
|
|
206
250
|
|
|
207
251
|
it("accumulates text in completed output", () => {
|
|
208
|
-
const chunks = responsesFormat.serialize(
|
|
252
|
+
const chunks = responsesFormat.serialize(
|
|
253
|
+
{ text: "hello world" },
|
|
254
|
+
"codex-mini",
|
|
255
|
+
);
|
|
209
256
|
const completed = parse<ResponsesEvent>(chunks.at(-1)!);
|
|
210
|
-
expect(completed.response?.output[0]!.content?.[0]?.text).toBe(
|
|
257
|
+
expect(completed.response?.output[0]!.content?.[0]?.text).toBe(
|
|
258
|
+
"hello world",
|
|
259
|
+
);
|
|
211
260
|
});
|
|
212
261
|
|
|
213
262
|
it("produces function_call events for tool calls", () => {
|
|
@@ -217,7 +266,10 @@ describe("Responses Format", () => {
|
|
|
217
266
|
);
|
|
218
267
|
const fnAdded = chunks.find((c) => {
|
|
219
268
|
const d = parse<ResponsesEvent>(c);
|
|
220
|
-
return
|
|
269
|
+
return (
|
|
270
|
+
d.type === "response.output_item.added" &&
|
|
271
|
+
d.item?.type === "function_call"
|
|
272
|
+
);
|
|
221
273
|
});
|
|
222
274
|
expect(fnAdded).toBeDefined();
|
|
223
275
|
const item = parse<ResponsesEvent>(fnAdded!).item!;
|
|
@@ -242,7 +294,10 @@ describe("Responses Format", () => {
|
|
|
242
294
|
|
|
243
295
|
describe("serializeComplete (non-streaming)", () => {
|
|
244
296
|
it("produces correct top-level structure", () => {
|
|
245
|
-
const result = responsesFormat.serializeComplete(
|
|
297
|
+
const result = responsesFormat.serializeComplete(
|
|
298
|
+
{ text: "Hello" },
|
|
299
|
+
"codex-mini",
|
|
300
|
+
) as ResponsesComplete;
|
|
246
301
|
expect(result.object).toBe("response");
|
|
247
302
|
expect(result.status).toBe("completed");
|
|
248
303
|
expect(result.model).toBe("codex-mini");
|
|
@@ -250,7 +305,10 @@ describe("Responses Format", () => {
|
|
|
250
305
|
});
|
|
251
306
|
|
|
252
307
|
it("includes message output item with status and annotations", () => {
|
|
253
|
-
const result = responsesFormat.serializeComplete(
|
|
308
|
+
const result = responsesFormat.serializeComplete(
|
|
309
|
+
{ text: "Hello, world!" },
|
|
310
|
+
"codex-mini",
|
|
311
|
+
) as ResponsesComplete;
|
|
254
312
|
const msg = result.output[0]!;
|
|
255
313
|
expect(msg.type).toBe("message");
|
|
256
314
|
expect(msg.status).toBe("completed");
|
|
@@ -286,13 +344,20 @@ describe("Responses Format", () => {
|
|
|
286
344
|
{ text: "hi", usage: { input: 20, output: 15 } },
|
|
287
345
|
"codex-mini",
|
|
288
346
|
) as ResponsesComplete;
|
|
289
|
-
expect(result.usage).toEqual({
|
|
347
|
+
expect(result.usage).toEqual({
|
|
348
|
+
input_tokens: 20,
|
|
349
|
+
output_tokens: 15,
|
|
350
|
+
total_tokens: 35,
|
|
351
|
+
});
|
|
290
352
|
});
|
|
291
353
|
});
|
|
292
354
|
|
|
293
355
|
describe("serializeError", () => {
|
|
294
356
|
it("produces Responses error format", () => {
|
|
295
|
-
const result = responsesFormat.serializeError({
|
|
357
|
+
const result = responsesFormat.serializeError({
|
|
358
|
+
status: 500,
|
|
359
|
+
message: "Internal error",
|
|
360
|
+
}) as ResponsesError;
|
|
296
361
|
expect(result.error.message).toBe("Internal error");
|
|
297
362
|
});
|
|
298
363
|
});
|
package/test/history.test.ts
CHANGED
|
@@ -140,9 +140,18 @@ describe("RequestHistory", () => {
|
|
|
140
140
|
|
|
141
141
|
describe("where()", () => {
|
|
142
142
|
beforeEach(() => {
|
|
143
|
-
history.record(
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
history.record(
|
|
144
|
+
makeReq({ lastMessage: "hello", model: "gpt-5.4" }),
|
|
145
|
+
"rule-a",
|
|
146
|
+
);
|
|
147
|
+
history.record(
|
|
148
|
+
makeReq({ lastMessage: "world", model: "claude-4" }),
|
|
149
|
+
undefined,
|
|
150
|
+
);
|
|
151
|
+
history.record(
|
|
152
|
+
makeReq({ lastMessage: "hello again", model: "gpt-5.4" }),
|
|
153
|
+
"rule-b",
|
|
154
|
+
);
|
|
146
155
|
});
|
|
147
156
|
|
|
148
157
|
it("filters entries by predicate", () => {
|
|
@@ -153,13 +162,17 @@ describe("RequestHistory", () => {
|
|
|
153
162
|
});
|
|
154
163
|
|
|
155
164
|
it("filters by request properties", () => {
|
|
156
|
-
const claudeRequests = history.where(
|
|
165
|
+
const claudeRequests = history.where(
|
|
166
|
+
(e) => e.request.model === "claude-4",
|
|
167
|
+
);
|
|
157
168
|
expect(claudeRequests).toHaveLength(1);
|
|
158
169
|
expect(claudeRequests[0].request.lastMessage).toBe("world");
|
|
159
170
|
});
|
|
160
171
|
|
|
161
172
|
it("returns an empty array when nothing matches", () => {
|
|
162
|
-
const none = history.where(
|
|
173
|
+
const none = history.where(
|
|
174
|
+
(e) => e.request.lastMessage === "nonexistent",
|
|
175
|
+
);
|
|
163
176
|
expect(none).toEqual([]);
|
|
164
177
|
});
|
|
165
178
|
|
package/test/loader.test.ts
CHANGED
|
@@ -40,11 +40,15 @@ describe("Loader", () => {
|
|
|
40
40
|
await loadRulesFromPath(rulesPath, { engine });
|
|
41
41
|
expect(engine.ruleCount).toBe(2);
|
|
42
42
|
|
|
43
|
-
const match1 = engine.match(
|
|
43
|
+
const match1 = engine.match(
|
|
44
|
+
makeReq({ lastMessage: "Please explain recursion" }),
|
|
45
|
+
);
|
|
44
46
|
if (!match1) throw new Error("expected match for 'explain'");
|
|
45
47
|
expect(match1.resolve).toBe("A function that calls itself.");
|
|
46
48
|
|
|
47
|
-
const match2 = engine.match(
|
|
49
|
+
const match2 = engine.match(
|
|
50
|
+
makeReq({ model: "gpt-5.4", lastMessage: "hello" }),
|
|
51
|
+
);
|
|
48
52
|
expect(match2).toBeDefined();
|
|
49
53
|
});
|
|
50
54
|
|
|
@@ -61,7 +65,9 @@ describe("Loader", () => {
|
|
|
61
65
|
);
|
|
62
66
|
|
|
63
67
|
await loadRulesFromPath(rulesPath, { engine });
|
|
64
|
-
const match = engine.match(
|
|
68
|
+
const match = engine.match(
|
|
69
|
+
makeReq({ lastMessage: "explain polymorphism" }),
|
|
70
|
+
);
|
|
65
71
|
expect(match).toBeDefined();
|
|
66
72
|
});
|
|
67
73
|
|
|
@@ -73,7 +79,9 @@ describe("Loader", () => {
|
|
|
73
79
|
);
|
|
74
80
|
|
|
75
81
|
await loadRulesFromPath(rulesPath, { engine });
|
|
76
|
-
expect(
|
|
82
|
+
expect(
|
|
83
|
+
engine.match(makeReq({ lastMessage: "hello world" })),
|
|
84
|
+
).toBeDefined();
|
|
77
85
|
});
|
|
78
86
|
|
|
79
87
|
it("loads rules with times", async () => {
|
|
@@ -123,7 +131,9 @@ describe("Loader", () => {
|
|
|
123
131
|
}`,
|
|
124
132
|
);
|
|
125
133
|
|
|
126
|
-
await expect(loadRulesFromPath(rulesPath, { engine })).rejects.toThrow(
|
|
134
|
+
await expect(loadRulesFromPath(rulesPath, { engine })).rejects.toThrow(
|
|
135
|
+
"Unknown template",
|
|
136
|
+
);
|
|
127
137
|
});
|
|
128
138
|
|
|
129
139
|
it("loads a replies sequence", async () => {
|
|
@@ -161,7 +171,9 @@ describe("Loader", () => {
|
|
|
161
171
|
let capturedFallback: unknown;
|
|
162
172
|
await loadRulesFromPath(rulesPath, {
|
|
163
173
|
engine,
|
|
164
|
-
setFallback: (reply) => {
|
|
174
|
+
setFallback: (reply) => {
|
|
175
|
+
capturedFallback = reply;
|
|
176
|
+
},
|
|
165
177
|
});
|
|
166
178
|
|
|
167
179
|
expect(capturedFallback).toBe("Default reply.");
|
|
@@ -183,7 +195,9 @@ describe("Loader", () => {
|
|
|
183
195
|
await loadRulesFromPath(handlerPath, { engine });
|
|
184
196
|
expect(engine.ruleCount).toBe(1);
|
|
185
197
|
|
|
186
|
-
const match = engine.match(
|
|
198
|
+
const match = engine.match(
|
|
199
|
+
makeReq({ lastMessage: "summarize this article" }),
|
|
200
|
+
);
|
|
187
201
|
if (!match) throw new Error("expected match for 'summarize'");
|
|
188
202
|
expect(match.resolve).toBeTypeOf("function");
|
|
189
203
|
});
|
|
@@ -233,9 +247,14 @@ describe("Loader", () => {
|
|
|
233
247
|
|
|
234
248
|
it("throws on invalid handler file (missing match/respond)", async () => {
|
|
235
249
|
const handlerPath = join(tmpDir, "bad.ts");
|
|
236
|
-
await writeFile(
|
|
250
|
+
await writeFile(
|
|
251
|
+
handlerPath,
|
|
252
|
+
`export default { mach: () => true, respond: () => "hi" };`,
|
|
253
|
+
);
|
|
237
254
|
|
|
238
|
-
await expect(loadRulesFromPath(handlerPath, { engine })).rejects.toThrow(
|
|
255
|
+
await expect(loadRulesFromPath(handlerPath, { engine })).rejects.toThrow(
|
|
256
|
+
"Invalid handler file",
|
|
257
|
+
);
|
|
239
258
|
});
|
|
240
259
|
|
|
241
260
|
it("loads fallback from handler file", async () => {
|
|
@@ -252,7 +271,9 @@ describe("Loader", () => {
|
|
|
252
271
|
let capturedFallback: unknown;
|
|
253
272
|
await loadRulesFromPath(handlerPath, {
|
|
254
273
|
engine,
|
|
255
|
-
setFallback: (reply) => {
|
|
274
|
+
setFallback: (reply) => {
|
|
275
|
+
capturedFallback = reply;
|
|
276
|
+
},
|
|
256
277
|
});
|
|
257
278
|
|
|
258
279
|
expect(capturedFallback).toBe("Default reply.");
|
|
@@ -262,14 +283,8 @@ describe("Loader", () => {
|
|
|
262
283
|
|
|
263
284
|
describe("directory loading", () => {
|
|
264
285
|
it("loads all .json5 files from a directory", async () => {
|
|
265
|
-
await writeFile(
|
|
266
|
-
|
|
267
|
-
`[{ when: "aaa", reply: "A" }]`,
|
|
268
|
-
);
|
|
269
|
-
await writeFile(
|
|
270
|
-
join(tmpDir, "b.json5"),
|
|
271
|
-
`[{ when: "bbb", reply: "B" }]`,
|
|
272
|
-
);
|
|
286
|
+
await writeFile(join(tmpDir, "a.json5"), `[{ when: "aaa", reply: "A" }]`);
|
|
287
|
+
await writeFile(join(tmpDir, "b.json5"), `[{ when: "bbb", reply: "B" }]`);
|
|
273
288
|
|
|
274
289
|
await loadRulesFromPath(tmpDir, { engine });
|
|
275
290
|
expect(engine.ruleCount).toBe(2);
|
package/test/logger.test.ts
CHANGED
|
@@ -260,18 +260,68 @@ describe("Logger", () => {
|
|
|
260
260
|
});
|
|
261
261
|
|
|
262
262
|
describe("each level as constructor argument", () => {
|
|
263
|
-
const cases: Array<{
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
+
},
|
|
270
312
|
];
|
|
271
313
|
|
|
272
|
-
for (const {
|
|
314
|
+
for (const {
|
|
315
|
+
level,
|
|
316
|
+
expectError,
|
|
317
|
+
expectWarn,
|
|
318
|
+
expectInfo,
|
|
319
|
+
expectDebug,
|
|
320
|
+
} of cases) {
|
|
273
321
|
it(`level '${level}' enables the correct methods`, () => {
|
|
274
|
-
const errorSpy = vi
|
|
322
|
+
const errorSpy = vi
|
|
323
|
+
.spyOn(console, "error")
|
|
324
|
+
.mockImplementation(() => {});
|
|
275
325
|
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
276
326
|
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
277
327
|
|