@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
package/src/mock/fs.js
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { Readable, Writable } from "node:stream";
|
|
2
|
+
import { spy } from "./spy.js";
|
|
3
|
+
/**
|
|
4
|
+
* Creates a mock filesystem backed by an in-memory Map
|
|
5
|
+
* @param {Object<string, string>} files - Initial file contents keyed by path
|
|
6
|
+
* @returns {object} Mock fs with readFile, writeFile, readdir, stat, mkdir, access, copyFile
|
|
7
|
+
*/
|
|
8
|
+
export function createMockFs(files = {}) {
|
|
9
|
+
const data = new Map(Object.entries(files));
|
|
10
|
+
const dirs = new Set();
|
|
11
|
+
let nextFd = 3;
|
|
12
|
+
const openFds = new Map();
|
|
13
|
+
|
|
14
|
+
// Auto-create parent directories for initial files
|
|
15
|
+
for (const path of data.keys()) {
|
|
16
|
+
const parts = path.split("/");
|
|
17
|
+
for (let i = 1; i < parts.length; i++) {
|
|
18
|
+
dirs.add(parts.slice(0, i).join("/"));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
data,
|
|
24
|
+
dirs,
|
|
25
|
+
readFile: spy(async (path, encoding) => {
|
|
26
|
+
const content = data.get(path);
|
|
27
|
+
if (content === undefined) {
|
|
28
|
+
const err = new Error(
|
|
29
|
+
`ENOENT: no such file or directory, open '${path}'`,
|
|
30
|
+
);
|
|
31
|
+
err.code = "ENOENT";
|
|
32
|
+
throw err;
|
|
33
|
+
}
|
|
34
|
+
return encoding ? content : Buffer.from(content);
|
|
35
|
+
}),
|
|
36
|
+
writeFile: spy(async (path, content) => {
|
|
37
|
+
data.set(
|
|
38
|
+
path,
|
|
39
|
+
typeof content === "string" ? content : content.toString(),
|
|
40
|
+
);
|
|
41
|
+
}),
|
|
42
|
+
appendFile: spy(async (path, content) => {
|
|
43
|
+
const chunk = typeof content === "string" ? content : content.toString();
|
|
44
|
+
data.set(path, (data.get(path) ?? "") + chunk);
|
|
45
|
+
}),
|
|
46
|
+
rename: spy(async (src, dest) => {
|
|
47
|
+
if (!data.has(src)) {
|
|
48
|
+
const err = new Error(
|
|
49
|
+
`ENOENT: no such file or directory, rename '${src}' -> '${dest}'`,
|
|
50
|
+
);
|
|
51
|
+
err.code = "ENOENT";
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
data.set(dest, data.get(src));
|
|
55
|
+
data.delete(src);
|
|
56
|
+
}),
|
|
57
|
+
readdir: spy(async (path, opts = {}) => {
|
|
58
|
+
// Collect immediate children; an entry is a directory if anything lives
|
|
59
|
+
// below it (a deeper key) or it was registered via mkdir/cp.
|
|
60
|
+
const prefix = path.endsWith("/") ? path : `${path}/`;
|
|
61
|
+
const isDir = new Map();
|
|
62
|
+
for (const key of [...data.keys(), ...dirs]) {
|
|
63
|
+
if (!key.startsWith(prefix)) continue;
|
|
64
|
+
const rest = key.slice(prefix.length);
|
|
65
|
+
const name = rest.split("/")[0];
|
|
66
|
+
if (!name) continue;
|
|
67
|
+
const dir = rest.includes("/") || dirs.has(`${prefix}${name}`);
|
|
68
|
+
isDir.set(name, isDir.get(name) || dir);
|
|
69
|
+
}
|
|
70
|
+
const names = [...isDir.keys()];
|
|
71
|
+
if (!opts.withFileTypes) return names;
|
|
72
|
+
// Mirror node:fs Dirent: name + isDirectory()/isFile()/isSymbolicLink().
|
|
73
|
+
return names.map((name) => ({
|
|
74
|
+
name,
|
|
75
|
+
isDirectory: () => isDir.get(name),
|
|
76
|
+
isFile: () => !isDir.get(name),
|
|
77
|
+
isSymbolicLink: () => false,
|
|
78
|
+
}));
|
|
79
|
+
}),
|
|
80
|
+
stat: spy(async (path) => {
|
|
81
|
+
if (data.has(path)) {
|
|
82
|
+
return { isFile: () => true, isDirectory: () => false };
|
|
83
|
+
}
|
|
84
|
+
if (dirs.has(path)) {
|
|
85
|
+
return { isFile: () => false, isDirectory: () => true };
|
|
86
|
+
}
|
|
87
|
+
const err = new Error(
|
|
88
|
+
`ENOENT: no such file or directory, stat '${path}'`,
|
|
89
|
+
);
|
|
90
|
+
err.code = "ENOENT";
|
|
91
|
+
throw err;
|
|
92
|
+
}),
|
|
93
|
+
mkdir: spy(async (path) => {
|
|
94
|
+
dirs.add(path);
|
|
95
|
+
}),
|
|
96
|
+
mkdtemp: spy(async (prefix) => {
|
|
97
|
+
// Mirror node:fs/promises.mkdtemp: append 6 random chars to the prefix
|
|
98
|
+
// and register the directory. Returns the created path.
|
|
99
|
+
const path = `${prefix}${Math.random().toString(36).slice(2, 8)}`;
|
|
100
|
+
dirs.add(path);
|
|
101
|
+
return path;
|
|
102
|
+
}),
|
|
103
|
+
rm: spy(async (path, opts = {}) => {
|
|
104
|
+
// Mirror fs.rm(path, { recursive }): with `recursive`, drop the entry
|
|
105
|
+
// and every descendant so subsequent readdir/access don't see ghosts.
|
|
106
|
+
const prefix = path.endsWith("/") ? path : `${path}/`;
|
|
107
|
+
const matches = (k) =>
|
|
108
|
+
k === path || (opts.recursive && k.startsWith(prefix));
|
|
109
|
+
for (const k of [...data.keys()]) if (matches(k)) data.delete(k);
|
|
110
|
+
for (const k of [...dirs]) if (matches(k)) dirs.delete(k);
|
|
111
|
+
}),
|
|
112
|
+
lstat: spy(async (path) => {
|
|
113
|
+
if (data.has(path)) {
|
|
114
|
+
return {
|
|
115
|
+
isFile: () => true,
|
|
116
|
+
isDirectory: () => false,
|
|
117
|
+
isSymbolicLink: () => false,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (dirs.has(path)) {
|
|
121
|
+
return {
|
|
122
|
+
isFile: () => false,
|
|
123
|
+
isDirectory: () => true,
|
|
124
|
+
isSymbolicLink: () => false,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const err = new Error(
|
|
128
|
+
`ENOENT: no such file or directory, lstat '${path}'`,
|
|
129
|
+
);
|
|
130
|
+
err.code = "ENOENT";
|
|
131
|
+
throw err;
|
|
132
|
+
}),
|
|
133
|
+
unlink: spy(async (path) => {
|
|
134
|
+
data.delete(path);
|
|
135
|
+
}),
|
|
136
|
+
symlink: spy(async (_target, path) => {
|
|
137
|
+
dirs.add(path);
|
|
138
|
+
}),
|
|
139
|
+
access: spy(async (path) => {
|
|
140
|
+
if (!data.has(path) && !dirs.has(path)) {
|
|
141
|
+
const err = new Error(
|
|
142
|
+
`ENOENT: no such file or directory, access '${path}'`,
|
|
143
|
+
);
|
|
144
|
+
err.code = "ENOENT";
|
|
145
|
+
throw err;
|
|
146
|
+
}
|
|
147
|
+
}),
|
|
148
|
+
copyFile: spy(async (src, dest) => {
|
|
149
|
+
const content = data.get(src);
|
|
150
|
+
if (content === undefined) {
|
|
151
|
+
const err = new Error(
|
|
152
|
+
`ENOENT: no such file or directory, copyFile '${src}'`,
|
|
153
|
+
);
|
|
154
|
+
err.code = "ENOENT";
|
|
155
|
+
throw err;
|
|
156
|
+
}
|
|
157
|
+
data.set(dest, content);
|
|
158
|
+
}),
|
|
159
|
+
cp: spy(async (src, dest) => {
|
|
160
|
+
// Mirror fs.cp(src, dest, { recursive }): copy a file, or a directory
|
|
161
|
+
// subtree (every entry under `src/` re-rooted under `dest/`).
|
|
162
|
+
if (data.has(src)) {
|
|
163
|
+
data.set(dest, data.get(src));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const prefix = src.endsWith("/") ? src : `${src}/`;
|
|
167
|
+
const destPrefix = dest.endsWith("/") ? dest : `${dest}/`;
|
|
168
|
+
const reroot = (k) => destPrefix + k.slice(prefix.length);
|
|
169
|
+
const under = (k) => k.startsWith(prefix);
|
|
170
|
+
dirs.add(dest);
|
|
171
|
+
for (const key of [...data.keys()].filter(under)) {
|
|
172
|
+
data.set(reroot(key), data.get(key));
|
|
173
|
+
}
|
|
174
|
+
for (const dir of [...dirs].filter(under)) dirs.add(reroot(dir));
|
|
175
|
+
}),
|
|
176
|
+
// Timestamps and permissions are not modeled by the in-memory store; these
|
|
177
|
+
// record the call (via spy) so consumers stay testable and assertable.
|
|
178
|
+
utimes: spy(async () => {}),
|
|
179
|
+
chmod: spy(async () => {}),
|
|
180
|
+
existsSync: spy((path) => data.has(path) || dirs.has(path)),
|
|
181
|
+
readFileSync: spy((path, encoding) => {
|
|
182
|
+
const content = data.get(path);
|
|
183
|
+
if (content === undefined) {
|
|
184
|
+
const err = new Error(
|
|
185
|
+
`ENOENT: no such file or directory, open '${path}'`,
|
|
186
|
+
);
|
|
187
|
+
err.code = "ENOENT";
|
|
188
|
+
throw err;
|
|
189
|
+
}
|
|
190
|
+
return encoding ? content : Buffer.from(content);
|
|
191
|
+
}),
|
|
192
|
+
writeFileSync: spy((path, content) => {
|
|
193
|
+
data.set(
|
|
194
|
+
path,
|
|
195
|
+
typeof content === "string" ? content : content.toString(),
|
|
196
|
+
);
|
|
197
|
+
}),
|
|
198
|
+
mkdirSync: spy((path) => {
|
|
199
|
+
dirs.add(path);
|
|
200
|
+
}),
|
|
201
|
+
statSync: spy((path) => {
|
|
202
|
+
const content = data.get(path);
|
|
203
|
+
if (content !== undefined)
|
|
204
|
+
return {
|
|
205
|
+
size: Buffer.byteLength(content),
|
|
206
|
+
mtimeMs: 0,
|
|
207
|
+
isFile: () => true,
|
|
208
|
+
isDirectory: () => false,
|
|
209
|
+
};
|
|
210
|
+
if (dirs.has(path))
|
|
211
|
+
return {
|
|
212
|
+
size: 0,
|
|
213
|
+
mtimeMs: 0,
|
|
214
|
+
isFile: () => false,
|
|
215
|
+
isDirectory: () => true,
|
|
216
|
+
};
|
|
217
|
+
const err = new Error(
|
|
218
|
+
`ENOENT: no such file or directory, stat '${path}'`,
|
|
219
|
+
);
|
|
220
|
+
err.code = "ENOENT";
|
|
221
|
+
throw err;
|
|
222
|
+
}),
|
|
223
|
+
readdirSync: spy((dir) => {
|
|
224
|
+
const prefix = dir.endsWith("/") ? dir : `${dir}/`;
|
|
225
|
+
const names = new Set();
|
|
226
|
+
for (const key of [...data.keys(), ...dirs]) {
|
|
227
|
+
if (!key.startsWith(prefix)) continue;
|
|
228
|
+
const name = key.slice(prefix.length).split("/")[0];
|
|
229
|
+
if (name) names.add(name);
|
|
230
|
+
}
|
|
231
|
+
return [...names];
|
|
232
|
+
}),
|
|
233
|
+
// Permissions are not modeled by the in-memory store; record the call.
|
|
234
|
+
chmodSync: spy(() => {}),
|
|
235
|
+
openSync: spy((path, flags = "r") => {
|
|
236
|
+
// For write/append flags, create/truncate; for read flags, require the
|
|
237
|
+
// file to exist (mirror node:fs.openSync ENOENT). Hand back a synthetic
|
|
238
|
+
// descriptor that records its backing path.
|
|
239
|
+
const reading = typeof flags === "string" && flags.startsWith("r");
|
|
240
|
+
if (reading && !data.has(path)) {
|
|
241
|
+
const err = new Error(
|
|
242
|
+
`ENOENT: no such file or directory, open '${path}'`,
|
|
243
|
+
);
|
|
244
|
+
err.code = "ENOENT";
|
|
245
|
+
throw err;
|
|
246
|
+
}
|
|
247
|
+
if (!reading) data.set(path, "");
|
|
248
|
+
const fd = nextFd++;
|
|
249
|
+
openFds.set(fd, path);
|
|
250
|
+
return fd;
|
|
251
|
+
}),
|
|
252
|
+
readSync: spy((fd, buffer, offset = 0, length, position = 0) => {
|
|
253
|
+
// Mirror fs.readSync(fd, buffer, offset, length, position): copy bytes
|
|
254
|
+
// from the file's stored content into `buffer`, returning the byte count.
|
|
255
|
+
const path = openFds.get(fd);
|
|
256
|
+
if (path === undefined) {
|
|
257
|
+
const err = new Error("EBADF: bad file descriptor, read");
|
|
258
|
+
err.code = "EBADF";
|
|
259
|
+
throw err;
|
|
260
|
+
}
|
|
261
|
+
const content = Buffer.from(data.get(path) ?? "");
|
|
262
|
+
const start = position ?? 0;
|
|
263
|
+
const want = length ?? buffer.length - offset;
|
|
264
|
+
const slice = content.subarray(start, start + want);
|
|
265
|
+
slice.copy(buffer, offset);
|
|
266
|
+
return slice.length;
|
|
267
|
+
}),
|
|
268
|
+
closeSync: spy((fd) => {
|
|
269
|
+
openFds.delete(fd);
|
|
270
|
+
}),
|
|
271
|
+
unlinkSync: spy((path) => {
|
|
272
|
+
data.delete(path);
|
|
273
|
+
}),
|
|
274
|
+
createReadStream: spy((path) => {
|
|
275
|
+
// Stream the stored content (or error asynchronously, like node:fs).
|
|
276
|
+
const content = data.get(path);
|
|
277
|
+
if (content === undefined) {
|
|
278
|
+
const err = new Error(
|
|
279
|
+
`ENOENT: no such file or directory, open '${path}'`,
|
|
280
|
+
);
|
|
281
|
+
err.code = "ENOENT";
|
|
282
|
+
const stream = new Readable({ read() {} });
|
|
283
|
+
queueMicrotask(() => stream.emit("error", err));
|
|
284
|
+
return stream;
|
|
285
|
+
}
|
|
286
|
+
return Readable.from([Buffer.from(content)]);
|
|
287
|
+
}),
|
|
288
|
+
createWriteStream: spy((path, opts = {}) => {
|
|
289
|
+
// Accumulate writes into the in-memory store. `flags: "a"` appends.
|
|
290
|
+
const append = opts.flags === "a";
|
|
291
|
+
if (!append || !data.has(path)) data.set(path, "");
|
|
292
|
+
const stream = new Writable({
|
|
293
|
+
write(chunk, _enc, cb) {
|
|
294
|
+
data.set(path, (data.get(path) ?? "") + chunk.toString());
|
|
295
|
+
cb();
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
return stream;
|
|
299
|
+
}),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { spy } from "./spy.js";
|
|
2
|
+
|
|
3
|
+
const GH_METHODS = ["prCreate", "prMerge", "apiGet", "apiPost"];
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a mock `GhClient` collaborator. Each method is a spy returning the
|
|
7
|
+
* configured `responses[method]` value (or a no-op default). Invocations are
|
|
8
|
+
* recorded on `calls`.
|
|
9
|
+
*
|
|
10
|
+
* @param {object} [options]
|
|
11
|
+
* @param {Record<string, unknown>} [options.responses] - Per-method returns.
|
|
12
|
+
* @returns {object} The mock gh client.
|
|
13
|
+
*/
|
|
14
|
+
export function createMockGhClient({ responses = {} } = {}) {
|
|
15
|
+
const calls = [];
|
|
16
|
+
const client = { calls };
|
|
17
|
+
|
|
18
|
+
for (const method of GH_METHODS) {
|
|
19
|
+
client[method] = spy(async (...args) => {
|
|
20
|
+
calls.push({ method, args });
|
|
21
|
+
if (method in responses) return responses[method];
|
|
22
|
+
if (method === "prCreate") return "";
|
|
23
|
+
return null;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return client;
|
|
28
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { spy } from "./spy.js";
|
|
2
|
+
|
|
3
|
+
const GIT_METHODS = [
|
|
4
|
+
"clone",
|
|
5
|
+
"init",
|
|
6
|
+
"fetch",
|
|
7
|
+
"status",
|
|
8
|
+
"rebase",
|
|
9
|
+
"rebaseAbort",
|
|
10
|
+
"mergeOursStrategy",
|
|
11
|
+
"commitAll",
|
|
12
|
+
"push",
|
|
13
|
+
"revListCount",
|
|
14
|
+
"configGet",
|
|
15
|
+
"configSet",
|
|
16
|
+
"aheadCount",
|
|
17
|
+
"remoteGetUrl",
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Creates a mock `GitClient` collaborator. Every method on the real
|
|
22
|
+
* `GitClient` surface is a spy returning a no-op success by default, or the
|
|
23
|
+
* configured `responses[method]` value. `withAuth(token)` returns a client
|
|
24
|
+
* sharing the same `calls` log. Invocations are recorded on `calls`.
|
|
25
|
+
*
|
|
26
|
+
* @param {object} [options]
|
|
27
|
+
* @param {Record<string, unknown>} [options.responses] - Per-method returns.
|
|
28
|
+
* @returns {object} The mock git client.
|
|
29
|
+
*/
|
|
30
|
+
export function createMockGitClient({ responses = {} } = {}) {
|
|
31
|
+
const calls = [];
|
|
32
|
+
const client = { calls };
|
|
33
|
+
|
|
34
|
+
for (const method of GIT_METHODS) {
|
|
35
|
+
client[method] = spy(async (...args) => {
|
|
36
|
+
calls.push({ method, args });
|
|
37
|
+
if (method in responses) return responses[method];
|
|
38
|
+
if (method === "revListCount" || method === "aheadCount") return 0;
|
|
39
|
+
if (
|
|
40
|
+
method === "status" ||
|
|
41
|
+
method === "configGet" ||
|
|
42
|
+
method === "remoteGetUrl"
|
|
43
|
+
) {
|
|
44
|
+
return "";
|
|
45
|
+
}
|
|
46
|
+
return { stdout: "", stderr: "", exitCode: 0 };
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
client.withAuth = spy((token) => {
|
|
51
|
+
calls.push({ method: "withAuth", args: [token] });
|
|
52
|
+
return client;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return client;
|
|
56
|
+
}
|
package/src/mock/grpc.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { spy } from "./spy.js";
|
|
2
|
+
/**
|
|
3
|
+
* Creates a mock gRPC factory function
|
|
4
|
+
* @param {object} overrides - Method overrides
|
|
5
|
+
* @returns {Function} Mock gRPC factory
|
|
6
|
+
*/
|
|
7
|
+
export function createMockGrpcFn(overrides = {}) {
|
|
8
|
+
const mockGrpc = {
|
|
9
|
+
Server: function () {
|
|
10
|
+
return {
|
|
11
|
+
addService: spy(),
|
|
12
|
+
bindAsync: spy((uri, creds, callback) => callback(null, 5000)),
|
|
13
|
+
tryShutdown: spy((callback) => callback()),
|
|
14
|
+
...overrides.server,
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
loadPackageDefinition: spy(() => ({
|
|
18
|
+
test: { Test: { service: {} } },
|
|
19
|
+
})),
|
|
20
|
+
makeGenericClientConstructor: spy(() => function () {}),
|
|
21
|
+
ServerCredentials: {
|
|
22
|
+
createInsecure: spy(),
|
|
23
|
+
},
|
|
24
|
+
credentials: {
|
|
25
|
+
createInsecure: spy(),
|
|
26
|
+
},
|
|
27
|
+
status: {
|
|
28
|
+
OK: 0,
|
|
29
|
+
CANCELLED: 1,
|
|
30
|
+
UNKNOWN: 2,
|
|
31
|
+
INVALID_ARGUMENT: 3,
|
|
32
|
+
DEADLINE_EXCEEDED: 4,
|
|
33
|
+
NOT_FOUND: 5,
|
|
34
|
+
ALREADY_EXISTS: 6,
|
|
35
|
+
PERMISSION_DENIED: 7,
|
|
36
|
+
RESOURCE_EXHAUSTED: 8,
|
|
37
|
+
FAILED_PRECONDITION: 9,
|
|
38
|
+
ABORTED: 10,
|
|
39
|
+
OUT_OF_RANGE: 11,
|
|
40
|
+
UNIMPLEMENTED: 12,
|
|
41
|
+
INTERNAL: 13,
|
|
42
|
+
UNAVAILABLE: 14,
|
|
43
|
+
DATA_LOSS: 15,
|
|
44
|
+
UNAUTHENTICATED: 16,
|
|
45
|
+
},
|
|
46
|
+
...overrides.grpc,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const mockProtoLoader = {
|
|
50
|
+
loadSync: spy(() => ({})),
|
|
51
|
+
...overrides.protoLoader,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return () => ({ grpc: mockGrpc, protoLoader: mockProtoLoader });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Creates a mock gRPC Metadata class
|
|
59
|
+
*/
|
|
60
|
+
export class MockMetadata {
|
|
61
|
+
/**
|
|
62
|
+
* Creates a new MockMetadata instance
|
|
63
|
+
*/
|
|
64
|
+
constructor() {
|
|
65
|
+
this.data = new Map();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Sets a metadata key-value pair
|
|
70
|
+
* @param {string} key - The metadata key
|
|
71
|
+
* @param {string} value - The metadata value
|
|
72
|
+
*/
|
|
73
|
+
set(key, value) {
|
|
74
|
+
this.data.set(key, value);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Gets metadata value by key
|
|
79
|
+
* @param {string} key - The metadata key
|
|
80
|
+
* @returns {string[]} Array containing the value, or empty array if not found
|
|
81
|
+
*/
|
|
82
|
+
get(key) {
|
|
83
|
+
const value = this.data.get(key);
|
|
84
|
+
return value !== undefined ? [value] : [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Returns all metadata as a plain object
|
|
89
|
+
* @returns {object} Object with all metadata key-value pairs
|
|
90
|
+
*/
|
|
91
|
+
getMap() {
|
|
92
|
+
return Object.fromEntries(this.data);
|
|
93
|
+
}
|
|
94
|
+
}
|
package/src/mock/http.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a mock HTTP request with event emitter behavior
|
|
3
|
+
* @param {object} options - Request options
|
|
4
|
+
* @returns {object} Mock request
|
|
5
|
+
*/
|
|
6
|
+
export function createMockRequest(options = {}) {
|
|
7
|
+
const { method = "GET", url = "/", body = {}, headers = {} } = options;
|
|
8
|
+
|
|
9
|
+
const bodyStr = JSON.stringify(body);
|
|
10
|
+
let dataCallback;
|
|
11
|
+
let endCallback;
|
|
12
|
+
let errorCallback;
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
method,
|
|
16
|
+
url,
|
|
17
|
+
headers,
|
|
18
|
+
on(event, callback) {
|
|
19
|
+
if (event === "data") dataCallback = callback;
|
|
20
|
+
if (event === "end") endCallback = callback;
|
|
21
|
+
if (event === "error") errorCallback = callback;
|
|
22
|
+
},
|
|
23
|
+
simulateBody() {
|
|
24
|
+
if (dataCallback) dataCallback(bodyStr);
|
|
25
|
+
if (endCallback) endCallback();
|
|
26
|
+
},
|
|
27
|
+
simulateError(err) {
|
|
28
|
+
if (errorCallback) errorCallback(err);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Creates a mock HTTP response with tracking
|
|
35
|
+
* @returns {object} Mock response
|
|
36
|
+
*/
|
|
37
|
+
export function createMockResponse() {
|
|
38
|
+
const response = {
|
|
39
|
+
headersSent: false,
|
|
40
|
+
statusCode: null,
|
|
41
|
+
headers: {},
|
|
42
|
+
body: "",
|
|
43
|
+
chunks: [],
|
|
44
|
+
writeHead(status, headers) {
|
|
45
|
+
response.statusCode = status;
|
|
46
|
+
response.headers = headers || {};
|
|
47
|
+
response.headersSent = true;
|
|
48
|
+
},
|
|
49
|
+
write(data) {
|
|
50
|
+
response.chunks.push(data);
|
|
51
|
+
},
|
|
52
|
+
end(data) {
|
|
53
|
+
response.body = data || "";
|
|
54
|
+
},
|
|
55
|
+
getBody() {
|
|
56
|
+
return response.chunks.join("") + response.body;
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
return response;
|
|
60
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createMockConfig,
|
|
3
|
+
createMockServiceConfig,
|
|
4
|
+
createMockExtensionConfig,
|
|
5
|
+
} from "./config.js";
|
|
6
|
+
export { createMockStorage, MockStorage } from "./storage.js";
|
|
7
|
+
export { createMockLogger, createSilentLogger } from "./logger.js";
|
|
8
|
+
export { createMockGrpcFn, MockMetadata } from "./grpc.js";
|
|
9
|
+
export { createMockRequest, createMockResponse } from "./http.js";
|
|
10
|
+
export {
|
|
11
|
+
createMockObserverFn,
|
|
12
|
+
createMockTracer,
|
|
13
|
+
createMockAuthFn,
|
|
14
|
+
} from "./observer.js";
|
|
15
|
+
|
|
16
|
+
export { createMockResourceIndex } from "./resource-index.js";
|
|
17
|
+
export {
|
|
18
|
+
createMockMemoryClient,
|
|
19
|
+
createMockLlmClient,
|
|
20
|
+
createMockAgentClient,
|
|
21
|
+
createMockTraceClient,
|
|
22
|
+
createMockVectorClient,
|
|
23
|
+
createMockGraphClient,
|
|
24
|
+
createMockToolClient,
|
|
25
|
+
createMockDiscussionClient,
|
|
26
|
+
createStatefulDiscussionClient,
|
|
27
|
+
} from "./clients.js";
|
|
28
|
+
export { createMockServiceCallbacks } from "./service-callbacks.js";
|
|
29
|
+
export { createMockFs } from "./fs.js";
|
|
30
|
+
export { createMockClock } from "./clock.js";
|
|
31
|
+
export { createMockSubprocess } from "./subprocess.js";
|
|
32
|
+
export { createMockFinder } from "./finder.js";
|
|
33
|
+
export { createMockGitClient } from "./git-client.js";
|
|
34
|
+
export { createMockGhClient } from "./gh-client.js";
|
|
35
|
+
export { spy } from "./spy.js";
|
|
36
|
+
export {
|
|
37
|
+
createMockSupabaseClient,
|
|
38
|
+
createTurtleHelpers,
|
|
39
|
+
createMockProcess,
|
|
40
|
+
createMockStdin,
|
|
41
|
+
withSilentConsole,
|
|
42
|
+
createMockS3Client,
|
|
43
|
+
createMockQueries,
|
|
44
|
+
} from "./infra.js";
|
|
45
|
+
export {
|
|
46
|
+
createGraphIndexFixture,
|
|
47
|
+
createMockGrpcHealthDefinition,
|
|
48
|
+
createReplEnvironment,
|
|
49
|
+
} from "./environments.js";
|