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/src/index.ts CHANGED
@@ -37,7 +37,9 @@ import type { MockServerOptions } from "./mock-server.js";
37
37
  * await server.stop();
38
38
  * ```
39
39
  */
40
- export async function createMock(options: MockServerOptions = {}): Promise<MockServer> {
40
+ export async function createMock(
41
+ options: MockServerOptions = {},
42
+ ): Promise<MockServer> {
41
43
  const server = new MockServer(options);
42
44
  await server.start(options.port ?? 0);
43
45
  return server;
package/src/loader.ts CHANGED
@@ -25,7 +25,11 @@ const json5ReplySchema = z.union([
25
25
  z.object({
26
26
  text: z.string().optional(),
27
27
  reasoning: z.string().optional(),
28
- tools: z.array(z.object({ name: z.string(), args: z.record(z.string(), z.unknown()) })).optional(),
28
+ tools: z
29
+ .array(
30
+ z.object({ name: z.string(), args: z.record(z.string(), z.unknown()) }),
31
+ )
32
+ .optional(),
29
33
  }),
30
34
  ]);
31
35
 
@@ -77,7 +81,9 @@ function compileMatch(when: z.infer<typeof json5MatchSchema>): Match {
77
81
  return parseRegexString(when);
78
82
  }
79
83
  const obj: MatchObject = {
80
- ...(when.message !== undefined && { message: parseRegexString(when.message) }),
84
+ ...(when.message !== undefined && {
85
+ message: parseRegexString(when.message),
86
+ }),
81
87
  ...(when.model !== undefined && { model: parseRegexString(when.model) }),
82
88
  ...(when.system !== undefined && { system: parseRegexString(when.system) }),
83
89
  ...(when.format !== undefined && { format: when.format }),
@@ -124,14 +130,21 @@ function addSequenceRule(
124
130
  rule.remaining = entryCount;
125
131
  }
126
132
 
127
- async function loadJson5File(filePath: string, ctx: LoadContext): Promise<void> {
133
+ async function loadJson5File(
134
+ filePath: string,
135
+ ctx: LoadContext,
136
+ ): Promise<void> {
128
137
  const content = await readFile(filePath, "utf-8");
129
138
  const parsed = json5FileSchema.parse(JSON5.parse(content));
130
139
 
131
140
  const rules = Array.isArray(parsed) ? parsed : parsed.rules;
132
141
  const templates = Array.isArray(parsed) ? undefined : parsed.templates;
133
142
 
134
- if (!Array.isArray(parsed) && parsed.fallback !== undefined && ctx.setFallback) {
143
+ if (
144
+ !Array.isArray(parsed) &&
145
+ parsed.fallback !== undefined &&
146
+ ctx.setFallback
147
+ ) {
135
148
  ctx.setFallback(parsed.fallback);
136
149
  }
137
150
 
@@ -152,7 +165,9 @@ async function loadJson5File(filePath: string, ctx: LoadContext): Promise<void>
152
165
  const handlerSchema = z.custom<Handler>((val): val is Handler => {
153
166
  if (typeof val !== "object" || val === null) return false;
154
167
  const obj = val as Record<string, unknown>;
155
- return typeof obj["match"] === "function" && typeof obj["respond"] === "function";
168
+ return (
169
+ typeof obj["match"] === "function" && typeof obj["respond"] === "function"
170
+ );
156
171
  });
157
172
 
158
173
  const handlerExportSchema = z.object({
@@ -160,7 +175,10 @@ const handlerExportSchema = z.object({
160
175
  fallback: json5ReplySchema.optional(),
161
176
  });
162
177
 
163
- async function loadHandlerFile(filePath: string, ctx: LoadContext): Promise<void> {
178
+ async function loadHandlerFile(
179
+ filePath: string,
180
+ ctx: LoadContext,
181
+ ): Promise<void> {
164
182
  const mod = await import(filePath);
165
183
  const parsed = handlerExportSchema.safeParse(mod);
166
184
  if (!parsed.success) {
@@ -168,14 +186,20 @@ async function loadHandlerFile(filePath: string, ctx: LoadContext): Promise<void
168
186
  `Invalid handler file ${filePath}. Expected default export with { match: Function, respond: Function }.`,
169
187
  );
170
188
  }
171
- const handlers = Array.isArray(parsed.data.default) ? parsed.data.default : [parsed.data.default];
189
+ const handlers = Array.isArray(parsed.data.default)
190
+ ? parsed.data.default
191
+ : [parsed.data.default];
172
192
 
173
193
  if (parsed.data.fallback !== undefined && ctx.setFallback) {
174
194
  ctx.setFallback(parsed.data.fallback);
175
195
  }
176
196
 
177
197
  for (const handler of handlers) {
178
- ctx.engine.addHandler(handler.match, handler.respond, `(handler: ${filePath})`);
198
+ ctx.engine.addHandler(
199
+ handler.match,
200
+ handler.respond,
201
+ `(handler: ${filePath})`,
202
+ );
179
203
  }
180
204
  }
181
205
 
@@ -189,7 +213,10 @@ const loaderByExtension: ReadonlyMap<string, FileLoader> = new Map([
189
213
  [".mjs", loadHandlerFile],
190
214
  ]);
191
215
 
192
- export async function loadRulesFromPath(pathOrDir: string, ctx: LoadContext): Promise<void> {
216
+ export async function loadRulesFromPath(
217
+ pathOrDir: string,
218
+ ctx: LoadContext,
219
+ ): Promise<void> {
193
220
  const info = await stat(pathOrDir);
194
221
 
195
222
  if (info.isFile()) {
package/src/logger.ts CHANGED
@@ -21,7 +21,10 @@ const LEVEL_STYLE = {
21
21
 
22
22
  type ConsoleMethod = "error" | "warn" | "log";
23
23
 
24
- const LEVEL_CONFIG: Record<keyof typeof LEVEL_STYLE, { priority: number; method: ConsoleMethod; dim?: boolean }> = {
24
+ const LEVEL_CONFIG: Record<
25
+ keyof typeof LEVEL_STYLE,
26
+ { priority: number; method: ConsoleMethod; dim?: boolean }
27
+ > = {
25
28
  error: { priority: LEVEL_PRIORITY.error, method: "error" },
26
29
  warn: { priority: LEVEL_PRIORITY.warning, method: "warn" },
27
30
  info: { priority: LEVEL_PRIORITY.info, method: "log" },
@@ -37,16 +40,31 @@ export class Logger {
37
40
  this.threshold = LEVEL_PRIORITY[level];
38
41
  }
39
42
 
40
- private log(key: keyof typeof LEVEL_STYLE, msg: string, args: unknown[]): void {
43
+ private log(
44
+ key: keyof typeof LEVEL_STYLE,
45
+ msg: string,
46
+ args: unknown[],
47
+ ): void {
41
48
  const config = LEVEL_CONFIG[key];
42
49
  if (this.threshold < config.priority) return;
43
50
  const { label, symbol } = LEVEL_STYLE[key];
44
51
  const text = config.dim ? pc.dim(msg) : msg;
45
- console[config.method](`${pc.dim(new Date().toISOString())} ${symbol} ${label} ${text}`, ...args);
52
+ console[config.method](
53
+ `${pc.dim(new Date().toISOString())} ${symbol} ${label} ${text}`,
54
+ ...args,
55
+ );
46
56
  }
47
57
 
48
- error(msg: string, ...args: unknown[]): void { this.log("error", msg, args); }
49
- warn(msg: string, ...args: unknown[]): void { this.log("warn", msg, args); }
50
- info(msg: string, ...args: unknown[]): void { this.log("info", msg, args); }
51
- debug(msg: string, ...args: unknown[]): void { this.log("debug", msg, args); }
58
+ error(msg: string, ...args: unknown[]): void {
59
+ this.log("error", msg, args);
60
+ }
61
+ warn(msg: string, ...args: unknown[]): void {
62
+ this.log("warn", msg, args);
63
+ }
64
+ info(msg: string, ...args: unknown[]): void {
65
+ this.log("info", msg, args);
66
+ }
67
+ debug(msg: string, ...args: unknown[]): void {
68
+ this.log("debug", msg, args);
69
+ }
52
70
  }
@@ -1,7 +1,15 @@
1
1
  import Fastify from "fastify";
2
2
  import type { FastifyInstance } from "fastify";
3
3
  import type {
4
- Match, PendingRule, Reply, ReplyOptions, Resolver, Rule, RuleHandle, RuleSummary, SequenceEntry,
4
+ Match,
5
+ PendingRule,
6
+ Reply,
7
+ ReplyOptions,
8
+ Resolver,
9
+ Rule,
10
+ RuleHandle,
11
+ RuleSummary,
12
+ SequenceEntry,
5
13
  } from "./types.js";
6
14
  import { RuleEngine, createSequenceResolver } from "./rule-engine.js";
7
15
  import { RequestHistory } from "./history.js";
@@ -13,7 +21,11 @@ import { Logger } from "./logger.js";
13
21
  import type { LogLevel } from "./logger.js";
14
22
  import { createRouteHandler } from "./route-handler.js";
15
23
 
16
- const formats: readonly Format[] = [openaiFormat, anthropicFormat, responsesFormat];
24
+ const formats: readonly Format[] = [
25
+ openaiFormat,
26
+ anthropicFormat,
27
+ responsesFormat,
28
+ ];
17
29
 
18
30
  export interface MockServerOptions {
19
31
  readonly port?: number;
@@ -56,8 +68,12 @@ export class MockServer {
56
68
  this.host = options.host ?? "127.0.0.1";
57
69
  this.logger = new Logger(options.logLevel ?? "none");
58
70
  this.defaultOptions = {
59
- ...(options.defaultLatency !== undefined && { latency: options.defaultLatency }),
60
- ...(options.defaultChunkSize !== undefined && { chunkSize: options.defaultChunkSize }),
71
+ ...(options.defaultLatency !== undefined && {
72
+ latency: options.defaultLatency,
73
+ }),
74
+ ...(options.defaultChunkSize !== undefined && {
75
+ chunkSize: options.defaultChunkSize,
76
+ }),
61
77
  };
62
78
  this.app = Fastify({ logger: false });
63
79
 
@@ -171,10 +187,14 @@ export class MockServer {
171
187
  const { loadRulesFromPath } = await import("./loader.js");
172
188
  await loadRulesFromPath(pathOrDir, {
173
189
  engine: this.engine,
174
- setFallback: (reply) => { this.fallbackReply = reply; },
190
+ setFallback: (reply) => {
191
+ this.fallbackReply = reply;
192
+ },
175
193
  });
176
194
  const loaded = this.engine.ruleCount - before;
177
- this.logger.info(`Loaded ${loaded} rule${loaded !== 1 ? "s" : ""} from ${pathOrDir}`);
195
+ this.logger.info(
196
+ `Loaded ${loaded} rule${loaded !== 1 ? "s" : ""} from ${pathOrDir}`,
197
+ );
178
198
  }
179
199
 
180
200
  /** Every request the server has handled. */
@@ -197,7 +217,8 @@ export class MockServer {
197
217
 
198
218
  /** The base URL the server is listening on, e.g. `http://127.0.0.1:12345`. Throws if the server hasn't started. */
