@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 +24 -19
- package/dist/cli-harness.js +26 -3
- package/dist/cli-helpers.js +91 -4
- package/dist/index.d.ts +6 -268
- package/dist/index.js +5 -405
- package/dist/mcp-harness.js +43 -4
- package/dist/mock-factories.js +107 -4
- package/package.json +26 -26
- package/dist/shared/@outfitter/testing-05hzzr8n.js +0 -48
- package/dist/shared/@outfitter/testing-kdwa417a.js +0 -111
- package/dist/shared/@outfitter/testing-wfp5f7pq.js +0 -29
- package/dist/shared/@outfitter/testing-xsfjh1n3.js +0 -94
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// packages/testing/src/mock-factories.ts
|
|
3
|
-
import {
|
|
4
|
-
generateRequestId
|
|
5
|
-
} from "@outfitter/contracts";
|
|
6
|
-
function createTestLogger(context = {}) {
|
|
7
|
-
return createTestLoggerWithContext(context, []);
|
|
8
|
-
}
|
|
9
|
-
function createTestLoggerWithContext(context, logs) {
|
|
10
|
-
const write = (level, message, data) => {
|
|
11
|
-
const merged = { ...context, ...data ?? {} };
|
|
12
|
-
const entry = {
|
|
13
|
-
level,
|
|
14
|
-
message
|
|
15
|
-
};
|
|
16
|
-
if (Object.keys(merged).length > 0) {
|
|
17
|
-
entry.data = merged;
|
|
18
|
-
}
|
|
19
|
-
logs.push(entry);
|
|
20
|
-
};
|
|
21
|
-
return {
|
|
22
|
-
logs,
|
|
23
|
-
clear() {
|
|
24
|
-
logs.length = 0;
|
|
25
|
-
},
|
|
26
|
-
trace: (message, metadata) => {
|
|
27
|
-
write("trace", message, metadata);
|
|
28
|
-
},
|
|
29
|
-
debug: (message, metadata) => {
|
|
30
|
-
write("debug", message, metadata);
|
|
31
|
-
},
|
|
32
|
-
info: (message, metadata) => {
|
|
33
|
-
write("info", message, metadata);
|
|
34
|
-
},
|
|
35
|
-
warn: (message, metadata) => {
|
|
36
|
-
write("warn", message, metadata);
|
|
37
|
-
},
|
|
38
|
-
error: (message, metadata) => {
|
|
39
|
-
write("error", message, metadata);
|
|
40
|
-
},
|
|
41
|
-
fatal: (message, metadata) => {
|
|
42
|
-
write("fatal", message, metadata);
|
|
43
|
-
},
|
|
44
|
-
child(childContext) {
|
|
45
|
-
return createTestLoggerWithContext({ ...context, ...childContext }, logs);
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
function createTestConfig(schema, values) {
|
|
50
|
-
const parsed = schema.safeParse(values);
|
|
51
|
-
let data;
|
|
52
|
-
if (parsed.success) {
|
|
53
|
-
data = parsed.data;
|
|
54
|
-
} else {
|
|
55
|
-
const maybePartial = schema.partial;
|
|
56
|
-
if (typeof maybePartial !== "function") {
|
|
57
|
-
throw parsed.error;
|
|
58
|
-
}
|
|
59
|
-
const partialSchema = maybePartial.call(schema);
|
|
60
|
-
const partialParsed = partialSchema.safeParse(values);
|
|
61
|
-
if (!partialParsed.success) {
|
|
62
|
-
throw partialParsed.error;
|
|
63
|
-
}
|
|
64
|
-
data = partialParsed.data;
|
|
65
|
-
}
|
|
66
|
-
return {
|
|
67
|
-
get(key) {
|
|
68
|
-
return getPath(data, key);
|
|
69
|
-
},
|
|
70
|
-
getRequired(key) {
|
|
71
|
-
const value = getPath(data, key);
|
|
72
|
-
if (value === undefined) {
|
|
73
|
-
throw new Error(`Missing required config value: ${key}`);
|
|
74
|
-
}
|
|
75
|
-
return value;
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
function createTestContext(overrides = {}) {
|
|
80
|
-
const logger = overrides.logger ?? createTestLogger();
|
|
81
|
-
const requestId = overrides.requestId ?? generateRequestId();
|
|
82
|
-
const context = {
|
|
83
|
-
requestId,
|
|
84
|
-
logger,
|
|
85
|
-
cwd: overrides.cwd ?? process.cwd(),
|
|
86
|
-
env: overrides.env ?? { ...process.env }
|
|
87
|
-
};
|
|
88
|
-
if (overrides.config !== undefined) {
|
|
89
|
-
context.config = overrides.config;
|
|
90
|
-
}
|
|
91
|
-
if (overrides.signal !== undefined) {
|
|
92
|
-
context.signal = overrides.signal;
|
|
93
|
-
}
|
|
94
|
-
if (overrides.workspaceRoot !== undefined) {
|
|
95
|
-
context.workspaceRoot = overrides.workspaceRoot;
|
|
96
|
-
}
|
|
97
|
-
return context;
|
|
98
|
-
}
|
|
99
|
-
function getPath(obj, key) {
|
|
100
|
-
const parts = key.split(".").filter((part) => part.length > 0);
|
|
101
|
-
let current = obj;
|
|
102
|
-
for (const part of parts) {
|
|
103
|
-
if (current === null || typeof current !== "object") {
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
current = current[part];
|
|
107
|
-
}
|
|
108
|
-
return current;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export { createTestLogger, createTestConfig, createTestContext };
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
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
|
-
}
|
|
28
|
-
|
|
29
|
-
export { createCliHarness };
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
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
|
-
}
|
|
93
|
-
|
|
94
|
-
export { captureCLI, mockStdin };
|