@outfitter/testing 0.2.4 → 0.2.5

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/README.md CHANGED
@@ -93,6 +93,7 @@ const config = createConfig({
93
93
  ```
94
94
 
95
95
  Notes:
96
+
96
97
  - Arrays are replaced, not merged.
97
98
  - `undefined` override values are ignored (defaults remain).
98
99
 
@@ -198,7 +199,7 @@ const server: McpServer = {
198
199
  },
199
200
  ],
200
201
  async invoke(toolName, input) {
201
- const tool = this.tools.find(t => t.name === toolName);
202
+ const tool = this.tools.find((t) => t.name === toolName);
202
203
  if (!tool) throw new Error(`Tool not found: ${toolName}`);
203
204
  return tool.handler(input);
204
205
  },
@@ -260,7 +261,11 @@ Import specific modules directly for smaller bundles:
260
261
 
261
262
  ```typescript
262
263
  // Just fixtures
263
- import { createFixture, withTempDir, withEnv } from "@outfitter/testing/fixtures";
264
+ import {
265
+ createFixture,
266
+ withTempDir,
267
+ withEnv,
268
+ } from "@outfitter/testing/fixtures";
264
269
  ```
265
270
 
266
271
  All harness utilities are available from the main package export:
@@ -273,29 +278,29 @@ import { createCliHarness, createMcpHarness } from "@outfitter/testing";
273
278
 
274
279
  ### Fixtures
275
280
 
276
- | Export | Description |
277
- |--------|-------------|
278
- | `createFixture<T>(defaults)` | Create a fixture factory with deep merge support |
279
- | `withTempDir<T>(fn)` | Run callback with auto-cleaned temp directory |
280
- | `withEnv<T>(vars, fn)` | Run callback with temporary environment variables |
281
+ | Export | Description |
282
+ | ---------------------------- | ------------------------------------------------- |
283
+ | `createFixture<T>(defaults)` | Create a fixture factory with deep merge support |
284
+ | `withTempDir<T>(fn)` | Run callback with auto-cleaned temp directory |
285
+ | `withEnv<T>(vars, fn)` | Run callback with temporary environment variables |
281
286
 
282
287
  ### CLI Harness
283
288
 
284
- | Export | Description |
285
- |--------|-------------|
286
- | `createCliHarness(command)` | Create a CLI test harness |
287
- | `CliHarness` | Interface for CLI harness |
288
- | `CliResult` | Interface for command execution result |
289
+ | Export | Description |
290
+ | --------------------------- | -------------------------------------- |
291
+ | `createCliHarness(command)` | Create a CLI test harness |
292
+ | `CliHarness` | Interface for CLI harness |
293
+ | `CliResult` | Interface for command execution result |
289
294
 
290
295
  ### MCP Harness
291
296
 
292
- | Export | Description |
293
- |--------|-------------|
294
- | `createMcpHarness(server)` | Create an MCP test harness |
295
- | `McpHarness` | Interface for MCP harness |
296
- | `McpServer` | Interface for MCP server |
297
- | `McpTool` | Interface for tool definition |
298
- | `McpToolHandler` | Type for tool handler functions |
297
+ | Export | Description |
298
+ | -------------------------- | ------------------------------- |
299
+ | `createMcpHarness(server)` | Create an MCP test harness |
300
+ | `McpHarness` | Interface for MCP harness |
301
+ | `McpServer` | Interface for MCP server |
302
+ | `McpTool` | Interface for tool definition |
303
+ | `McpToolHandler` | Type for tool handler functions |
299
304
 
300
305
  ## Related Packages
301
306
 
@@ -1,7 +1,30 @@
1
1
  // @bun
2
- import {
3
- createCliHarness
4
- } from "./shared/@outfitter/testing-wfp5f7pq.js";
2
+ // packages/testing/src/cli-harness.ts
3
+ function createCliHarness(command) {
4
+ return {
5
+ async run(args) {
6
+ const child = Bun.spawn([command, ...args], {
7
+ stdin: "pipe",
8
+ stdout: "pipe",
9
+ stderr: "pipe"
10
+ });
11
+ child.stdin?.end();
12
+ const stdoutPromise = child.stdout ? new Response(child.stdout).text() : Promise.resolve("");
13
+ const stderrPromise = child.stderr ? new Response(child.stderr).text() : Promise.resolve("");
14
+ const exitCodePromise = child.exited;
15
+ const [stdout, stderr, exitCode] = await Promise.all([
16
+ stdoutPromise,
17
+ stderrPromise,
18
+ exitCodePromise
19
+ ]);
20
+ return {
21
+ stdout,
22
+ stderr,
23
+ exitCode: typeof exitCode === "number" ? exitCode : 1
24
+ };
25
+ }
26
+ };
27
+ }
5
28
  export {
6
29
  createCliHarness
7
30
  };
@@ -1,8 +1,95 @@
1
1
  // @bun
2
- import {
3
- captureCLI,
4
- mockStdin
5
- } from "./shared/@outfitter/testing-xsfjh1n3.js";
2
+ // packages/testing/src/cli-helpers.ts
3
+ class ExitError extends Error {
4
+ code;
5
+ constructor(code) {
6
+ super(`Process exited with code ${code}`);
7
+ this.code = code;
8
+ }
9
+ }
10
+ async function captureCLI(fn) {
11
+ const stdoutChunks = [];
12
+ const stderrChunks = [];
13
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
14
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
15
+ const originalExit = process.exit.bind(process);
16
+ const originalConsoleLog = console.log;
17
+ const originalConsoleError = console.error;
18
+ process.stdout.write = (chunk, _encoding, cb) => {
19
+ stdoutChunks.push(typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk);
20
+ if (typeof cb === "function")
21
+ cb();
22
+ return true;
23
+ };
24
+ process.stderr.write = (chunk, _encoding, cb) => {
25
+ stderrChunks.push(typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk);
26
+ if (typeof cb === "function")
27
+ cb();
28
+ return true;
29
+ };
30
+ console.log = (...args) => {
31
+ const line = `${args.map(String).join(" ")}
32
+ `;
33
+ stdoutChunks.push(new TextEncoder().encode(line));
34
+ };
35
+ console.error = (...args) => {
36
+ const line = `${args.map(String).join(" ")}
37
+ `;
38
+ stderrChunks.push(new TextEncoder().encode(line));
39
+ };
40
+ process.exit = (code) => {
41
+ throw new ExitError(code ?? 0);
42
+ };
43
+ let exitCode = 0;
44
+ try {
45
+ await fn();
46
+ } catch (error) {
47
+ if (error instanceof ExitError) {
48
+ exitCode = error.code;
49
+ } else {
50
+ exitCode = 1;
51
+ }
52
+ } finally {
53
+ process.stdout.write = originalStdoutWrite;
54
+ process.stderr.write = originalStderrWrite;
55
+ process.exit = originalExit;
56
+ console.log = originalConsoleLog;
57
+ console.error = originalConsoleError;
58
+ }
59
+ const decoder = new TextDecoder("utf-8");
60
+ return {
61
+ stdout: decoder.decode(concatChunks(stdoutChunks)),
62
+ stderr: decoder.decode(concatChunks(stderrChunks)),
63
+ exitCode
64
+ };
65
+ }
66
+ function mockStdin(input) {
67
+ const originalStdin = process.stdin;
68
+ const encoded = new TextEncoder().encode(input);
69
+ const mockStream = {
70
+ async* [Symbol.asyncIterator]() {
71
+ yield encoded;
72
+ },
73
+ fd: 0,
74
+ isTTY: false
75
+ };
76
+ process.stdin = mockStream;
77
+ return {
78
+ restore: () => {
79
+ process.stdin = originalStdin;
80
+ }
81
+ };
82
+ }
83
+ function concatChunks(chunks) {
84
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
85
+ const result = new Uint8Array(totalLength);
86
+ let offset = 0;
87
+ for (const chunk of chunks) {
88
+ result.set(chunk, offset);
89
+ offset += chunk.length;
90
+ }
91
+ return result;
92
+ }
6
93
  export {
7
94
  mockStdin,
8
95
  captureCLI
package/dist/index.d.ts CHANGED
@@ -1,268 +1,6 @@
1
- /**
2
- * Deep partial type that makes all nested properties optional.
3
- * Used for fixture overrides.
4
- */
5
- type DeepPartial<T> = T extends object ? { [P in keyof T]? : DeepPartial<T[P]> } : T;
6
- /**
7
- * Creates a fixture factory for generating test data with defaults.
8
- *
9
- * The factory returns a new object each time it's called, preventing
10
- * test pollution from shared mutable state. Supports deep merging
11
- * for nested objects.
12
- *
13
- * @typeParam T - The shape of the fixture object
14
- * @param defaults - Default values for the fixture
15
- * @returns A factory function that creates fixtures with optional overrides
16
- *
17
- * @example
18
- * ```typescript
19
- * const createUser = createFixture({
20
- * id: 1,
21
- * name: "John Doe",
22
- * email: "john@example.com",
23
- * settings: { theme: "dark", notifications: true }
24
- * });
25
- *
26
- * // Use defaults
27
- * const user1 = createUser();
28
- *
29
- * // Override specific fields
30
- * const user2 = createUser({ name: "Jane Doe", settings: { theme: "light" } });
31
- * ```
32
- */
33
- declare function createFixture<T extends object>(defaults: T): (overrides?: DeepPartial<T>) => T;
34
- /**
35
- * Runs a function with a temporary directory, cleaning up after.
36
- *
37
- * Creates a unique temporary directory before invoking the callback,
38
- * and removes it (including all contents) after the callback completes.
39
- * Cleanup occurs even if the callback throws an error.
40
- *
41
- * @typeParam T - Return type of the callback
42
- * @param fn - Async function that receives the temp directory path
43
- * @returns The value returned by the callback
44
- *
45
- * @example
46
- * ```typescript
47
- * const result = await withTempDir(async (dir) => {
48
- * await Bun.write(join(dir, "test.txt"), "content");
49
- * return await processFiles(dir);
50
- * });
51
- * // Directory is automatically cleaned up
52
- * ```
53
- */
54
- declare function withTempDir<T>(fn: (dir: string) => Promise<T>): Promise<T>;
55
- /**
56
- * Runs a function with temporary environment variables, restoring after.
57
- *
58
- * Sets the specified environment variables before invoking the callback,
59
- * then restores the original values after the callback completes.
60
- * Restoration occurs even if the callback throws an error.
61
- *
62
- * @typeParam T - Return type of the callback
63
- * @param vars - Environment variables to set
64
- * @param fn - Async function to run with the modified environment
65
- * @returns The value returned by the callback
66
- *
67
- * @example
68
- * ```typescript
69
- * await withEnv({ API_KEY: "test-key", DEBUG: "true" }, async () => {
70
- * // process.env.API_KEY is "test-key"
71
- * // process.env.DEBUG is "true"
72
- * await runTests();
73
- * });
74
- * // Original environment is restored
75
- * ```
76
- */
77
- declare function withEnv<T>(vars: Record<string, string>, fn: () => Promise<T>): Promise<T>;
78
- interface LoadFixtureOptions {
79
- /**
80
- * Base fixtures directory.
81
- * Defaults to `${process.cwd()}/__fixtures__`.
82
- */
83
- readonly fixturesDir?: string;
84
- }
85
- /**
86
- * Load a fixture from the fixtures directory.
87
- *
88
- * JSON fixtures are parsed automatically; all other file types are returned as strings.
89
- *
90
- * @example
91
- * ```typescript
92
- * const note = loadFixture<{ id: string }>("mcp/notes.json");
93
- * const config = loadFixture("mcp/config.toml");
94
- * ```
95
- */
96
- declare function loadFixture2<T = string>(name: string, options?: LoadFixtureOptions): T;
97
- /**
98
- * @outfitter/testing - CLI Harness
99
- *
100
- * Test harness for executing and capturing CLI command output.
101
- * Provides a simple interface for running CLI commands in tests
102
- * and capturing their stdout, stderr, and exit code.
103
- *
104
- * @packageDocumentation
105
- */
106
- /**
107
- * Result of a CLI command execution.
108
- *
109
- * Contains the captured output streams and exit code from the command.
110
- */
111
- interface CliResult {
112
- /** Exit code from the command (0 typically indicates success) */
113
- exitCode: number;
114
- /** Standard error output from the command */
115
- stderr: string;
116
- /** Standard output from the command */
117
- stdout: string;
118
- }
119
- /**
120
- * Harness for executing CLI commands in tests.
121
- *
122
- * Provides a simple interface to run a pre-configured command
123
- * with various arguments and capture the results.
124
- */
125
- interface CliHarness {
126
- /**
127
- * Runs the command with the given arguments.
128
- *
129
- * @param args - Command-line arguments to pass to the command
130
- * @returns Promise resolving to the execution result
131
- */
132
- run(args: string[]): Promise<CliResult>;
133
- }
134
- /**
135
- * Creates a CLI harness for testing command-line tools.
136
- *
137
- * The harness wraps a command and provides a simple interface for
138
- * executing it with different arguments, capturing stdout, stderr,
139
- * and exit code for assertions.
140
- *
141
- * @param command - The command to execute (e.g., "echo", "node", "./bin/cli")
142
- * @returns A CliHarness instance for running the command
143
- *
144
- * @example
145
- * ```typescript
146
- * const harness = createCliHarness("./bin/my-cli");
147
- *
148
- * // Test help output
149
- * const helpResult = await harness.run(["--help"]);
150
- * expect(helpResult.stdout).toContain("Usage:");
151
- * expect(helpResult.exitCode).toBe(0);
152
- *
153
- * // Test error case
154
- * const errorResult = await harness.run(["--invalid-flag"]);
155
- * expect(errorResult.stderr).toContain("Unknown option");
156
- * expect(errorResult.exitCode).toBe(1);
157
- * ```
158
- */
159
- declare function createCliHarness(command: string): CliHarness;
160
- /**
161
- * @outfitter/testing - CLI Helpers
162
- *
163
- * Utilities for capturing CLI output and mocking stdin in tests.
164
- *
165
- * @packageDocumentation
166
- */
167
- interface CliTestResult {
168
- /** Process exit code */
169
- exitCode: number;
170
- /** Captured stderr */
171
- stderr: string;
172
- /** Captured stdout */
173
- stdout: string;
174
- }
175
- /**
176
- * Capture stdout/stderr and exit code from an async CLI function.
177
- */
178
- declare function captureCLI(fn: () => Promise<void> | void): Promise<CliTestResult>;
179
- /**
180
- * Mock stdin with provided input.
181
- *
182
- * Returns a restore function for convenience.
183
- */
184
- declare function mockStdin(input: string): {
185
- restore: () => void;
186
- };
187
- import { OutfitterError, Result } from "@outfitter/contracts";
188
- import { McpError, McpServer, SerializedTool, ToolDefinition } from "@outfitter/mcp";
189
- /**
190
- * MCP tool response content.
191
- * Matches the MCP protocol shape used in the spec.
192
- */
193
- interface McpToolResponse {
194
- content: Array<{
195
- type: "text" | "image";
196
- text?: string;
197
- data?: string;
198
- }>;
199
- isError?: boolean;
200
- }
201
- /**
202
- * Test harness for MCP servers.
203
- */
204
- interface McpHarness {
205
- /**
206
- * Call a tool by name with input parameters.
207
- * Returns the MCP-formatted response.
208
- */
209
- callTool(name: string, input: Record<string, unknown>): Promise<Result<McpToolResponse, InstanceType<typeof McpError>>>;
210
- /**
211
- * List all registered tools with schemas.
212
- */
213
- listTools(): SerializedTool[];
214
- /**
215
- * Load fixture data by name (relative to __fixtures__).
216
- */
217
- loadFixture<T = string>(name: string): T;
218
- /**
219
- * Reset harness state between tests.
220
- */
221
- reset(): void;
222
- /**
223
- * Search tools by name or description (case-insensitive).
224
- */
225
- searchTools(query: string): SerializedTool[];
226
- }
227
- interface McpHarnessOptions {
228
- /** Base fixtures directory (defaults to `${process.cwd()}/__fixtures__`). */
229
- readonly fixturesDir?: string;
230
- }
231
- interface McpTestHarnessOptions {
232
- /** Base fixtures directory (defaults to `${process.cwd()}/__fixtures__`). */
233
- readonly fixturesDir?: string;
234
- /** Optional server name for diagnostics. */
235
- readonly name?: string;
236
- /** Tools to register on the test MCP server. */
237
- readonly tools: ToolDefinition<unknown, unknown, OutfitterError>[];
238
- /** Optional server version for diagnostics. */
239
- readonly version?: string;
240
- }
241
- /**
242
- * Creates an MCP test harness from an MCP server.
243
- */
244
- declare function createMcpHarness(server: McpServer, options?: McpHarnessOptions): McpHarness;
245
- /**
246
- * Creates an MCP test harness from tool definitions.
247
- *
248
- * This is a spec-compatible wrapper that builds a test server,
249
- * registers tools, and returns the standard MCP harness.
250
- */
251
- declare function createMCPTestHarness(options: McpTestHarnessOptions): McpHarness;
252
- import { HandlerContext, Logger, ResolvedConfig } from "@outfitter/contracts";
253
- import { z } from "zod";
254
- interface LogEntry {
255
- data?: Record<string, unknown>;
256
- level: "trace" | "debug" | "info" | "warn" | "error" | "fatal";
257
- message: string;
258
- }
259
- interface TestLogger extends Logger {
260
- /** Clear captured logs */
261
- clear(): void;
262
- /** Captured log entries for assertions */
263
- logs: LogEntry[];
264
- }
265
- declare function createTestLogger(context?: Record<string, unknown>): TestLogger;
266
- declare function createTestConfig<T>(schema: z.ZodType<T>, values: Partial<T>): ResolvedConfig;
267
- declare function createTestContext(overrides?: Partial<HandlerContext>): HandlerContext;
268
- export { withTempDir, withEnv, mockStdin, loadFixture2 as loadFixture, createTestLogger, createTestContext, createTestConfig, createMCPTestHarness as createMcpTestHarness, createMcpHarness, createMCPTestHarness, createFixture, createCliHarness, captureCLI, TestLogger, McpToolResponse, McpTestHarnessOptions, McpHarness, LogEntry, CliTestResult, CliResult, CliHarness };
1
+ import { CliTestResult, captureCLI, mockStdin } from "./shared/@outfitter/testing-fyjbwn80.js";
2
+ import { createFixture, loadFixture, withEnv, withTempDir } from "./shared/@outfitter/testing-98fsks4n.js";
3
+ import { McpHarness, McpTestHarnessOptions, McpToolResponse, createMCPTestHarness, createMcpHarness } from "./shared/@outfitter/testing-5gdrv3f5.js";
4
+ import { LogEntry, TestLogger, createTestConfig, createTestContext, createTestLogger } from "./shared/@outfitter/testing-jdfrrv33.js";
5
+ import { CliHarness, CliResult, createCliHarness } from "./shared/@outfitter/testing-xaxkt6c9.js";
6
+ export { withTempDir, withEnv, mockStdin, loadFixture, createTestLogger, createTestContext, createTestConfig, createMCPTestHarness as createMcpTestHarness, createMcpHarness, createMCPTestHarness, createFixture, createCliHarness, captureCLI, TestLogger, McpToolResponse, McpTestHarnessOptions, McpHarness, LogEntry, CliTestResult, CliResult, CliHarness };