@specific.dev/spectest 0.38.0 → 0.39.0
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/dist/components/k3s.js +1 -24
- package/dist/daemon.js +17 -5
- package/dist/inspect.d.ts +23 -0
- package/dist/inspect.js +65 -0
- package/package.json +1 -1
package/dist/components/k3s.js
CHANGED
|
@@ -6,7 +6,7 @@ import { readFile, unlink } from "node:fs/promises";
|
|
|
6
6
|
import { AppsV1Api, BatchV1Api, CoreV1Api, KubeConfig, KubernetesObjectApi, PatchStrategy, ResponseContext, ServerConfiguration, createConfiguration, loadAllYaml, } from "@kubernetes/client-node";
|
|
7
7
|
import { Observable } from "@kubernetes/client-node/dist/gen/rxjsStub.js";
|
|
8
8
|
import { dnsName, provides, SELF_SERVICE_TOKEN } from "../index.js";
|
|
9
|
-
import {
|
|
9
|
+
import { deepUnwrap, readTag, wrap } from "../inspect.js";
|
|
10
10
|
import { recorderAnnotate, recorderRemove } from "../recorder.js";
|
|
11
11
|
/** Port the in-cluster registry listens on (plain HTTP). */
|
|
12
12
|
const K3S_REGISTRY_PORT = 5000;
|
|
@@ -345,29 +345,6 @@ async function doFetch(request) {
|
|
|
345
345
|
binary: async () => buf,
|
|
346
346
|
});
|
|
347
347
|
}
|
|
348
|
-
/**
|
|
349
|
-
* Recursively strip the inspector's carrier/proxy wrappers from a
|
|
350
|
-
* value. Needed for arguments flowing into the kubernetes/client-node
|
|
351
|
-
* API methods — if a wrapped pod's `metadata.name` (a primitive-carrier
|
|
352
|
-
* object) reaches a URL template, the lib stringifies it to
|
|
353
|
-
* `"[object Object]"` and the request 404s.
|
|
354
|
-
*/
|
|
355
|
-
function deepUnwrap(value) {
|
|
356
|
-
if (value === null || value === undefined)
|
|
357
|
-
return value;
|
|
358
|
-
const raw = readRaw(value);
|
|
359
|
-
if (raw !== value)
|
|
360
|
-
return deepUnwrap(raw);
|
|
361
|
-
if (typeof value !== "object")
|
|
362
|
-
return value;
|
|
363
|
-
if (Array.isArray(value))
|
|
364
|
-
return value.map(deepUnwrap);
|
|
365
|
-
const out = {};
|
|
366
|
-
for (const [k, v] of Object.entries(value)) {
|
|
367
|
-
out[k] = deepUnwrap(v);
|
|
368
|
-
}
|
|
369
|
-
return out;
|
|
370
|
-
}
|
|
371
348
|
/**
|
|
372
349
|
* Wrap a `@kubernetes/client-node` Api instance so each method call
|
|
373
350
|
* runs in its own AsyncLocalStorage slot — `doFetch` writes the HTTP
|
package/dist/daemon.js
CHANGED
|
@@ -27,7 +27,7 @@ import { acquirePersistentBrowser } from "./browser.js";
|
|
|
27
27
|
import { isMobileApp, openPersistentMobile } from "./mobile.js";
|
|
28
28
|
import { openTerminal } from "./terminal.js";
|
|
29
29
|
import { recordEnv, recordExec, recordFake, recordHttp, recordTerminal, recordWait, reserveEvent, recorderEventCount, recorderMarkChildren, recorderTruncate, startRecording, stopRecording, truncateUtf8, } from "./recorder.js";
|
|
30
|
-
import { wrap, wrapResponse } from "./inspect.js";
|
|
30
|
+
import { deepUnwrap, wrap, wrapResponse } from "./inspect.js";
|
|
31
31
|
import { clearRecordSecrets, setRecordSecrets } from "./record-secrets.js";
|
|
32
32
|
import { encodeReplayBundle, replayChunk } from "./replay-bundle.js";
|
|
33
33
|
function namedServices(cfg) {
|
|
@@ -2528,8 +2528,9 @@ async function ensureFakeHelpers(name) {
|
|
|
2528
2528
|
fake.trackedHelpers = trackFakeHelpers(name, fake.helpers);
|
|
2529
2529
|
return fake.trackedHelpers;
|
|
2530
2530
|
}
|
|
2531
|
-
/** Wrap a fake's helpers so each call becomes a recorded `fake` event
|
|
2532
|
-
* and its return value is `wrap()`ped
|
|
2531
|
+
/** Wrap a fake's helpers so each call becomes a recorded `fake` event,
|
|
2532
|
+
* its arguments are `deepUnwrap`ped, and its return value is `wrap()`ped
|
|
2533
|
+
* for assertion provenance. Helpers
|
|
2533
2534
|
* are functions that read/mutate the fake's private state via closure;
|
|
2534
2535
|
* tests only ever see what those functions return. The proxy is built
|
|
2535
2536
|
* once and shared across tests; it consults the recorder at call time,
|
|
@@ -2556,10 +2557,21 @@ function trackFakeHelpers(fakeName, helpers) {
|
|
|
2556
2557
|
}
|
|
2557
2558
|
/** Invoke a fake helper function, recording a `fake` event and wrapping
|
|
2558
2559
|
* the return value. Handles both sync and async helpers, and records an
|
|
2559
|
-
* error event (then rethrows) if the helper throws.
|
|
2560
|
-
|
|
2560
|
+
* error event (then rethrows) if the helper throws.
|
|
2561
|
+
*
|
|
2562
|
+
* Arguments are `deepUnwrap`ped on the way in, so a helper body is plain
|
|
2563
|
+
* user code operating on plain values: a test that feeds one helper's
|
|
2564
|
+
* return (or a `ctx.fetch` field) to another passes a provenance wrapper,
|
|
2565
|
+
* and `msg.html.matchAll(...)` inside the fake would otherwise throw a
|
|
2566
|
+
* `TypeError` from a stack the author can't see. Same treatment the k8s
|
|
2567
|
+
* client's arguments get (`components/k3s.ts::withTagging`). The types
|
|
2568
|
+
* still say the value is a handle — passing a wrapped value where the
|
|
2569
|
+
* parameter type is known is a compile error asking for `.unwrap()`; this
|
|
2570
|
+
* catches the case where it isn't known (an `any` off `req.json()`). */
|
|
2571
|
+
function invokeFakeHelper(fakeName, member, fn, thisArg, callArgs) {
|
|
2561
2572
|
const t = Date.now();
|
|
2562
2573
|
const resv = reserveEvent();
|
|
2574
|
+
const args = callArgs.map((a) => deepUnwrap(a));
|
|
2563
2575
|
const safeArgs = args.map((a) => safeSerialize(a));
|
|
2564
2576
|
const recordResult = (value) => {
|
|
2565
2577
|
const seq = recordFake({
|
package/dist/inspect.d.ts
CHANGED
|
@@ -22,6 +22,29 @@ export declare function clearPendingNullish(): void;
|
|
|
22
22
|
export declare function adoptNullishTag<T>(value: T): T;
|
|
23
23
|
/** If x is wrapped, return the raw value; otherwise return x. */
|
|
24
24
|
export declare function readRaw<T>(x: T): T;
|
|
25
|
+
/**
|
|
26
|
+
* Strip provenance wrappers from a value AND from anything nested inside a
|
|
27
|
+
* plain object/array it holds. The inbound counterpart of {@link wrap}: for
|
|
28
|
+
* values crossing *back* into code that expects raw data — a fake's `helpers`,
|
|
29
|
+
* a k8s client method's arguments. Without it a helper that takes a value some
|
|
30
|
+
* earlier op produced gets a `Carrier`/proxy, and the first `.matchAll(...)` /
|
|
31
|
+
* typed-client call inside it throws a `TypeError` from deep in the callee,
|
|
32
|
+
* far from the call site that passed the wrapper. Coercion sinks
|
|
33
|
+
* ({@link makeCarrier}) rescue only the interpolation cases; a callee that
|
|
34
|
+
* calls a method on the value, or checks its `typeof`, still sees an object.
|
|
35
|
+
*
|
|
36
|
+
* One {@link readRaw} finishes a wrapped value: a wrapper's target is raw all
|
|
37
|
+
* the way down (the proxy wraps children lazily, on read), so there is nothing
|
|
38
|
+
* left to walk underneath it. The recursion is for the other shape — a
|
|
39
|
+
* container the *caller* built around wrapped leaves: `{ to: msg.to }`,
|
|
40
|
+
* `[row.id, row.email]`.
|
|
41
|
+
*
|
|
42
|
+
* Rebuilds only what changed, and only plain objects/arrays: a `Date`,
|
|
43
|
+
* `Buffer`, `Map`, `Response` or class instance is returned untouched rather
|
|
44
|
+
* than flattened into a plain object. Cyclic containers are left in place at
|
|
45
|
+
* the point the cycle closes.
|
|
46
|
+
*/
|
|
47
|
+
export declare function deepUnwrap<T>(value: T): T;
|
|
25
48
|
/** The raw type behind a wrapper: a `Carrier`/`WrappedObject`/`WrappedArray`/
|
|
26
49
|
* `WrappedResponse` resolves to its `unwrap()` return type; anything else is
|
|
27
50
|
* already raw and passes through unchanged. */
|
package/dist/inspect.js
CHANGED
|
@@ -136,6 +136,71 @@ export function readRaw(x) {
|
|
|
136
136
|
}
|
|
137
137
|
return cur;
|
|
138
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Strip provenance wrappers from a value AND from anything nested inside a
|
|
141
|
+
* plain object/array it holds. The inbound counterpart of {@link wrap}: for
|
|
142
|
+
* values crossing *back* into code that expects raw data — a fake's `helpers`,
|
|
143
|
+
* a k8s client method's arguments. Without it a helper that takes a value some
|
|
144
|
+
* earlier op produced gets a `Carrier`/proxy, and the first `.matchAll(...)` /
|
|
145
|
+
* typed-client call inside it throws a `TypeError` from deep in the callee,
|
|
146
|
+
* far from the call site that passed the wrapper. Coercion sinks
|
|
147
|
+
* ({@link makeCarrier}) rescue only the interpolation cases; a callee that
|
|
148
|
+
* calls a method on the value, or checks its `typeof`, still sees an object.
|
|
149
|
+
*
|
|
150
|
+
* One {@link readRaw} finishes a wrapped value: a wrapper's target is raw all
|
|
151
|
+
* the way down (the proxy wraps children lazily, on read), so there is nothing
|
|
152
|
+
* left to walk underneath it. The recursion is for the other shape — a
|
|
153
|
+
* container the *caller* built around wrapped leaves: `{ to: msg.to }`,
|
|
154
|
+
* `[row.id, row.email]`.
|
|
155
|
+
*
|
|
156
|
+
* Rebuilds only what changed, and only plain objects/arrays: a `Date`,
|
|
157
|
+
* `Buffer`, `Map`, `Response` or class instance is returned untouched rather
|
|
158
|
+
* than flattened into a plain object. Cyclic containers are left in place at
|
|
159
|
+
* the point the cycle closes.
|
|
160
|
+
*/
|
|
161
|
+
export function deepUnwrap(value) {
|
|
162
|
+
return deepUnwrapInner(value, new Set());
|
|
163
|
+
}
|
|
164
|
+
function deepUnwrapInner(value, seen) {
|
|
165
|
+
if (value === null || value === undefined)
|
|
166
|
+
return value;
|
|
167
|
+
const raw = readRaw(value);
|
|
168
|
+
// It was wrapped, so it's now fully raw — and recursing into it would be
|
|
169
|
+
// wrong anyway (the raw form may be an exotic object we must not rebuild).
|
|
170
|
+
if (raw !== value)
|
|
171
|
+
return raw;
|
|
172
|
+
if (typeof raw !== "object")
|
|
173
|
+
return raw;
|
|
174
|
+
const obj = raw;
|
|
175
|
+
if (seen.has(obj))
|
|
176
|
+
return obj;
|
|
177
|
+
if (Array.isArray(obj)) {
|
|
178
|
+
seen.add(obj);
|
|
179
|
+
let changed = false;
|
|
180
|
+
const out = obj.map((el) => {
|
|
181
|
+
const next = deepUnwrapInner(el, seen);
|
|
182
|
+
if (next !== el)
|
|
183
|
+
changed = true;
|
|
184
|
+
return next;
|
|
185
|
+
});
|
|
186
|
+
seen.delete(obj);
|
|
187
|
+
return changed ? out : obj;
|
|
188
|
+
}
|
|
189
|
+
const proto = Object.getPrototypeOf(obj);
|
|
190
|
+
if (proto !== Object.prototype && proto !== null)
|
|
191
|
+
return obj;
|
|
192
|
+
seen.add(obj);
|
|
193
|
+
let changed = false;
|
|
194
|
+
const out = {};
|
|
195
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
196
|
+
const next = deepUnwrapInner(v, seen);
|
|
197
|
+
if (next !== v)
|
|
198
|
+
changed = true;
|
|
199
|
+
out[k] = next;
|
|
200
|
+
}
|
|
201
|
+
seen.delete(obj);
|
|
202
|
+
return changed ? out : obj;
|
|
203
|
+
}
|
|
139
204
|
/**
|
|
140
205
|
* Wrap a value so reads through it carry an `OpTag`. Recursion is lazy:
|
|
141
206
|
* a property read on an object Proxy wraps its child on demand.
|