@forwardimpact/libmock 0.1.14 → 0.1.16
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 +9 -8
- package/package.json +1 -1
- package/src/expect/index.js +28 -20
- package/src/fixture/assertions.js +4 -3
- package/src/fixture/cache.js +10 -10
- package/src/fixture/eval.js +11 -9
- package/src/fixture/pathway.js +9 -9
- package/src/fixture/services.js +5 -5
- package/src/mock/clients.js +14 -14
- package/src/mock/clock.js +11 -10
- package/src/mock/data.js +1 -1
- package/src/mock/environments.js +8 -6
- package/src/mock/finder.js +5 -5
- package/src/mock/fs.js +10 -10
- package/src/mock/gh-client.js +3 -3
- package/src/mock/git-client.js +14 -12
- package/src/mock/grpc.js +1 -1
- package/src/mock/http.js +1 -1
- package/src/mock/index.js +1 -1
- package/src/mock/infra.js +32 -28
- package/src/mock/logger.js +1 -1
- package/src/mock/resource-index.js +2 -2
- package/src/mock/service-callbacks.js +1 -1
- package/src/mock/services.js +1 -1
- package/src/mock/spy.js +1 -1
- package/src/mock/storage.js +4 -4
- package/src/mock/subprocess.js +18 -15
- package/src/runtime.js +3 -3
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ for narrower imports.
|
|
|
75
75
|
|
|
76
76
|
Canonical fakes for the `runtime` collaborator surfaces. Every test that needs
|
|
77
77
|
a fake imports it from here so production and test wire the same shape.
|
|
78
|
-
`createTestRuntime` assembles them into a frozen bag
|
|
78
|
+
`createTestRuntime` assembles them into a frozen bag that mirrors libutil's
|
|
79
79
|
`createDefaultRuntime`.
|
|
80
80
|
|
|
81
81
|
| Surface | Production shape | Factory | Example |
|
|
@@ -92,16 +92,17 @@ a fake imports it from here so production and test wire the same shape.
|
|
|
92
92
|
| grpc health def | stripped `{ Check: { path, … } }` shape | `createMockGrpcHealthDefinition` | `const def = createMockGrpcHealthDefinition();` |
|
|
93
93
|
| repl env | readline/process/os/formatter/storage bundle | `createReplEnvironment` | `const { readline, process } = createReplEnvironment();` |
|
|
94
94
|
|
|
95
|
-
`test/runtime-completeness.test.js` asserts every field on libutil's
|
|
96
|
-
typedef has a matching fake here
|
|
95
|
+
`test/runtime-completeness.test.js` asserts that every field on libutil's
|
|
96
|
+
`Runtime` typedef has a matching fake here. So the fakes cannot drift behind
|
|
97
|
+
the bag.
|
|
97
98
|
|
|
98
99
|
## When to extend libmock
|
|
99
100
|
|
|
100
|
-
|
|
101
|
-
helper
|
|
102
|
-
|
|
101
|
+
Check `src/index.js` before you add a helper locally in a test file. If the
|
|
102
|
+
helper does not exist, and two or more files would use it, add it to libmock
|
|
103
|
+
in the same PR. Do not inline it. See
|
|
103
104
|
[CONTRIBUTING.md](../../CONTRIBUTING.md) READ-DO and DO-CONFIRM checklists for
|
|
104
|
-
the enforced policy
|
|
105
|
+
the enforced policy. See `.jidoka/invariants/libmock.rules.mjs` for the
|
|
105
106
|
pre-commit guard that flags inline reimplementations.
|
|
106
107
|
|
|
107
108
|
## `spy` vs `node:test`'s `mock.fn`
|
|
@@ -118,5 +119,5 @@ fn.mock.resetCalls();
|
|
|
118
119
|
fn.mock.mockImplementation((x) => x + 1);
|
|
119
120
|
```
|
|
120
121
|
|
|
121
|
-
Prefer `spy` over `node:test`'s `mock.fn
|
|
122
|
+
Prefer `spy` over `node:test`'s `mock.fn`. `spy` works under both `bun test`
|
|
122
123
|
(the default runner) and `node --test`.
|
package/package.json
CHANGED
package/src/expect/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dependency-free, runner-independent `expect()` shim. Replaces `expect` from
|
|
3
|
-
* `bun:test` so the test suite can run under `node --test`
|
|
4
|
-
* `
|
|
3
|
+
* `bun:test` so the test suite can run under `node --test` and under
|
|
4
|
+
* `bun test`. `node --test` ships no `expect`. Mirrors how `spy()` replaced
|
|
5
|
+
* `mock.fn`.
|
|
5
6
|
*
|
|
6
7
|
* Covers exactly the matcher surface the converged test files use: `toBe`,
|
|
7
8
|
* `toEqual`, `toMatchObject`, `toBeNull`, `toBeUndefined`, `toBeDefined`,
|
|
@@ -16,9 +17,9 @@ import { AssertionError } from "node:assert";
|
|
|
16
17
|
import { isDeepStrictEqual } from "node:util";
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
|
-
* Render a value for assertion messages
|
|
20
|
+
* Render a value for assertion messages. It does not throw on circular refs.
|
|
20
21
|
* @param {unknown} value - Value to stringify.
|
|
21
|
-
* @returns {string} Human-readable
|
|
22
|
+
* @returns {string} Human-readable text.
|
|
22
23
|
*/
|
|
23
24
|
function show(value) {
|
|
24
25
|
if (typeof value === "string") {
|
|
@@ -41,12 +42,13 @@ function show(value) {
|
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
/**
|
|
44
|
-
* Throw or
|
|
45
|
-
* invert every matcher. `pass` is the un-negated truth of
|
|
45
|
+
* Throw, or swallow when negated. This function is the single choke point
|
|
46
|
+
* that makes `.not` invert every matcher. `pass` is the un-negated truth of
|
|
47
|
+
* the assertion.
|
|
46
48
|
* @param {boolean} pass - Whether the positive assertion holds.
|
|
47
49
|
* @param {boolean} negated - Whether `.not` is active.
|
|
48
|
-
* @param {string} message - Message for
|
|
49
|
-
* @param {string} [negatedMessage] - Message for
|
|
50
|
+
* @param {string} message - Message for a failed positive assertion.
|
|
51
|
+
* @param {string} [negatedMessage] - Message for a failed negated assertion.
|
|
50
52
|
*/
|
|
51
53
|
function settle(pass, negated, message, negatedMessage) {
|
|
52
54
|
if (negated) {
|
|
@@ -65,7 +67,8 @@ function settle(pass, negated, message, negatedMessage) {
|
|
|
65
67
|
const ASYMMETRIC = Symbol.for("libmock.expect.asymmetric");
|
|
66
68
|
|
|
67
69
|
/**
|
|
68
|
-
* Whether `value` is an asymmetric matcher
|
|
70
|
+
* Whether `value` is an asymmetric matcher from `expect.any` or a sibling
|
|
71
|
+
* helper.
|
|
69
72
|
* @param {unknown} value - Candidate.
|
|
70
73
|
* @returns {boolean} True when `value` carries the asymmetric marker.
|
|
71
74
|
*/
|
|
@@ -98,7 +101,8 @@ function equals(actual, expected) {
|
|
|
98
101
|
}
|
|
99
102
|
return expected.every((e, i) => equals(actual[i], e));
|
|
100
103
|
}
|
|
101
|
-
// No asymmetric matcher anywhere
|
|
104
|
+
// No asymmetric matcher anywhere, so exact deep equality is correct and
|
|
105
|
+
// cheap.
|
|
102
106
|
if (!containsAsymmetric(expected)) {
|
|
103
107
|
return isDeepStrictEqual(actual, expected);
|
|
104
108
|
}
|
|
@@ -128,8 +132,9 @@ function containsAsymmetric(value) {
|
|
|
128
132
|
}
|
|
129
133
|
|
|
130
134
|
/**
|
|
131
|
-
* Deep subset match
|
|
132
|
-
* in `actual
|
|
135
|
+
* Deep subset match. Every own enumerable key of `subset` matches the same
|
|
136
|
+
* key in `actual`. The match recurses into plain objects. The match also
|
|
137
|
+
* understands asymmetric matchers.
|
|
133
138
|
* @param {unknown} actual - Candidate object.
|
|
134
139
|
* @param {unknown} subset - Expected subset.
|
|
135
140
|
* @returns {boolean} Whether `actual` contains `subset`.
|
|
@@ -164,7 +169,7 @@ function matchesObject(actual, subset) {
|
|
|
164
169
|
}
|
|
165
170
|
|
|
166
171
|
/**
|
|
167
|
-
* Run a
|
|
172
|
+
* Run a function that may throw. Then apply the `toThrow` predicate.
|
|
168
173
|
* @param {() => unknown} fn - The thunk under test.
|
|
169
174
|
* @param {unknown} matcher - undefined, a string (substring), or a RegExp.
|
|
170
175
|
* @param {boolean} negated - Whether `.not` is active.
|
|
@@ -343,8 +348,9 @@ function matchers(actual, negated) {
|
|
|
343
348
|
}
|
|
344
349
|
|
|
345
350
|
/**
|
|
346
|
-
* Build the async matcher object for a promise
|
|
347
|
-
* awaits the promise
|
|
351
|
+
* Build the async matcher object for an actual value that produces a promise.
|
|
352
|
+
* Each matcher awaits the promise. Then it applies the underlying matcher to
|
|
353
|
+
* the settled value.
|
|
348
354
|
* @param {unknown} actual - A promise (or thenable) under test.
|
|
349
355
|
* @param {boolean} negated - Whether `.not` is active.
|
|
350
356
|
* @param {"resolves" | "rejects"} mode - Which settlement to assert on.
|
|
@@ -381,8 +387,9 @@ function asyncMatchers(actual, negated, mode) {
|
|
|
381
387
|
(name) =>
|
|
382
388
|
(...args) => {
|
|
383
389
|
if (name === "toThrow") {
|
|
384
|
-
// `rejects.toThrow(x)` asserts the rejection's message
|
|
385
|
-
// here is the rejection itself
|
|
390
|
+
// `rejects.toThrow(x)` asserts the rejection's message. The settled
|
|
391
|
+
// value here is the rejection itself. So wrap it back into a thunk
|
|
392
|
+
// that throws.
|
|
386
393
|
return wrap((settled) => {
|
|
387
394
|
const thunk = () => {
|
|
388
395
|
throw settled;
|
|
@@ -438,8 +445,9 @@ export function expect(actual) {
|
|
|
438
445
|
}
|
|
439
446
|
|
|
440
447
|
/**
|
|
441
|
-
*
|
|
442
|
-
*
|
|
448
|
+
* An asymmetric matcher. It matches any value that `ctor` constructed, or any
|
|
449
|
+
* value typed as `ctor`. Use it inside `toEqual` and `toMatchObject`. Mirrors
|
|
450
|
+
* Jest/bun `expect.any`.
|
|
443
451
|
* @param {Function} ctor - A constructor or primitive wrapper (String, Number…).
|
|
444
452
|
* @returns {object} An asymmetric matcher.
|
|
445
453
|
*/
|
|
@@ -467,7 +475,7 @@ expect.any = (ctor) => ({
|
|
|
467
475
|
});
|
|
468
476
|
|
|
469
477
|
/**
|
|
470
|
-
*
|
|
478
|
+
* An asymmetric matcher. It matches any non-null, non-undefined value.
|
|
471
479
|
* @returns {object} An asymmetric matcher.
|
|
472
480
|
*/
|
|
473
481
|
expect.anything = () => ({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Asserts that a function throws with a
|
|
4
|
+
* Asserts that a function throws with a message that matches the pattern
|
|
5
5
|
* @param {Function} fn - Function to test
|
|
6
6
|
* @param {RegExp|string} pattern - Pattern to match
|
|
7
7
|
* @param {string} message - Assertion message
|
|
@@ -15,7 +15,8 @@ export function assertThrowsMessage(fn, pattern, message) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Asserts that an async function rejects with a
|
|
18
|
+
* Asserts that an async function rejects with a message that matches the
|
|
19
|
+
* pattern
|
|
19
20
|
* @param {Function} fn - Async function to test
|
|
20
21
|
* @param {RegExp|string} pattern - Pattern to match
|
|
21
22
|
* @param {string} message - Assertion message
|
|
@@ -29,7 +30,7 @@ export async function assertRejectsMessage(fn, pattern, message) {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
|
-
* Creates a deferred promise for async
|
|
33
|
+
* Creates a deferred promise for async tests
|
|
33
34
|
* @returns {object} Object with promise, resolve, and reject
|
|
34
35
|
*/
|
|
35
36
|
export function createDeferred() {
|
package/src/fixture/cache.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* test file per process
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Helpers that cache fixtures across files. The test runner currently executes
|
|
3
|
+
* one test file per process. But each `test(...)` case re-parses the fixtures
|
|
4
|
+
* that a file loads inline, unless you hoist them. One example is the starter
|
|
5
|
+
* standard YAML from `createDataLoader(runtime).loadAllData(dir)`.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const caches = new WeakMap();
|
|
9
9
|
const stringCaches = new Map();
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Wraps an async factory
|
|
12
|
+
* Wraps an async factory. Calls the factory at most once per unique key.
|
|
13
13
|
*
|
|
14
14
|
* @template T
|
|
15
|
-
* @param {string} key - Cache key.
|
|
16
|
-
* @param {() => Promise<T>} factory - Factory
|
|
15
|
+
* @param {string} key - Cache key. The same key returns the cached value.
|
|
16
|
+
* @param {() => Promise<T>} factory - Factory to call on a cache miss.
|
|
17
17
|
* @returns {Promise<T>}
|
|
18
18
|
*/
|
|
19
19
|
export async function memoizeAsync(key, factory) {
|
|
@@ -29,8 +29,8 @@ export async function memoizeAsync(key, factory) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Caches the result of `fn(subject)
|
|
33
|
-
* for expensive derivations over a frozen input object.
|
|
32
|
+
* Caches the result of `fn(subject)`. The identity of `subject` is the key.
|
|
33
|
+
* Use it for expensive derivations over a frozen input object.
|
|
34
34
|
*
|
|
35
35
|
* @template S, T
|
|
36
36
|
* @param {S} subject
|
|
@@ -43,7 +43,7 @@ export function memoizeOnSubject(subject, fn) {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
* Clears all memoization caches.
|
|
46
|
+
* Clears all memoization caches. Use it only in self-tests of this helper.
|
|
47
47
|
*/
|
|
48
48
|
export function __resetMemoCaches() {
|
|
49
49
|
stringCaches.clear();
|
package/src/fixture/eval.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Builds a tool-use message envelope
|
|
10
|
+
* Builds a tool-use message envelope in the shape the agent SDK emits.
|
|
11
11
|
* Replaces per-file concludeMsg / redirectMsg / tellMsg / shareMsg helpers.
|
|
12
12
|
*
|
|
13
13
|
* @param {string} name - Tool name, e.g. "Conclude", "Redirect", "Tell", "Share".
|
|
@@ -33,7 +33,8 @@ export function createToolUseMsg(name, input, { id } = {}) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* Builds an assistant text message
|
|
36
|
+
* Builds an envelope for an assistant text message, in the shape the agent
|
|
37
|
+
* SDK emits.
|
|
37
38
|
* @param {string} text - Text content.
|
|
38
39
|
* @returns {object} Assistant message with a single text content block.
|
|
39
40
|
*/
|
|
@@ -76,7 +77,8 @@ export function stripAnsi(s) {
|
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
/**
|
|
79
|
-
* Writes each line
|
|
80
|
+
* Writes each line to the writer with a newline after it. Then ends the
|
|
81
|
+
* writer.
|
|
80
82
|
* @param {import("node:stream").Writable} writer
|
|
81
83
|
* @param {string[]} lines
|
|
82
84
|
* @returns {Promise<void>}
|
|
@@ -120,15 +122,15 @@ export function createTestTrace(overrides = {}) {
|
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
/**
|
|
123
|
-
* Creates an async-generator agent query
|
|
125
|
+
* Creates an async-generator stub for an agent query. The shape of `messages`
|
|
124
126
|
* determines per-call behaviour:
|
|
125
|
-
* - Flat array of message objects:
|
|
126
|
-
* - Array of arrays (batches): the Nth
|
|
127
|
-
*
|
|
127
|
+
* - Flat array of message objects: the stub yields them on every call.
|
|
128
|
+
* - Array of arrays (batches): the Nth call yields messages from the Nth
|
|
129
|
+
* batch. Later calls past the last batch repeat the last batch.
|
|
128
130
|
*
|
|
129
131
|
* @param {object[] | object[][]} messages
|
|
130
|
-
* @param {(params: object) => void} [onParams] -
|
|
131
|
-
* @returns {Function} Async generator
|
|
132
|
+
* @param {(params: object) => void} [onParams] - Receives the call params.
|
|
133
|
+
* @returns {Function} Async generator that mimics `query({...})`.
|
|
132
134
|
*/
|
|
133
135
|
export function createMockAgentQuery(messages, onParams) {
|
|
134
136
|
const isBatched = Array.isArray(messages[0]);
|
package/src/fixture/pathway.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pathway test fixtures
|
|
2
|
+
* Pathway test fixtures that create realistic test data
|
|
3
3
|
* Provides mock disciplines, levels, tracks, skills, and behaviours
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -27,7 +27,7 @@ export function createTestLevel(overrides = {}) {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* Creates a set of mock levels
|
|
30
|
+
* Creates a set of mock levels from junior to senior
|
|
31
31
|
* @returns {object[]} Array of mock levels
|
|
32
32
|
*/
|
|
33
33
|
export function createTestLevels() {
|
|
@@ -260,9 +260,9 @@ export function createTestDriver(overrides = {}) {
|
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
/**
|
|
263
|
-
* Creates a mock skill
|
|
263
|
+
* Creates a mock skill-matrix entry
|
|
264
264
|
* @param {object} overrides - Properties to override
|
|
265
|
-
* @returns {object} Mock skill
|
|
265
|
+
* @returns {object} Mock skill-matrix entry
|
|
266
266
|
*/
|
|
267
267
|
export function createTestSkillEntry(overrides = {}) {
|
|
268
268
|
return {
|
|
@@ -278,9 +278,9 @@ export function createTestSkillEntry(overrides = {}) {
|
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
/**
|
|
281
|
-
* Creates a mock behaviour
|
|
281
|
+
* Creates a mock behaviour-profile entry
|
|
282
282
|
* @param {object} overrides - Properties to override
|
|
283
|
-
* @returns {object} Mock behaviour
|
|
283
|
+
* @returns {object} Mock behaviour-profile entry
|
|
284
284
|
*/
|
|
285
285
|
export function createTestBehaviourEntry(overrides = {}) {
|
|
286
286
|
return {
|
|
@@ -314,7 +314,7 @@ export function createTestDrivers() {
|
|
|
314
314
|
}
|
|
315
315
|
|
|
316
316
|
/**
|
|
317
|
-
* Creates a set of mock capabilities
|
|
317
|
+
* Creates a set of mock capabilities across delivery / scale / ai.
|
|
318
318
|
* @returns {object[]}
|
|
319
319
|
*/
|
|
320
320
|
export function createTestCapabilities() {
|
|
@@ -416,7 +416,7 @@ export function createTestRoster(overrides = {}) {
|
|
|
416
416
|
}
|
|
417
417
|
|
|
418
418
|
/**
|
|
419
|
-
* Creates a mock evidence row
|
|
419
|
+
* Creates a mock evidence row that map tests and landmark tests share.
|
|
420
420
|
* @param {object} [overrides]
|
|
421
421
|
* @returns {object}
|
|
422
422
|
*/
|
|
@@ -433,7 +433,7 @@ export function createTestEvidenceRow(overrides = {}) {
|
|
|
433
433
|
}
|
|
434
434
|
|
|
435
435
|
/**
|
|
436
|
-
* Creates a mock skill
|
|
436
|
+
* Creates a mock skill with per-level markers (human + agent arrays).
|
|
437
437
|
* @param {object} [overrides]
|
|
438
438
|
* @returns {object}
|
|
439
439
|
*/
|
package/src/fixture/services.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Test fixture utilities
|
|
2
|
+
* Test fixture utilities that create dynamic test data
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Creates sample request data for
|
|
6
|
+
* Creates sample request data for tests
|
|
7
7
|
* @param {object} overrides - Properties to override
|
|
8
8
|
* @returns {object} Test request object
|
|
9
9
|
*/
|
|
@@ -17,7 +17,7 @@ export function createTestRequest(overrides = {}) {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* Creates sample vector data for
|
|
20
|
+
* Creates sample vector data for tests
|
|
21
21
|
* @param {number} count - Number of vectors to create
|
|
22
22
|
* @returns {Array} Array of test vectors
|
|
23
23
|
*/
|
|
@@ -30,7 +30,7 @@ export function createTestVectors(count = 3) {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
* Creates sample message data for
|
|
33
|
+
* Creates sample message data for tests
|
|
34
34
|
* @param {number} count - Number of messages to create
|
|
35
35
|
* @returns {Array} Array of test messages
|
|
36
36
|
*/
|
|
@@ -43,7 +43,7 @@ export function createTestMessages(count = 3) {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
* Creates sample chunk data for
|
|
46
|
+
* Creates sample chunk data for tests
|
|
47
47
|
* @param {number} count - Number of chunks to create
|
|
48
48
|
* @returns {Array} Array of test chunks
|
|
49
49
|
*/
|
package/src/mock/clients.js
CHANGED
|
@@ -76,11 +76,11 @@ export function createMockAgentClient(overrides = {}) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
|
-
* Creates a mock
|
|
79
|
+
* Creates a mock span client
|
|
80
80
|
* @param {object} overrides - Method overrides
|
|
81
|
-
* @returns {object} Mock
|
|
81
|
+
* @returns {object} Mock span client
|
|
82
82
|
*/
|
|
83
|
-
export function
|
|
83
|
+
export function createMockSpanClient(overrides = {}) {
|
|
84
84
|
return {
|
|
85
85
|
RecordSpan: spy(() => Promise.resolve()),
|
|
86
86
|
...overrides,
|
|
@@ -168,12 +168,12 @@ export function createMockDiscussionClient(overrides = {}) {
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
/**
|
|
171
|
-
* Reject a request that omits `tenant_id
|
|
172
|
-
* `
|
|
173
|
-
* both deployment modes (single-tenant binds the literal `"default"`)
|
|
174
|
-
* empty value is a caller error
|
|
175
|
-
* applies the same guard
|
|
176
|
-
* `tenant_id`
|
|
171
|
+
* Reject a request that omits `tenant_id`. This mirrors the `requireTenant`
|
|
172
|
+
* guard in `services/bridge`. Every tenant-scoped RPC carries a `tenant_id`
|
|
173
|
+
* in both deployment modes (single-tenant binds the literal `"default"`).
|
|
174
|
+
* An empty value is a caller error. It is not an empty result. The stateful
|
|
175
|
+
* mock applies the same guard. A production caller that forgets to thread a
|
|
176
|
+
* `tenant_id` fails in tests exactly as it fails against the real service.
|
|
177
177
|
*
|
|
178
178
|
* @param {{tenant_id?: string}} obj
|
|
179
179
|
* @returns {string} the validated tenant id
|
|
@@ -208,7 +208,7 @@ function coerceInt64Fields(obj) {
|
|
|
208
208
|
|
|
209
209
|
/**
|
|
210
210
|
* Creates a stateful mock discussion client that retains records across
|
|
211
|
-
* save/load cycles
|
|
211
|
+
* save/load cycles. The client coerces proto int64 fields back to numbers.
|
|
212
212
|
* @returns {object} Stateful mock discussion client
|
|
213
213
|
*/
|
|
214
214
|
export function createStatefulDiscussionClient() {
|
|
@@ -241,8 +241,8 @@ export function createStatefulDiscussionClient() {
|
|
|
241
241
|
const obj = req?.toJSON?.() ?? req;
|
|
242
242
|
const tenant_id = requireTenant(obj);
|
|
243
243
|
for (const rec of records.values()) {
|
|
244
|
-
// Filter to the
|
|
245
|
-
// correlation owned by tenant A
|
|
244
|
+
// Filter to the tenant that made the request, after the correlation
|
|
245
|
+
// scan. A correlation owned by tenant A stays invisible to tenant B.
|
|
246
246
|
if (rec.tenant_id !== tenant_id) continue;
|
|
247
247
|
if (
|
|
248
248
|
Object.values(rec.pending_callbacks ?? {}).includes(
|
|
@@ -274,8 +274,8 @@ export function createStatefulDiscussionClient() {
|
|
|
274
274
|
RecordOrigin: spy(async (req) => {
|
|
275
275
|
const obj = req?.toJSON?.() ?? req;
|
|
276
276
|
const tenant_id = requireTenant(obj);
|
|
277
|
-
// Tenant-scope the origin key so
|
|
278
|
-
//
|
|
277
|
+
// Tenant-scope the origin key so tenant B does not see a comment id
|
|
278
|
+
// recorded by tenant A as self-originated.
|
|
279
279
|
origins.set(`${tenant_id}:${obj.id}`, obj);
|
|
280
280
|
return {};
|
|
281
281
|
}),
|
package/src/mock/clock.js
CHANGED
|
@@ -4,21 +4,22 @@ import { spy } from "./spy.js";
|
|
|
4
4
|
* Creates a mock clock with controllable time and a no-wait sleep.
|
|
5
5
|
*
|
|
6
6
|
* `now()` returns the current virtual time in ms. `sleep(ms)` advances
|
|
7
|
-
* virtual time by `ms` and resolves on the next microtask
|
|
8
|
-
* timers
|
|
9
|
-
*
|
|
7
|
+
* virtual time by `ms` and resolves on the next microtask. It schedules no
|
|
8
|
+
* real timers. `advance(ms)` lets a test move time forward without a call
|
|
9
|
+
* to `sleep` (e.g. to expire a token).
|
|
10
10
|
*
|
|
11
11
|
* Pass `{ sleep, now }` from a returned clock to any constructor that
|
|
12
12
|
* accepts those collaborators to make its tests deterministic.
|
|
13
13
|
*
|
|
14
14
|
* `setTimeout(fn, ms)` / `clearTimeout(handle)` / `setInterval(fn, ms)` /
|
|
15
|
-
* `clearInterval(handle)` delegate to the host's real timers
|
|
16
|
-
* `createDefaultClock
|
|
17
|
-
* injected clock keeps its real-timer test behaviour. Virtual `now()`
|
|
18
|
-
* real timers
|
|
19
|
-
* waits on real time as it did before migration
|
|
20
|
-
* typically `.unref()`'d and never fires during a unit test
|
|
21
|
-
* its eviction path through an explicit `now` argument
|
|
15
|
+
* `clearInterval(handle)` delegate to the host's real timers, the same as
|
|
16
|
+
* `createDefaultClock`. So a migrated module that schedules work through
|
|
17
|
+
* the injected clock keeps its real-timer test behaviour. Virtual `now()`
|
|
18
|
+
* and the real timers stay independent by design. A test that needs a fired
|
|
19
|
+
* timer waits on real time, as it did before migration. A periodic sweep
|
|
20
|
+
* timer is typically `.unref()`'d and never fires during a unit test. The
|
|
21
|
+
* unit test exercises its eviction path through an explicit `now` argument
|
|
22
|
+
* instead.
|
|
22
23
|
*
|
|
23
24
|
* @param {object} [options]
|
|
24
25
|
* @param {number} [options.start=0] - Initial virtual time in ms.
|
package/src/mock/data.js
CHANGED
package/src/mock/environments.js
CHANGED
|
@@ -2,11 +2,12 @@ import { createMockStorage } from "./storage.js";
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Graph-index test triple: a mock storage, an n3 Store, and a GraphIndex wired
|
|
5
|
-
* to both. GraphIndex and Store
|
|
5
|
+
* to both. The caller injects GraphIndex and Store so libmock stays
|
|
6
|
+
* dependency-free.
|
|
6
7
|
* @param {object} opts
|
|
7
8
|
* @param {Function} opts.GraphIndex - libgraph GraphIndex constructor.
|
|
8
9
|
* @param {Function} opts.Store - n3 Store constructor.
|
|
9
|
-
* @param {object} [opts.storageOverrides] -
|
|
10
|
+
* @param {object} [opts.storageOverrides] - overrides for createMockStorage.
|
|
10
11
|
* @param {*} [opts.prefixes] - prefixes arg for GraphIndex (default {}).
|
|
11
12
|
* @param {string} [opts.indexKey] - jsonl key (default "test-graph.jsonl").
|
|
12
13
|
* @returns {{ n3Store: object, graphIndex: object, mockStorage: object }}
|
|
@@ -25,9 +26,10 @@ export function createGraphIndexFixture({
|
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
|
-
* The stripped gRPC health service
|
|
29
|
-
* `{ Check: { path, requestStream, responseStream } }`
|
|
30
|
-
* real `healthDefinition
|
|
29
|
+
* The stripped definition of the gRPC health service that consumers' tests
|
|
30
|
+
* fake. It has the `{ Check: { path, requestStream, responseStream } }`
|
|
31
|
+
* shape. It is not librpc's real `healthDefinition`, which librpc's own
|
|
32
|
+
* tests exercise directly.
|
|
31
33
|
* @returns {{ Check: { path: string, requestStream: boolean, responseStream: boolean } }}
|
|
32
34
|
*/
|
|
33
35
|
export function createMockGrpcHealthDefinition() {
|
|
@@ -42,7 +44,7 @@ export function createMockGrpcHealthDefinition() {
|
|
|
42
44
|
|
|
43
45
|
/**
|
|
44
46
|
* The readline/process/os/formatter/storage bundle librepl's tests inject.
|
|
45
|
-
*
|
|
47
|
+
* It mirrors libraries/librepl/test/librepl.test.js's pre-collapse beforeEach.
|
|
46
48
|
* @returns {{ readline: object, process: object, os: object, formatter: Function, storage: object }}
|
|
47
49
|
*/
|
|
48
50
|
export function createReplEnvironment() {
|
package/src/mock/finder.js
CHANGED
|
@@ -2,11 +2,11 @@ import path from "node:path";
|
|
|
2
2
|
import { spy } from "./spy.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Creates a mock `Finder` collaborator over an in-memory `files` map.
|
|
6
|
-
* the real `Finder` surface (`findUpward`, `findData`,
|
|
7
|
-
* `
|
|
8
|
-
* `createPackageSymlinks`)
|
|
9
|
-
*
|
|
5
|
+
* Creates a mock `Finder` collaborator over an in-memory `files` map. It
|
|
6
|
+
* mimics the real `Finder` surface (`findUpward`, `findData`,
|
|
7
|
+
* `findProjectRoot`, `findPackagePath`, `findGeneratedPath`,
|
|
8
|
+
* `createSymlink`, `createPackageSymlinks`). It does not touch the real
|
|
9
|
+
* filesystem. The mock records every call on `calls`.
|
|
10
10
|
*
|
|
11
11
|
* @param {object} [options]
|
|
12
12
|
* @param {Object<string, true|string>} [options.files] - Existing paths.
|
package/src/mock/fs.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Readable, Writable } from "node:stream";
|
|
2
2
|
import { spy } from "./spy.js";
|
|
3
3
|
/**
|
|
4
|
-
* Creates a mock filesystem
|
|
4
|
+
* Creates a mock filesystem over an in-memory Map
|
|
5
5
|
* @param {Object<string, string>} files - Initial file contents keyed by path
|
|
6
6
|
* @returns {object} Mock fs with readFile, writeFile, readdir, stat, mkdir, access, copyFile
|
|
7
7
|
*/
|
|
@@ -55,8 +55,8 @@ export function createMockFs(files = {}) {
|
|
|
55
55
|
data.delete(src);
|
|
56
56
|
}),
|
|
57
57
|
readdir: spy(async (path, opts = {}) => {
|
|
58
|
-
// Collect immediate children
|
|
59
|
-
// below it (a deeper key) or
|
|
58
|
+
// Collect immediate children. An entry is a directory if anything lives
|
|
59
|
+
// below it (a deeper key), or if mkdir/cp registered it.
|
|
60
60
|
const prefix = path.endsWith("/") ? path : `${path}/`;
|
|
61
61
|
const isDir = new Map();
|
|
62
62
|
for (const key of [...data.keys(), ...dirs]) {
|
|
@@ -95,7 +95,7 @@ export function createMockFs(files = {}) {
|
|
|
95
95
|
}),
|
|
96
96
|
mkdtemp: spy(async (prefix) => {
|
|
97
97
|
// Mirror node:fs/promises.mkdtemp: append 6 random chars to the prefix
|
|
98
|
-
// and register the directory.
|
|
98
|
+
// and register the directory. Then return the created path.
|
|
99
99
|
const path = `${prefix}${Math.random().toString(36).slice(2, 8)}`;
|
|
100
100
|
dirs.add(path);
|
|
101
101
|
return path;
|
|
@@ -173,8 +173,8 @@ export function createMockFs(files = {}) {
|
|
|
173
173
|
}
|
|
174
174
|
for (const dir of [...dirs].filter(under)) dirs.add(reroot(dir));
|
|
175
175
|
}),
|
|
176
|
-
//
|
|
177
|
-
// record the call (
|
|
176
|
+
// The in-memory store does not model timestamps or permissions. These
|
|
177
|
+
// record the call (with spy) so consumers stay testable and assertable.
|
|
178
178
|
utimes: spy(async () => {}),
|
|
179
179
|
chmod: spy(async () => {}),
|
|
180
180
|
existsSync: spy((path) => data.has(path) || dirs.has(path)),
|
|
@@ -230,12 +230,12 @@ export function createMockFs(files = {}) {
|
|
|
230
230
|
}
|
|
231
231
|
return [...names];
|
|
232
232
|
}),
|
|
233
|
-
//
|
|
233
|
+
// The in-memory store does not model permissions. Record the call.
|
|
234
234
|
chmodSync: spy(() => {}),
|
|
235
235
|
openSync: spy((path, flags = "r") => {
|
|
236
|
-
// For write/append flags, create/truncate
|
|
236
|
+
// For write/append flags, create/truncate. For read flags, require the
|
|
237
237
|
// file to exist (mirror node:fs.openSync ENOENT). Hand back a synthetic
|
|
238
|
-
// descriptor that records
|
|
238
|
+
// descriptor that records the path it opens.
|
|
239
239
|
const reading = typeof flags === "string" && flags.startsWith("r");
|
|
240
240
|
if (reading && !data.has(path)) {
|
|
241
241
|
const err = new Error(
|
|
@@ -251,7 +251,7 @@ export function createMockFs(files = {}) {
|
|
|
251
251
|
}),
|
|
252
252
|
readSync: spy((fd, buffer, offset = 0, length, position = 0) => {
|
|
253
253
|
// Mirror fs.readSync(fd, buffer, offset, length, position): copy bytes
|
|
254
|
-
// from the file's stored content into `buffer
|
|
254
|
+
// from the file's stored content into `buffer`. Return the byte count.
|
|
255
255
|
const path = openFds.get(fd);
|
|
256
256
|
if (path === undefined) {
|
|
257
257
|
const err = new Error("EBADF: bad file descriptor, read");
|
package/src/mock/gh-client.js
CHANGED
|
@@ -9,9 +9,9 @@ const GH_METHODS = [
|
|
|
9
9
|
];
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Creates a mock `GhClient` collaborator. Each method is a spy
|
|
13
|
-
* configured `responses[method]` value (or a no-op default).
|
|
14
|
-
*
|
|
12
|
+
* Creates a mock `GhClient` collaborator. Each method is a spy that returns
|
|
13
|
+
* the configured `responses[method]` value (or a no-op default). The mock
|
|
14
|
+
* records every call on `calls`.
|
|
15
15
|
*
|
|
16
16
|
* @param {object} [options]
|
|
17
17
|
* @param {Record<string, unknown>} [options.responses] - Per-method returns.
|
package/src/mock/git-client.js
CHANGED
|
@@ -40,10 +40,10 @@ const GIT_METHODS = [
|
|
|
40
40
|
];
|
|
41
41
|
|
|
42
42
|
// A response descriptor models one git failure: an `Error` thrown as-is, or
|
|
43
|
-
// `{ throw: <message>, stderr?: <text> }` thrown as an Error
|
|
44
|
-
// stderr
|
|
45
|
-
// GitError exposes `.stderr`)
|
|
46
|
-
// push rejection from an auth failure) sees a faithful shape.
|
|
43
|
+
// `{ throw: <message>, stderr?: <text> }` thrown as an Error that carries
|
|
44
|
+
// that stderr. This mirrors how GitClient's `#runRaw` surfaces a failure
|
|
45
|
+
// (the real GitError exposes `.stderr`). So a caller that inspects stderr
|
|
46
|
+
// (e.g. to tell a push rejection from an auth failure) sees a faithful shape.
|
|
47
47
|
function isResponseDescriptor(value) {
|
|
48
48
|
return (
|
|
49
49
|
value instanceof Error ||
|
|
@@ -52,10 +52,11 @@ function isResponseDescriptor(value) {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
// A configured response that is an array of response descriptors is a per-call
|
|
55
|
-
// sequence
|
|
56
|
-
//
|
|
57
|
-
// 2".
|
|
58
|
-
//
|
|
55
|
+
// sequence. The mock takes one entry per invocation. It reuses the last entry
|
|
56
|
+
// after the sequence runs out. So a test can express "push rejected on call 1,
|
|
57
|
+
// succeeds on call 2". The mock returns the whole array when it holds plain
|
|
58
|
+
// data (e.g. a `logByAuthor` commit list). The descriptor check keeps data
|
|
59
|
+
// returns and failure sequences apart.
|
|
59
60
|
function makeResponder(configured) {
|
|
60
61
|
const isSequence =
|
|
61
62
|
Array.isArray(configured) && configured.some(isResponseDescriptor);
|
|
@@ -75,7 +76,7 @@ function resolveResponse(responder) {
|
|
|
75
76
|
return value;
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
// Per-method default returns when no `responses[method]
|
|
79
|
+
// Per-method default returns when the caller configures no `responses[method]`.
|
|
79
80
|
// Methods absent here default to a no-op success `{ stdout, stderr, exitCode }`.
|
|
80
81
|
const GIT_DEFAULTS = {
|
|
81
82
|
revListCount: 0,
|
|
@@ -90,9 +91,10 @@ const GIT_DEFAULTS = {
|
|
|
90
91
|
|
|
91
92
|
/**
|
|
92
93
|
* Creates a mock `GitClient` collaborator. Every method on the real
|
|
93
|
-
* `GitClient` surface is a spy
|
|
94
|
-
* configured `responses[method]` value. `withAuth(token)` returns a
|
|
95
|
-
*
|
|
94
|
+
* `GitClient` surface is a spy that returns a no-op success by default, or
|
|
95
|
+
* the configured `responses[method]` value. `withAuth(token)` returns a
|
|
96
|
+
* client that shares the same `calls` log. The mock records every call on
|
|
97
|
+
* `calls`.
|
|
96
98
|
*
|
|
97
99
|
* @param {object} [options]
|
|
98
100
|
* @param {Record<string, unknown>} [options.responses] - Per-method returns.
|
package/src/mock/grpc.js
CHANGED
|
@@ -77,7 +77,7 @@ export class MockMetadata {
|
|
|
77
77
|
/**
|
|
78
78
|
* Gets metadata value by key
|
|
79
79
|
* @param {string} key - The metadata key
|
|
80
|
-
* @returns {string[]} Array
|
|
80
|
+
* @returns {string[]} Array with the value, or empty array if not found
|
|
81
81
|
*/
|
|
82
82
|
get(key) {
|
|
83
83
|
const value = this.data.get(key);
|
package/src/mock/http.js
CHANGED
package/src/mock/index.js
CHANGED
package/src/mock/infra.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Additional infrastructure mocks for services/products tests.
|
|
3
|
-
* variants that consumers previously inlined.
|
|
2
|
+
* Additional infrastructure mocks for services/products tests. This module
|
|
3
|
+
* centralizes variants that consumers previously inlined.
|
|
4
4
|
*/
|
|
5
5
|
import { Writable } from "node:stream";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* A
|
|
9
|
-
* the `chunks` accessor
|
|
10
|
-
*
|
|
8
|
+
* A `Writable` that captures output. The mock uses it as
|
|
9
|
+
* `proc.stdout`/`stderr`. It retains the `chunks` accessor that current
|
|
10
|
+
* assertions read. It is also a real `Writable`, so it accepts piped input
|
|
11
|
+
* (e.g. a `pipeline()` from a read stream).
|
|
11
12
|
*/
|
|
12
13
|
class CaptureWritable extends Writable {
|
|
13
14
|
constructor() {
|
|
@@ -28,15 +29,15 @@ class CaptureWritable extends Writable {
|
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Creates a mock Supabase-style client with configurable table and storage
|
|
31
|
-
* behaviour.
|
|
32
|
+
* behaviour. It covers the patterns across products/map/test/activity/*.
|
|
32
33
|
*
|
|
33
34
|
* @param {object} [options]
|
|
34
35
|
* @param {Record<string, object>} [options.tables] - Map of table name to
|
|
35
36
|
* override. Each override may expose `select`, `insert`, `upsert`, `delete`
|
|
36
37
|
* as async functions. Unspecified methods return `{ data: [], error: null }`.
|
|
37
|
-
* @param {Record<string, string>} [options.files] - Files exposed
|
|
38
|
+
* @param {Record<string, string>} [options.files] - Files exposed through
|
|
38
39
|
* `storage.from(...).list(prefix)` / `.download(path)`.
|
|
39
|
-
* @returns {object} Mock client and
|
|
40
|
+
* @returns {object} Mock client and arrays that track calls.
|
|
40
41
|
*/
|
|
41
42
|
export function createMockSupabaseClient({ tables = {}, files = {} } = {}) {
|
|
42
43
|
const calls = {
|
|
@@ -115,8 +116,8 @@ export function createMockSupabaseClient({ tables = {}, files = {} } = {}) {
|
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
/**
|
|
118
|
-
* Creates
|
|
119
|
-
* libmock free of an n3 dependency
|
|
119
|
+
* Creates helpers that parse Turtle with an injected n3 Parser. This keeps
|
|
120
|
+
* libmock free of an n3 dependency. Services can still share the
|
|
120
121
|
* parseQuads / findAll / findOne idiom.
|
|
121
122
|
*
|
|
122
123
|
* @param {import("n3").Parser | Function} ParserOrInstance - n3 Parser class
|
|
@@ -156,8 +157,8 @@ export function createTurtleHelpers(
|
|
|
156
157
|
}
|
|
157
158
|
|
|
158
159
|
/**
|
|
159
|
-
* Build an `AsyncIterable<string>` over a fixed list of input chunks
|
|
160
|
-
*
|
|
160
|
+
* Build an `AsyncIterable<string>` over a fixed list of input chunks. The
|
|
161
|
+
* mock uses it as `proc.stdin`.
|
|
161
162
|
* @param {string[]} chunks - Lines/chunks the iterator yields in order.
|
|
162
163
|
* @returns {AsyncIterable<string>}
|
|
163
164
|
*/
|
|
@@ -170,13 +171,14 @@ export function createMockStdin(chunks = []) {
|
|
|
170
171
|
}
|
|
171
172
|
|
|
172
173
|
/**
|
|
173
|
-
* Creates a mock `process`-like object
|
|
174
|
-
* `cwd()`, `env`, `argv`, `stdin`, `stdout`/`stderr` (
|
|
175
|
-
* `exit(code)`, `kill(pid, signal)`, `pid`,
|
|
176
|
-
* and a settable `exitCode`.
|
|
177
|
-
* `
|
|
178
|
-
*
|
|
179
|
-
*
|
|
174
|
+
* Creates a mock `process`-like object that matches the `Runtime.proc`
|
|
175
|
+
* surface: `cwd()`, `env`, `argv`, `stdin`, `stdout`/`stderr` (`Writable`s
|
|
176
|
+
* that capture output), `exit(code)`, `kill(pid, signal)`, `pid`,
|
|
177
|
+
* `platform`, `on(event, handler)`, and a settable `exitCode`. The mock
|
|
178
|
+
* captures writes on `stdout.chunks` / `stderr.chunks`, and the streams
|
|
179
|
+
* accept piped input. It records kill calls on `kills`. It records event
|
|
180
|
+
* handlers on `handlers`. Fire them with `emit(event, ...args)` to simulate
|
|
181
|
+
* a signal.
|
|
180
182
|
*
|
|
181
183
|
* @param {object} [options]
|
|
182
184
|
* @param {Record<string, string>} [options.env] - Initial env map.
|
|
@@ -184,11 +186,12 @@ export function createMockStdin(chunks = []) {
|
|
|
184
186
|
* @param {string[]} [options.argv] - The frozen `argv` array.
|
|
185
187
|
* @param {string[]} [options.stdin] - Chunks the `stdin` iterator yields.
|
|
186
188
|
* @param {(pid: number, signal: string|number) => any} [options.kill] - Optional
|
|
187
|
-
* `kill` implementation (e.g. to model a liveness probe)
|
|
188
|
-
*
|
|
189
|
+
* `kill` implementation (e.g. to model a liveness probe). The mock always
|
|
190
|
+
* records calls on the returned `kills` array regardless.
|
|
189
191
|
* @param {number} [options.pid] - The fake's `pid` (default 1234).
|
|
190
192
|
* @param {string} [options.platform] - The fake's `platform` string
|
|
191
|
-
* (default `"linux"
|
|
193
|
+
* (default `"linux"`). Set `"darwin"`/`"win32"` to exercise per-platform
|
|
194
|
+
* code.
|
|
192
195
|
* @returns {object}
|
|
193
196
|
*/
|
|
194
197
|
export function createMockProcess({
|
|
@@ -203,8 +206,8 @@ export function createMockProcess({
|
|
|
203
206
|
const stdout = new CaptureWritable();
|
|
204
207
|
const stderr = new CaptureWritable();
|
|
205
208
|
const kills = [];
|
|
206
|
-
// Registered event handlers (e.g. "SIGTERM"/"SIGINT")
|
|
207
|
-
//
|
|
209
|
+
// Registered event handlers (e.g. "SIGTERM"/"SIGINT"). A test can fire them
|
|
210
|
+
// with `emit(event, ...args)` to simulate a signal without a real process.
|
|
208
211
|
const handlers = {};
|
|
209
212
|
return {
|
|
210
213
|
env: { ...env },
|
|
@@ -235,8 +238,8 @@ export function createMockProcess({
|
|
|
235
238
|
}
|
|
236
239
|
|
|
237
240
|
/**
|
|
238
|
-
* Runs `fn`
|
|
239
|
-
*
|
|
241
|
+
* Runs `fn` and suppresses `console.log`, `console.info`, and `console.warn`.
|
|
242
|
+
* It returns whatever `fn` returns. Errors still propagate.
|
|
240
243
|
*
|
|
241
244
|
* @template T
|
|
242
245
|
* @param {() => T | Promise<T>} fn
|
|
@@ -262,8 +265,9 @@ export async function withSilentConsole(fn) {
|
|
|
262
265
|
|
|
263
266
|
/**
|
|
264
267
|
* Creates a bag of async query stubs from a plain values object. A function
|
|
265
|
-
* value
|
|
266
|
-
*
|
|
268
|
+
* value passes through untouched. Anything else becomes an async function
|
|
269
|
+
* that returns that value. This collapses landmark-style `stubQueries`
|
|
270
|
+
* boilerplate.
|
|
267
271
|
*
|
|
268
272
|
* @param {Record<string, unknown>} values
|
|
269
273
|
* @returns {Record<string, Function>}
|
package/src/mock/logger.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spy } from "./spy.js";
|
|
2
2
|
/**
|
|
3
|
-
* Creates a mock logger
|
|
3
|
+
* Creates a mock logger that tracks calls
|
|
4
4
|
* @param {object} options - Logger options
|
|
5
5
|
* @param {boolean} options.captureOutput - Whether to capture log output
|
|
6
6
|
* @returns {object} Mock logger
|
|
@@ -72,9 +72,9 @@ export function createMockResourceIndex(options = {}) {
|
|
|
72
72
|
},
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
*
|
|
75
|
+
* Finds resources by prefix
|
|
76
76
|
* @param {string} prefix - Prefix to search for
|
|
77
|
-
* @returns {Promise<string[]>} List of
|
|
77
|
+
* @returns {Promise<string[]>} List of keys that match
|
|
78
78
|
*/
|
|
79
79
|
async findByPrefix(prefix) {
|
|
80
80
|
const keys = [];
|
package/src/mock/services.js
CHANGED
package/src/mock/spy.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Portable mock function helper. Replaces `mock.fn` from `node:test` so the
|
|
3
3
|
* test suite can run under either node:test or bun:test.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* The shape matches node:test's `mock.fn` to keep call-inspection sites
|
|
6
6
|
* (`fn.mock.calls[0].arguments`, `fn.mock.callCount()`, `fn.mock.resetCalls()`)
|
|
7
7
|
* unchanged across the codebase.
|
|
8
8
|
*
|
package/src/mock/storage.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { spy } from "./spy.js";
|
|
2
2
|
/**
|
|
3
|
-
* Creates a mock storage instance
|
|
3
|
+
* Creates a mock storage instance that tracks data
|
|
4
4
|
* @param {object} overrides - Method overrides
|
|
5
|
-
* @returns {object} Mock storage
|
|
5
|
+
* @returns {object} Mock storage that tracks data
|
|
6
6
|
*/
|
|
7
7
|
export function createMockStorage(overrides = {}) {
|
|
8
8
|
const data = new Map();
|
|
@@ -78,7 +78,7 @@ export class MockStorage {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
|
-
* Alias for put
|
|
81
|
+
* Alias for put. Stores a value by key
|
|
82
82
|
* @param {string} key - The storage key
|
|
83
83
|
* @param {*} value - The value to store
|
|
84
84
|
* @returns {Promise<void>}
|
|
@@ -101,7 +101,7 @@ export class MockStorage {
|
|
|
101
101
|
/**
|
|
102
102
|
* Checks if a key exists
|
|
103
103
|
* @param {string} key - The storage key
|
|
104
|
-
* @returns {Promise<boolean>} True if key exists
|
|
104
|
+
* @returns {Promise<boolean>} True if the key exists
|
|
105
105
|
*/
|
|
106
106
|
async exists(key) {
|
|
107
107
|
return this.data.has(key);
|
package/src/mock/subprocess.js
CHANGED
|
@@ -11,11 +11,12 @@ function asyncIterableOf(str) {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* A captured-chunks sink for a spawned child's writable stdin.
|
|
15
|
-
* `node:stream` Writable
|
|
16
|
-
* supervisor pipes a service's output into its logger
|
|
17
|
-
* written chunk on `chunks
|
|
18
|
-
* `stdin` the production
|
|
14
|
+
* A captured-chunks sink for a spawned child's writable stdin. The sink is a
|
|
15
|
+
* real `node:stream` Writable, so it is a valid `pipe()` destination. A
|
|
16
|
+
* supervisor pipes a service's output into its logger in the same way. The
|
|
17
|
+
* sink records every written chunk on `chunks`. It does not forward the chunk
|
|
18
|
+
* anywhere. It mirrors the `stdin` that the production
|
|
19
|
+
* `createDefaultSubprocess().spawn` exposes.
|
|
19
20
|
*/
|
|
20
21
|
function createMockStdinSink() {
|
|
21
22
|
const chunks = [];
|
|
@@ -30,14 +31,14 @@ function createMockStdinSink() {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
|
-
* Creates a mock subprocess collaborator
|
|
34
|
-
* surface. `run(cmd, args, opts)`
|
|
35
|
-
*
|
|
36
|
-
* synchronous sibling
|
|
37
|
-
* quad backed by the same responses
|
|
34
|
+
* Creates a mock subprocess collaborator that matches the `Runtime.subprocess`
|
|
35
|
+
* surface. `run(cmd, args, opts)` consults `responses[cmd]` (default: empty
|
|
36
|
+
* success) and resolves to `{ stdout, stderr, exitCode }`. `runSync` is its
|
|
37
|
+
* synchronous sibling and returns the same shape. `spawn` returns a streaming
|
|
38
|
+
* quad backed by the same responses. Its result carries `stdout`/`stderr`
|
|
38
39
|
* AsyncIterables, a captured-chunks `stdin` sink, `exitCode`/`signal` Promises,
|
|
39
|
-
* a `kill(signal)` spy
|
|
40
|
-
*
|
|
40
|
+
* a `kill(signal)` spy that records on `kills`, and `pid`. The mock records
|
|
41
|
+
* every invocation on `calls`.
|
|
41
42
|
*
|
|
42
43
|
* @param {object} [options]
|
|
43
44
|
* @param {Record<string, {stdout?: string, stderr?: string, exitCode?: number}>} [options.responses]
|
|
@@ -70,11 +71,13 @@ export function createMockSubprocess({ responses = {} } = {}) {
|
|
|
70
71
|
return {
|
|
71
72
|
stdout: asyncIterableOf(r.stdout),
|
|
72
73
|
stderr: asyncIterableOf(r.stderr),
|
|
73
|
-
// A captured-chunks writable
|
|
74
|
-
// sets `stdin: null
|
|
74
|
+
// A captured-chunks writable. It is `null` only when a response
|
|
75
|
+
// explicitly sets `stdin: null`. That matches a child spawned without a
|
|
76
|
+
// stdin pipe.
|
|
75
77
|
stdin: r.stdin === null ? null : createMockStdinSink(),
|
|
76
78
|
exitCode: Promise.resolve(r.exitCode),
|
|
77
|
-
//
|
|
79
|
+
// The signal that stopped the child is `null` (clean exit) unless a
|
|
80
|
+
// response overrides it.
|
|
78
81
|
signal: Promise.resolve(r.signal ?? null),
|
|
79
82
|
kills,
|
|
80
83
|
kill: spy((signal) => {
|
package/src/runtime.js
CHANGED
|
@@ -5,9 +5,9 @@ import { createMockSubprocess } from "./mock/subprocess.js";
|
|
|
5
5
|
import { createMockFinder } from "./mock/finder.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Build a frozen mock runtime bag for tests
|
|
9
|
-
* `Runtime` typedef. Every field defaults to its canonical
|
|
10
|
-
*
|
|
8
|
+
* Build a frozen mock runtime bag for tests. The bag matches the production
|
|
9
|
+
* `Runtime` typedef. Every field defaults to its canonical libmock fake.
|
|
10
|
+
* Override each field independently through `overrides`.
|
|
11
11
|
*
|
|
12
12
|
* @param {object} [overrides] - Per-field replacements.
|
|
13
13
|
* @param {object} [overrides.fs] - Async fs surface (default `createMockFs()`).
|