199
219
  get url(): string {
200
- if (!this.listening) throw new Error("Server is not running. Call start() first.");
220
+ if (!this.listening)
221
+ throw new Error("Server is not running. Call start() first.");
201
222
  const addr = this.app.server.address();
202
223
  const port = addr !== null && typeof addr === "object" ? addr.port : 0;
203
224
  return `http://${this.host}:${port}`;
@@ -1,6 +1,12 @@
1
1
  import type { FastifyReply, FastifyRequest } from "fastify";
2
2
  import { ZodError } from "zod";
3
- import type { Reply, ReplyObject, ReplyOptions, MockRequest, Rule } from "./types.js";
3
+ import type {
4
+ Reply,
5
+ ReplyObject,
6
+ ReplyOptions,
7
+ MockRequest,
8
+ Rule,
9
+ } from "./types.js";
4
10
  import type { Format } from "./formats/types.js";
5
11
  import type { RuleEngine } from "./rule-engine.js";
6
12
  import type { RequestHistory } from "./history.js";
@@ -21,14 +27,17 @@ async function resolveReply(
21
27
  logger: Logger,
22
28
  ): Promise<{ reply: ReplyObject; ruleDesc: string | undefined }> {
23
29
  if (!matched) {
24
- logger.warn(`No matching rule for "${mockReq.lastMessage}", using fallback`);
30
+ logger.warn(
31
+ `No matching rule for "${mockReq.lastMessage}", using fallback`,
32
+ );
25
33
  return { reply: normalizeReply(fallback), ruleDesc: undefined };
26
34
  }
27
35
 
28
36
  try {
29
- const raw = typeof matched.resolve === "function"
30
- ? await matched.resolve(mockReq)
31
- : matched.resolve;
37
+ const raw =
38
+ typeof matched.resolve === "function"
39
+ ? await matched.resolve(mockReq)
40
+ : matched.resolve;
32
41
  logger.debug(`Matched rule ${matched.description}`);
33
42
  return { reply: normalizeReply(raw), ruleDesc: matched.description };
34
43
  } catch (err) {
@@ -45,7 +54,10 @@ interface RouteHandlerDeps {
45
54
  getFallback: () => Reply;
46
55
  }
47
56
 
48
- export function createRouteHandler(format: Format, deps: RouteHandlerDeps): (request: FastifyRequest, reply: FastifyReply) => Promise<void> {
57
+ export function createRouteHandler(
58
+ format: Format,
59
+ deps: RouteHandlerDeps,
60
+ ): (request: FastifyRequest, reply: FastifyReply) => Promise<void> {
49
61
  const { engine, history, logger, defaultOptions, getFallback } = deps;
50
62
 
51
63
  return async (request: FastifyRequest, reply: FastifyReply) => {
@@ -61,10 +73,19 @@ export function createRouteHandler(format: Format, deps: RouteHandlerDeps): (req
61
73
  mockReq = format.parseRequest(body, meta);
62
74
  } catch (err) {
63
75
  if (err instanceof ZodError) {
64
- logger.warn(`Invalid ${format.name} request: ${err.issues.map((i) => i.message).join(", ")}`);
65
- return reply.status(HTTP_BAD_REQUEST).type("application/json").send(
66
- format.serializeError({ status: HTTP_BAD_REQUEST, message: "Invalid request body", type: "invalid_request_error" }),
76
+ logger.warn(
77
+ `Invalid ${format.name} request: ${err.issues.map((i) => i.message).join(", ")}`,
67
78
  );
79
+ return reply
80
+ .status(HTTP_BAD_REQUEST)
81
+ .type("application/json")
82
+ .send(
83
+ format.serializeError({
84
+ status: HTTP_BAD_REQUEST,
85
+ message: "Invalid request body",
86
+ type: "invalid_request_error",
87
+ }),
88
+ );
68
89
  }
69
90
  throw err;
70
91
  }
@@ -76,14 +97,20 @@ export function createRouteHandler(format: Format, deps: RouteHandlerDeps): (req
76
97
 
77
98
  const matched = engine.match(mockReq);
78
99
  const { reply: resolvedReply, ruleDesc } = await resolveReply(
79
- matched, mockReq, getFallback(), logger,
100
+ matched,
101
+ mockReq,
102
+ getFallback(),
103
+ logger,
80
104
  );
81
105
 
82
106
  if (resolvedReply.error) {
83
107
  const { error } = resolvedReply;
84
108
  logger.info(`Error reply: ${String(error.status)} ${error.message}`);
85
109
  history.record(mockReq, ruleDesc);
86
- return reply.status(error.status).type("application/json").send(format.serializeError(error));
110
+ return reply
111
+ .status(error.status)
112
+ .type("application/json")
113
+ .send(format.serializeError(error));
87
114
  }
88
115
 
89
116
  history.record(mockReq, ruleDesc);
@@ -100,14 +127,22 @@ export function createRouteHandler(format: Format, deps: RouteHandlerDeps): (req
100
127
  logger.debug(`Reply text: "${resolvedReply.text}"`);
101
128
  }
102
129
  if (resolvedReply.tools?.length) {
103
- logger.debug(`Reply tool calls: ${resolvedReply.tools.map((t) => t.name).join(", ")}`);
130
+ logger.debug(
131
+ `Reply tool calls: ${resolvedReply.tools.map((t) => t.name).join(", ")}`,
132
+ );
104
133
  }
105
134
 
106
135
  if (!isStreaming) {
107
- return reply.type("application/json").send(format.serializeComplete(resolvedReply, mockReq.model));
136
+ return reply
137
+ .type("application/json")
138
+ .send(format.serializeComplete(resolvedReply, mockReq.model));
108
139
  }
109
140
 
110
- const chunks = format.serialize(resolvedReply, mockReq.model, effectiveOptions);
141
+ const chunks = format.serialize(
142
+ resolvedReply,
143
+ mockReq.model,
144
+ effectiveOptions,
145
+ );
111
146
  await writeSSE(reply, chunks, effectiveOptions);
112
147
  };
113
148
  }
@@ -1,7 +1,18 @@
1
- import type { Match, MatchObject, MockRequest, Resolver, Reply, ReplyOptions, Rule, RuleSummary } from "./types.js";
1
+ import type {
2
+ Match,
3
+ MatchObject,
4
+ MockRequest,
5
+ Resolver,
6
+ Reply,
7
+ ReplyOptions,
8
+ Rule,
9
+ RuleSummary,
10
+ } from "./types.js";
2
11
 
3
12
  function safeRegex(re: RegExp): RegExp {
4
- return (re.global || re.sticky) ? new RegExp(re.source, re.flags.replace(/[gy]/g, "")) : re;
13
+ return re.global || re.sticky
14
+ ? new RegExp(re.source, re.flags.replace(/[gy]/g, ""))
15
+ : re;
5
16
  }
6
17
 
7
18
  function compilePattern(pattern: string | RegExp): (value: string) => boolean {
@@ -26,16 +37,21 @@ function compileMatcher(match: Match): (req: MockRequest) => boolean {
26
37
  return match;
27
38
  }
28
39
  const obj = match;
29
- const messageTest = obj.message !== undefined ? compilePattern(obj.message) : undefined;
30
- const modelTest = obj.model !== undefined ? compilePattern(obj.model) : undefined;
31
- const systemTest = obj.system !== undefined ? compilePattern(obj.system) : undefined;
40
+ const messageTest =
41
+ obj.message !== undefined ? compilePattern(obj.message) : undefined;
42
+ const modelTest =
43
+ obj.model !== undefined ? compilePattern(obj.model) : undefined;
44
+ const systemTest =
45
+ obj.system !== undefined ? compilePattern(obj.system) : undefined;
32
46
  return (req) => {
33
47
  if (messageTest && !messageTest(req.lastMessage)) return false;
34
48
  if (modelTest && !modelTest(req.model)) return false;
35
49
  if (systemTest && !systemTest(req.systemMessage)) return false;
36
50
  if (obj.format !== undefined && req.format !== obj.format) return false;
37
- if (obj.toolName !== undefined && !req.toolNames.includes(obj.toolName)) return false;
38
- if (obj.toolCallId !== undefined && req.lastToolCallId !== obj.toolCallId) return false;
51
+ if (obj.toolName !== undefined && !req.toolNames.includes(obj.toolName))
52
+ return false;
53
+ if (obj.toolCallId !== undefined && req.lastToolCallId !== obj.toolCallId)
54
+ return false;
39
55
  if (obj.predicate && !obj.predicate(req)) return false;
40
56
  return true;
41
57
  };
@@ -52,7 +68,12 @@ function describeMatch(match: Match): string {
52
68
  return `{${parts.join(", ")}}`;
53
69
  }
54
70
 
55
- function createRule(match: Match, resolve: Resolver, options: ReplyOptions, description?: string): Rule {
71
+ function createRule(
72
+ match: Match,
73
+ resolve: Resolver,
74
+ options: ReplyOptions,
75
+ description?: string,
76
+ ): Rule {
56
77
  return {
57
78
  description: description ?? describeMatch(match),
58
79
  match: compileMatcher(match),
@@ -71,7 +92,8 @@ export function createSequenceResolver(
71
92
  steps: readonly SequenceStep[],
72
93
  rule: { options: ReplyOptions },
73
94
  ): { resolver: () => Reply; entryCount: number } {
74
- if (steps.length === 0) throw new Error("Sequence requires at least one entry.");
95
+ if (steps.length === 0)
96
+ throw new Error("Sequence requires at least one entry.");
75
97
  let index = 0;
76
98
  const last = steps[steps.length - 1]!;
77
99
  return {
@@ -101,7 +123,11 @@ export class RuleEngine {
101
123
  }
102
124
  }
103
125
 
104
- addHandler(matchFn: (req: MockRequest) => boolean, respond: Resolver, description = "(handler)"): Rule {
126
+ addHandler(
127
+ matchFn: (req: MockRequest) => boolean,
128
+ respond: Resolver,
129
+ description = "(handler)",
130
+ ): Rule {
105
131
  const rule = createRule(matchFn, respond, {}, description);
106
132
  this.rules.push(rule);
107
133
  return rule;
@@ -124,7 +150,9 @@ export class RuleEngine {
124
150
  }
125
151
 
126
152
  isDone(): boolean {
127
- return this.rules.every((r) => !Number.isFinite(r.remaining) || r.remaining <= 0);
153
+ return this.rules.every(
154
+ (r) => !Number.isFinite(r.remaining) || r.remaining <= 0,
155
+ );
128
156
  }
129
157
 
130
158
  get ruleCount(): number {
@@ -132,7 +160,10 @@ export class RuleEngine {
132
160
  }
133
161
 
134
162
  describe(): readonly RuleSummary[] {
135
- return this.rules.map((r) => ({ description: r.description, remaining: r.remaining }));
163
+ return this.rules.map((r) => ({
164
+ description: r.description,
165
+ remaining: r.remaining,
166
+ }));
136
167
  }
137
168
 
138
169
  clear(): void {
@@ -10,7 +10,9 @@ export interface ReplyObject {
10
10
  readonly reasoning?: string | undefined;
11
11
  readonly tools?: readonly ToolCall[] | undefined;
12
12
  /** Falls back to `{ input: 10, output: 5 }` if omitted. */
13
- readonly usage?: { readonly input: number; readonly output: number } | undefined;
13
+ readonly usage?:
14
+ | { readonly input: number; readonly output: number }
15
+ | undefined;
14
16
  /** When set, the server responds with this HTTP error instead of a normal reply. */
15
17
  readonly error?: ErrorReply | undefined;
16
18
  }
@@ -42,4 +44,6 @@ export interface ReplyOptions {
42
44
  }
43
45
 
44
46
  /** A single entry in a reply sequence. Can be a plain reply or a reply with per-step options. */
45
- export type SequenceEntry = Reply | { readonly reply: Reply; readonly options?: ReplyOptions };
47
+ export type SequenceEntry =
48
+ | Reply
49
+ | { readonly reply: Reply; readonly options?: ReplyOptions };
package/src/types.ts CHANGED
@@ -1,3 +1,24 @@
1
- export type { FormatName, MockRequest, Message, ToolDef } from "./types/request.js";
2
- export type { Reply, ReplyObject, ErrorReply, ToolCall, Resolver, ReplyOptions, SequenceEntry } from "./types/reply.js";
3
- export type { Match, MatchObject, PendingRule, RuleHandle, RuleSummary, Handler, Rule } from "./types/rule.js";
1
+ export type {
2
+ FormatName,
3
+ MockRequest,
4
+ Message,
5
+ ToolDef,
6
+ } from "./types/request.js";
7
+ export type {
8
+ Reply,
9
+ ReplyObject,
10
+ ErrorReply,
11
+ ToolCall,
12
+ Resolver,
13
+ ReplyOptions,
14
+ SequenceEntry,
15
+ } from "./types/reply.js";
16
+ export type {
17
+ Match,
18
+ MatchObject,
19
+ PendingRule,
20
+ RuleHandle,
21
+ RuleSummary,
22
+ Handler,
23
+ Rule,
24
+ } from "./types/rule.js";
@@ -1,5 +1,11 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { parsePort, parseHost, parseChunkSize, parseLogLevel, parseLatency } from "../src/cli-validators.js";
2
+ import {
3
+ parsePort,
4
+ parseHost,
5
+ parseChunkSize,
6
+ parseLogLevel,
7
+ parseLatency,
8
+ } from "../src/cli-validators.js";
3
9
 
4
10
  describe("parsePort", () => {
5
11
  it("parses a valid port", () => {
@@ -48,7 +54,9 @@ describe("parseLogLevel", () => {
48
54
  );
49
55
 
50
56
  it("throws on invalid level", () => {
51
- expect(() => parseLogLevel("verbose")).toThrow('Invalid log level "verbose"');
57
+ expect(() => parseLogLevel("verbose")).toThrow(
58
+ 'Invalid log level "verbose"',
59
+ );
52
60
  });
53
61
 
54
62
  it("throws on empty string", () => {
@@ -78,11 +86,15 @@ describe("parseHost", () => {
78
86
  });
79
87
 
80
88
  it("rejects an unresolvable hostname", async () => {
81
- await expect(parseHost("not.a.real.host.invalid")).rejects.toThrow("Invalid host");
89
+ await expect(parseHost("not.a.real.host.invalid")).rejects.toThrow(
90
+ "Invalid host",
91
+ );
82
92
  });
83
93
 
84
94
  it("rejects a string with spaces", async () => {
85
- await expect(parseHost("local host")).rejects.toThrow('Invalid host "local host"');
95
+ await expect(parseHost("local host")).rejects.toThrow(
96
+ 'Invalid host "local host"',
97
+ );
86
98
  });
87
99
  });
88
100