@forwardimpact/libmock 0.1.2
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/LICENSE +201 -0
- package/README.md +122 -0
- package/package.json +44 -0
- package/src/fixture/assertions.js +42 -0
- package/src/fixture/cache.js +50 -0
- package/src/fixture/eval.js +146 -0
- package/src/fixture/index.js +9 -0
- package/src/fixture/pathway.js +451 -0
- package/src/fixture/services.js +56 -0
- package/src/index.js +3 -0
- package/src/mock/clients.js +264 -0
- package/src/mock/clock.js +62 -0
- package/src/mock/config.js +45 -0
- package/src/mock/data.js +46 -0
- package/src/mock/environments.js +80 -0
- package/src/mock/finder.js +83 -0
- package/src/mock/fs.js +301 -0
- package/src/mock/gh-client.js +28 -0
- package/src/mock/git-client.js +56 -0
- package/src/mock/grpc.js +94 -0
- package/src/mock/http.js +60 -0
- package/src/mock/index.js +49 -0
- package/src/mock/infra.js +297 -0
- package/src/mock/logger.js +42 -0
- package/src/mock/observer.js +78 -0
- package/src/mock/resource-index.js +95 -0
- package/src/mock/service-callbacks.js +39 -0
- package/src/mock/services.js +79 -0
- package/src/mock/spy.js +44 -0
- package/src/mock/storage.js +118 -0
- package/src/mock/subprocess.js +92 -0
- package/src/runtime.js +29 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { spy } from "./spy.js";
|
|
2
|
+
/**
|
|
3
|
+
* Creates a mock storage instance with tracking
|
|
4
|
+
* @param {object} overrides - Method overrides
|
|
5
|
+
* @returns {object} Mock storage with data tracking
|
|
6
|
+
*/
|
|
7
|
+
export function createMockStorage(overrides = {}) {
|
|
8
|
+
const data = new Map();
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
data,
|
|
12
|
+
exists: spy((key) => Promise.resolve(data.has(key))),
|
|
13
|
+
get: spy((key) => {
|
|
14
|
+
const value = data.get(key);
|
|
15
|
+
if (!value) return Promise.reject(new Error("Not found"));
|
|
16
|
+
|
|
17
|
+
if (key.endsWith(".json")) {
|
|
18
|
+
return Promise.resolve(value ? JSON.parse(value) : {});
|
|
19
|
+
}
|
|
20
|
+
if (key.endsWith(".jsonl")) {
|
|
21
|
+
const lines = value.split("\n").filter(Boolean);
|
|
22
|
+
return Promise.resolve(lines.map((line) => JSON.parse(line)));
|
|
23
|
+
}
|
|
24
|
+
return Promise.resolve(value);
|
|
25
|
+
}),
|
|
26
|
+
put: spy((key, value) => {
|
|
27
|
+
data.set(key, value);
|
|
28
|
+
return Promise.resolve();
|
|
29
|
+
}),
|
|
30
|
+
append: spy((key, value) => {
|
|
31
|
+
const existing = data.get(key) || "";
|
|
32
|
+
data.set(key, existing ? `${existing}\n${value}` : value);
|
|
33
|
+
return Promise.resolve();
|
|
34
|
+
}),
|
|
35
|
+
delete: spy((key) => {
|
|
36
|
+
data.delete(key);
|
|
37
|
+
return Promise.resolve();
|
|
38
|
+
}),
|
|
39
|
+
findByPrefix: spy(() => Promise.resolve([])),
|
|
40
|
+
...overrides,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* MockStorage class for OOP-style usage
|
|
46
|
+
*/
|
|
47
|
+
export class MockStorage {
|
|
48
|
+
/**
|
|
49
|
+
* Creates a new MockStorage instance
|
|
50
|
+
*/
|
|
51
|
+
constructor() {
|
|
52
|
+
this.data = new Map();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Gets a value by key
|
|
57
|
+
* @param {string} key - The storage key
|
|
58
|
+
* @returns {Promise<*>} The stored value
|
|
59
|
+
*/
|
|
60
|
+
async get(key) {
|
|
61
|
+
const value = this.data.get(key);
|
|
62
|
+
if (!value) throw new Error("Not found");
|
|
63
|
+
|
|
64
|
+
if (key.endsWith(".jsonl")) {
|
|
65
|
+
return value.split("\n").map((line) => JSON.parse(line));
|
|
66
|
+
}
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Stores a value by key
|
|
72
|
+
* @param {string} key - The storage key
|
|
73
|
+
* @param {*} value - The value to store
|
|
74
|
+
* @returns {Promise<void>}
|
|
75
|
+
*/
|
|
76
|
+
async put(key, value) {
|
|
77
|
+
this.data.set(key, value);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Alias for put - stores a value by key
|
|
82
|
+
* @param {string} key - The storage key
|
|
83
|
+
* @param {*} value - The value to store
|
|
84
|
+
* @returns {Promise<void>}
|
|
85
|
+
*/
|
|
86
|
+
async set(key, value) {
|
|
87
|
+
this.data.set(key, value);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Appends a value to an existing key
|
|
92
|
+
* @param {string} key - The storage key
|
|
93
|
+
* @param {string} value - The value to append
|
|
94
|
+
* @returns {Promise<void>}
|
|
95
|
+
*/
|
|
96
|
+
async append(key, value) {
|
|
97
|
+
const existing = this.data.get(key) || "";
|
|
98
|
+
this.data.set(key, existing ? `${existing}\n${value}` : value);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Checks if a key exists
|
|
103
|
+
* @param {string} key - The storage key
|
|
104
|
+
* @returns {Promise<boolean>} True if key exists
|
|
105
|
+
*/
|
|
106
|
+
async exists(key) {
|
|
107
|
+
return this.data.has(key);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Deletes a value by key
|
|
112
|
+
* @param {string} key - The storage key
|
|
113
|
+
* @returns {Promise<void>}
|
|
114
|
+
*/
|
|
115
|
+
async delete(key) {
|
|
116
|
+
this.data.delete(key);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { spy } from "./spy.js";
|
|
2
|
+
|
|
3
|
+
function asyncIterableOf(str) {
|
|
4
|
+
return {
|
|
5
|
+
async *[Symbol.asyncIterator]() {
|
|
6
|
+
if (str) yield str;
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A captured-chunks stub for a spawned child's writable stdin. Records every
|
|
13
|
+
* `write(chunk)` on `chunks`; `end()`/`destroy()` are no-ops. Mirrors the
|
|
14
|
+
* shape `createDefaultSubprocess().spawn` exposes (the child's `node:stream`
|
|
15
|
+
* Writable) closely enough for a supervisor that pipes into it.
|
|
16
|
+
*/
|
|
17
|
+
function createMockStdinSink() {
|
|
18
|
+
const sink = {
|
|
19
|
+
chunks: [],
|
|
20
|
+
write(chunk) {
|
|
21
|
+
sink.chunks.push(typeof chunk === "string" ? chunk : chunk.toString());
|
|
22
|
+
return true;
|
|
23
|
+
},
|
|
24
|
+
end() {},
|
|
25
|
+
destroy() {},
|
|
26
|
+
on() {
|
|
27
|
+
return sink;
|
|
28
|
+
},
|
|
29
|
+
once() {
|
|
30
|
+
return sink;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
return sink;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Creates a mock subprocess collaborator matching the `Runtime.subprocess`
|
|
38
|
+
* surface. `run(cmd, args, opts)` resolves to `{ stdout, stderr, exitCode }`
|
|
39
|
+
* consulting `responses[cmd]` (default: empty success); `runSync` is its
|
|
40
|
+
* synchronous sibling returning the same shape. `spawn` returns a streaming
|
|
41
|
+
* quad backed by the same responses (its result carries `stdout`/`stderr`
|
|
42
|
+
* AsyncIterables, a captured-chunks `stdin` sink, `exitCode`/`signal` Promises,
|
|
43
|
+
* a `kill(signal)` spy recording on `kills`, and `pid`). All invocations are
|
|
44
|
+
* recorded on `calls`.
|
|
45
|
+
*
|
|
46
|
+
* @param {object} [options]
|
|
47
|
+
* @param {Record<string, {stdout?: string, stderr?: string, exitCode?: number}>} [options.responses]
|
|
48
|
+
* @returns {{run: Function, runSync: Function, spawn: Function, calls: Array<{cmd: string, args: string[], opts: object}>}}
|
|
49
|
+
*/
|
|
50
|
+
export function createMockSubprocess({ responses = {} } = {}) {
|
|
51
|
+
const calls = [];
|
|
52
|
+
const resolve = (cmd) => ({
|
|
53
|
+
stdout: "",
|
|
54
|
+
stderr: "",
|
|
55
|
+
exitCode: 0,
|
|
56
|
+
signal: null,
|
|
57
|
+
...(responses[cmd] ?? {}),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const run = spy(async (cmd, args = [], opts = {}) => {
|
|
61
|
+
calls.push({ cmd, args, opts });
|
|
62
|
+
return resolve(cmd);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const runSync = spy((cmd, args = [], opts = {}) => {
|
|
66
|
+
calls.push({ cmd, args, opts });
|
|
67
|
+
return resolve(cmd);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const spawn = spy((cmd, args = [], opts = {}) => {
|
|
71
|
+
calls.push({ cmd, args, opts });
|
|
72
|
+
const r = resolve(cmd);
|
|
73
|
+
const kills = [];
|
|
74
|
+
return {
|
|
75
|
+
stdout: asyncIterableOf(r.stdout),
|
|
76
|
+
stderr: asyncIterableOf(r.stderr),
|
|
77
|
+
// A captured-chunks writable; `null` only when a response explicitly
|
|
78
|
+
// sets `stdin: null` (matching a child spawned without a stdin pipe).
|
|
79
|
+
stdin: r.stdin === null ? null : createMockStdinSink(),
|
|
80
|
+
exitCode: Promise.resolve(r.exitCode),
|
|
81
|
+
// Terminating signal: `null` (clean exit) unless a response overrides it.
|
|
82
|
+
signal: Promise.resolve(r.signal ?? null),
|
|
83
|
+
kills,
|
|
84
|
+
kill: spy((signal) => {
|
|
85
|
+
kills.push(signal);
|
|
86
|
+
}),
|
|
87
|
+
pid: r.pid ?? 4321,
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return { run, runSync, spawn, calls };
|
|
92
|
+
}
|
package/src/runtime.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createMockClock } from "./mock/clock.js";
|
|
2
|
+
import { createMockFs } from "./mock/fs.js";
|
|
3
|
+
import { createMockProcess } from "./mock/infra.js";
|
|
4
|
+
import { createMockSubprocess } from "./mock/subprocess.js";
|
|
5
|
+
import { createMockFinder } from "./mock/finder.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Build a frozen mock runtime bag for tests, matching the production
|
|
9
|
+
* `Runtime` typedef. Every field defaults to its canonical
|
|
10
|
+
* libmock fake and is independently overridable via `overrides`.
|
|
11
|
+
*
|
|
12
|
+
* @param {object} [overrides] - Per-field replacements.
|
|
13
|
+
* @param {object} [overrides.fs] - Async fs surface (default `createMockFs()`).
|
|
14
|
+
* @param {object} [overrides.fsSync] - Sync fs surface (defaults to `fs`).
|
|
15
|
+
* @param {object} [overrides.proc] - Process surface.
|
|
16
|
+
* @param {object} [overrides.clock] - Clock surface.
|
|
17
|
+
* @param {object} [overrides.subprocess] - Subprocess surface.
|
|
18
|
+
* @param {object} [overrides.finder] - Finder collaborator.
|
|
19
|
+
* @returns {Readonly<import('../../libutil/src/runtime.js').Runtime>}
|
|
20
|
+
*/
|
|
21
|
+
export function createTestRuntime(overrides = {}) {
|
|
22
|
+
const fs = overrides.fs ?? createMockFs();
|
|
23
|
+
const fsSync = overrides.fsSync ?? fs;
|
|
24
|
+
const proc = overrides.proc ?? createMockProcess();
|
|
25
|
+
const clock = overrides.clock ?? createMockClock();
|
|
26
|
+
const subprocess = overrides.subprocess ?? createMockSubprocess();
|
|
27
|
+
const finder = overrides.finder ?? createMockFinder();
|
|
28
|
+
return Object.freeze({ fs, fsSync, proc, clock, subprocess, finder });
|
|
29
|
+
}
|