@jterrazz/test 16.1.1 → 18.0.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/README.md +45 -171
- package/dist/browser/index.js +1 -2980
- package/dist/browser/services.js +1 -0
- package/dist/browser/stubs.js +1 -0
- package/dist/browser-shared-index.browser.js +3233 -0
- package/dist/checker.d.ts +6 -1
- package/dist/checker.js +3721 -2624
- package/dist/config.d.ts +40 -0
- package/dist/config.js +309 -0
- package/dist/in-place.global-setup.d.ts +12 -0
- package/dist/in-place.global-setup.js +21 -0
- package/dist/index.d.ts +1641 -3004
- package/dist/index.js +5095 -3835
- package/dist/mobile-session.global-setup.d.ts +15 -0
- package/dist/mobile-session.global-setup.js +31 -0
- package/dist/oxlint.cjs +6069 -5634
- package/dist/oxlint.d.cts +43 -7212
- package/dist/oxlint.d.ts +43 -7212
- package/dist/oxlint.js +3979 -5799
- package/dist/route-coverage.global-setup.d.ts +43 -0
- package/dist/route-coverage.global-setup.js +1 -0
- package/dist/services.d.ts +3 -0
- package/dist/services.global-setup.d.ts +43 -0
- package/dist/services.global-setup.js +1 -0
- package/dist/services.js +597 -0
- package/dist/shared-appium.adapter.js +428 -0
- package/dist/shared-artifacts.js +34 -0
- package/dist/shared-chapter.js +7 -0
- package/dist/shared-container.port.d.ts +31 -0
- package/dist/shared-derived-projects.js +73 -0
- package/dist/shared-device.port.d.ts +189 -0
- package/dist/shared-exchange.js +447 -0
- package/dist/shared-in-place.js +172 -0
- package/dist/shared-index.services.d.ts +346 -0
- package/dist/shared-index.stubs.d.ts +287 -0
- package/dist/shared-json-shape.js +17 -0
- package/dist/shared-landmarks.js +71 -0
- package/dist/shared-ledger.js +68 -0
- package/dist/shared-listened-port.js +19 -0
- package/dist/shared-literate-plugin.js +184 -0
- package/dist/shared-manifest.js +1321 -0
- package/dist/shared-pause.js +22 -0
- package/dist/shared-peer.js +88 -0
- package/dist/shared-playwright.adapter.js +328 -0
- package/dist/shared-process-alive.js +13 -0
- package/dist/shared-process.adapter.js +74 -0
- package/dist/shared-record-key.js +13 -0
- package/dist/shared-refusal.js +35 -0
- package/dist/shared-resolve.js +24 -0
- package/dist/shared-route-coverage.global-setup.js +187 -0
- package/dist/shared-scenario-failure.js +198 -0
- package/dist/shared-serve.js +407 -0
- package/dist/shared-service-images.js +10 -0
- package/dist/shared-services.global-setup.js +174 -0
- package/dist/shared-session-ledger.js +305 -0
- package/dist/shared-setup-entry.js +12 -0
- package/dist/shared-source-scan.js +133 -0
- package/dist/shared-spec-document.js +303 -0
- package/dist/shared-spec-grammar.js +66 -0
- package/dist/shared-specifications.js +26 -0
- package/dist/shared-specs-modules.js +1033 -0
- package/dist/shared-stub-reply.js +167 -0
- package/dist/shared-stub-server.js +142 -0
- package/dist/shared-temp-root.global-setup.js +73 -0
- package/dist/shared-test-baseline.js +26 -0
- package/dist/shared-test-section.js +58 -0
- package/dist/shared-testcontainers.adapter.js +306 -0
- package/dist/shared-tokens.js +55 -0
- package/dist/shared-yaml-document.js +119 -0
- package/dist/stubs.d.ts +2 -0
- package/dist/stubs.js +657 -0
- package/dist/temp-root.global-setup.d.ts +7 -0
- package/dist/temp-root.global-setup.js +1 -0
- package/dist/vitest.d.ts +93 -164
- package/dist/vitest.js +382 -550
- package/docs/README.md +82 -36
- package/docs/concepts/architecture.md +118 -0
- package/docs/concepts/conventions.md +333 -0
- package/docs/guides/api-specs.md +374 -0
- package/docs/guides/cli-specs.md +566 -0
- package/docs/guides/component-specs.md +252 -0
- package/docs/guides/develop.md +97 -0
- package/docs/guides/integration-specs.md +181 -0
- package/docs/guides/job-specs.md +180 -0
- package/docs/guides/lint-specs.md +174 -0
- package/docs/guides/mobile-specs.md +319 -0
- package/docs/guides/module-tests.md +149 -0
- package/docs/guides/release.md +44 -0
- package/docs/guides/spec-documents.md +189 -0
- package/docs/guides/website-specs.md +413 -0
- package/docs/reference/assertions.md +345 -0
- package/docs/reference/glossary.md +20 -0
- package/docs/reference/linting.md +483 -0
- package/docs/reference/services.md +236 -0
- package/docs/reference/stubs.md +500 -0
- package/docs/reference/time-and-doubles.md +137 -0
- package/docs/reference/tokens.md +258 -0
- package/docs/reference/visitor.md +221 -0
- package/docs/start/getting-started.md +187 -0
- package/docs/start/set-up-a-project.md +228 -0
- package/package.json +56 -36
- package/schema/spec.schema.json +43 -17
- package/dist/appium.adapter.js +0 -489
- package/dist/artifacts.js +0 -32
- package/dist/fixtures.js +0 -110
- package/dist/literate.js +0 -1747
- package/dist/match.js +0 -804
- package/dist/playwright.adapter.js +0 -438
- package/dist/server.js +0 -160
- package/dist/stream.js +0 -321
- package/dist/substring-warning.js +0 -282
- package/docs/01-architecture.md +0 -95
- package/docs/02-developing.md +0 -351
- package/docs/03-testing.md +0 -495
- package/docs/04-operating.md +0 -101
- package/docs/05-module-tests.md +0 -152
- package/docs/06-integration.md +0 -180
- package/docs/07-component.md +0 -246
- package/docs/08-website.md +0 -404
- package/docs/09-mobile.md +0 -270
- package/docs/10-api.md +0 -264
- package/docs/11-jobs.md +0 -157
- package/docs/12-cli.md +0 -677
- package/docs/13-elements.md +0 -248
- package/docs/14-assertions.md +0 -314
- package/docs/15-tokens.md +0 -253
- package/docs/16-contracts.md +0 -438
- package/docs/17-services.md +0 -188
- package/docs/18-conventions.md +0 -332
- package/docs/19-linting.md +0 -512
- package/docs/decisions/001-v9-conventions-on-three-channels.md +0 -50
- package/docs/decisions/002-the-statique-channel-holds-only-what-upstream-does-not.md +0 -49
- package/docs/decisions/003-a-rendered-component-is-a-facet-on-vitest-browser-mode.md +0 -84
- package/docs/decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-facet.md +0 -81
- package/docs/decisions/005-the-doubles-ladder-is-closed.md +0 -86
- package/docs/decisions/006-the-catalogue-reaches-a-file-by-its-role.md +0 -245
- package/docs/decisions/007-compose-mode-leaves-the-package.md +0 -53
- package/docs/decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md +0 -75
- package/docs/decisions/_template.md +0 -22
- package/docs/reference/classes/Matcher.md +0 -120
- package/docs/reference/classes/ProcessHandle.md +0 -368
- package/docs/reference/functions/defineContract.md +0 -39
- package/docs/reference/functions/defineContracts.md +0 -36
- package/docs/reference/functions/disabled.md +0 -28
- package/docs/reference/functions/enabled.md +0 -20
- package/docs/reference/functions/focused.md +0 -26
- package/docs/reference/functions/mockOf.md +0 -79
- package/docs/reference/functions/postgres.md +0 -31
- package/docs/reference/functions/process.md +0 -33
- package/docs/reference/functions/redis.md +0 -28
- package/docs/reference/functions/required.md +0 -41
- package/docs/reference/functions/selected.md +0 -26
- package/docs/reference/functions/sqlite.md +0 -33
- package/docs/reference/functions/testId.md +0 -20
- package/docs/reference/functions/text.md +0 -40
- package/docs/reference/functions/valued.md +0 -28
- package/docs/reference/functions/waitUntil.md +0 -46
- package/docs/reference/functions/within.md +0 -29
- package/docs/reference/index.md +0 -196
- package/docs/reference/interfaces/BaseResult.md +0 -87
- package/docs/reference/interfaces/CallResult.md +0 -160
- package/docs/reference/interfaces/CaptureScope.md +0 -82
- package/docs/reference/interfaces/CliResult.md +0 -253
- package/docs/reference/interfaces/ContainerAccessor.md +0 -166
- package/docs/reference/interfaces/DirectoryAccessor.md +0 -33
- package/docs/reference/interfaces/FetchResult.md +0 -180
- package/docs/reference/interfaces/FilesystemAccessor.md +0 -46
- package/docs/reference/interfaces/HttpResult.md +0 -126
- package/docs/reference/interfaces/JsonAccessor.md +0 -46
- package/docs/reference/interfaces/PageResult.md +0 -356
- package/docs/reference/interfaces/RenderResult.md +0 -72
- package/docs/reference/interfaces/RenderedText.md +0 -110
- package/docs/reference/interfaces/ResponseAccessor.md +0 -47
- package/docs/reference/interfaces/ScreenResult.md +0 -128
- package/docs/reference/interfaces/TableAccessor.md +0 -26
- package/docs/reference/interfaces/TextAccessor.md +0 -96
- package/docs/reference/type-aliases/ApiHandle.md +0 -68
- package/docs/reference/type-aliases/ApiSpecification.md +0 -213
- package/docs/reference/type-aliases/ApiSpecificationOptions.md +0 -68
- package/docs/reference/type-aliases/BrowserConsoleMessage.md +0 -30
- package/docs/reference/type-aliases/BrowserLinkElement.md +0 -50
- package/docs/reference/type-aliases/BrowserMetaElement.md +0 -40
- package/docs/reference/type-aliases/BrowserOpenOptions.md +0 -87
- package/docs/reference/type-aliases/BrowserPage.md +0 -132
- package/docs/reference/type-aliases/BrowserPort.md +0 -51
- package/docs/reference/type-aliases/CliEnv.md +0 -11
- package/docs/reference/type-aliases/CliHandle.md +0 -80
- package/docs/reference/type-aliases/CliInput.md +0 -36
- package/docs/reference/type-aliases/CliOutput.md +0 -46
- package/docs/reference/type-aliases/CliPort.md +0 -58
- package/docs/reference/type-aliases/CliSpecification.md +0 -144
- package/docs/reference/type-aliases/CliSpecificationOptions.md +0 -134
- package/docs/reference/type-aliases/ComponentChain.md +0 -156
- package/docs/reference/type-aliases/ComponentScenario.md +0 -20
- package/docs/reference/type-aliases/ComponentUi.md +0 -47
- package/docs/reference/type-aliases/ComponentVisitor.md +0 -10
- package/docs/reference/type-aliases/ContainerPort.md +0 -113
- package/docs/reference/type-aliases/Contract.md +0 -68
- package/docs/reference/type-aliases/ContractInput.md +0 -13
- package/docs/reference/type-aliases/ContractRequest.md +0 -95
- package/docs/reference/type-aliases/ContractResponder.md +0 -23
- package/docs/reference/type-aliases/ContractResponse.md +0 -78
- package/docs/reference/type-aliases/ContractResponseValue.md +0 -13
- package/docs/reference/type-aliases/Contracts.md +0 -48
- package/docs/reference/type-aliases/DatabaseKeys.md +0 -16
- package/docs/reference/type-aliases/DatabasePort.md +0 -72
- package/docs/reference/type-aliases/DeviceOpenOptions.md +0 -46
- package/docs/reference/type-aliases/DevicePort.md +0 -51
- package/docs/reference/type-aliases/DeviceScreen.md +0 -32
- package/docs/reference/type-aliases/DeviceTimeouts.md +0 -39
- package/docs/reference/type-aliases/DockerSpecConfig.md +0 -43
- package/docs/reference/type-aliases/DomMount.md +0 -21
- package/docs/reference/type-aliases/ElementKind.md +0 -27
- package/docs/reference/type-aliases/ElementMatch.md +0 -74
- package/docs/reference/type-aliases/ElementOptions.md +0 -29
- package/docs/reference/type-aliases/ElementRef.md +0 -128
- package/docs/reference/type-aliases/ExecOptions.md +0 -37
- package/docs/reference/type-aliases/FileAccessor.md +0 -54
- package/docs/reference/type-aliases/HonoApp.md +0 -31
- package/docs/reference/type-aliases/HttpContractFilter.md +0 -49
- package/docs/reference/type-aliases/HttpResponseInit.md +0 -46
- package/docs/reference/type-aliases/HttpStreamInit.md +0 -58
- package/docs/reference/type-aliases/IntegrationHandle.md +0 -46
- package/docs/reference/type-aliases/IntegrationSpecification.md +0 -111
- package/docs/reference/type-aliases/IntegrationSpecificationOptions.md +0 -42
- package/docs/reference/type-aliases/Intercept.md +0 -19
- package/docs/reference/type-aliases/InterceptOptions.md +0 -28
- package/docs/reference/type-aliases/InterceptScope.md +0 -11
- package/docs/reference/type-aliases/IsolationStrategy.md +0 -75
- package/docs/reference/type-aliases/JobHandle.md +0 -34
- package/docs/reference/type-aliases/JobsHandle.md +0 -57
- package/docs/reference/type-aliases/JobsResult.md +0 -16
- package/docs/reference/type-aliases/JobsSpecification.md +0 -94
- package/docs/reference/type-aliases/JobsSpecificationOptions.md +0 -56
- package/docs/reference/type-aliases/LandmarkKind.md +0 -20
- package/docs/reference/type-aliases/LiterateRunFlags.md +0 -27
- package/docs/reference/type-aliases/LiterateServeRegistration.md +0 -70
- package/docs/reference/type-aliases/MatchFixtureOptions.md +0 -27
- package/docs/reference/type-aliases/MatchableRequest.md +0 -61
- package/docs/reference/type-aliases/MatcherKind.md +0 -34
- package/docs/reference/type-aliases/MobileBackendOptions.md +0 -28
- package/docs/reference/type-aliases/MobileElementKind.md +0 -14
- package/docs/reference/type-aliases/MobileElementMatch.md +0 -60
- package/docs/reference/type-aliases/MobileElementRef.md +0 -20
- package/docs/reference/type-aliases/MobileHandle.md +0 -65
- package/docs/reference/type-aliases/MobileScenario.md +0 -20
- package/docs/reference/type-aliases/MobileSpecification.md +0 -51
- package/docs/reference/type-aliases/MobileSpecificationOptions.md +0 -126
- package/docs/reference/type-aliases/MobileVisitor.md +0 -81
- package/docs/reference/type-aliases/MockOfOptions.md +0 -26
- package/docs/reference/type-aliases/MockPort.md +0 -10
- package/docs/reference/type-aliases/PinnedClock.md +0 -10
- package/docs/reference/type-aliases/PostgresOptions.md +0 -32
- package/docs/reference/type-aliases/ProcessOptions.md +0 -112
- package/docs/reference/type-aliases/RedisOptions.md +0 -20
- package/docs/reference/type-aliases/RenderSubject.md +0 -12
- package/docs/reference/type-aliases/ScreenNode.md +0 -67
- package/docs/reference/type-aliases/ServerPort.md +0 -36
- package/docs/reference/type-aliases/ServerResponse.md +0 -46
- package/docs/reference/type-aliases/ServerSpec.md +0 -21
- package/docs/reference/type-aliases/ServiceHandle.md +0 -216
- package/docs/reference/type-aliases/ServiceRecord.md +0 -12
- package/docs/reference/type-aliases/SpecDocument.md +0 -84
- package/docs/reference/type-aliases/SpecEnvToken.md +0 -21
- package/docs/reference/type-aliases/SpecFileAssertion.md +0 -27
- package/docs/reference/type-aliases/SpecFixture.md +0 -30
- package/docs/reference/type-aliases/SpecKind.md +0 -8
- package/docs/reference/type-aliases/SpecRun.md +0 -108
- package/docs/reference/type-aliases/SpecServeEntry.md +0 -40
- package/docs/reference/type-aliases/SpecStream.md +0 -32
- package/docs/reference/type-aliases/SpecificationConfig.md +0 -290
- package/docs/reference/type-aliases/SqliteOptions.md +0 -36
- package/docs/reference/type-aliases/SseEvent.md +0 -58
- package/docs/reference/type-aliases/StreamBody.md +0 -61
- package/docs/reference/type-aliases/TextFilter.md +0 -22
- package/docs/reference/type-aliases/VisitScenario.md +0 -20
- package/docs/reference/type-aliases/Visitor.md +0 -215
- package/docs/reference/type-aliases/WaitUntilOptions.md +0 -46
- package/docs/reference/type-aliases/WebsiteBackendOptions.md +0 -37
- package/docs/reference/type-aliases/WebsiteHandle.md +0 -51
- package/docs/reference/type-aliases/WebsiteSpecification.md +0 -118
- package/docs/reference/type-aliases/WebsiteSpecificationOptions.md +0 -40
- package/docs/reference/variables/anthropic.md +0 -19
- package/docs/reference/variables/banner.md +0 -10
- package/docs/reference/variables/button.md +0 -10
- package/docs/reference/variables/clock.md +0 -16
- package/docs/reference/variables/complementary.md +0 -10
- package/docs/reference/variables/component.md +0 -10
- package/docs/reference/variables/content.md +0 -10
- package/docs/reference/variables/contentinfo.md +0 -10
- package/docs/reference/variables/dialog.md +0 -10
- package/docs/reference/variables/field.md +0 -10
- package/docs/reference/variables/form.md +0 -10
- package/docs/reference/variables/heading.md +0 -10
- package/docs/reference/variables/http.md +0 -40
- package/docs/reference/variables/intercept.md +0 -11
- package/docs/reference/variables/link.md +0 -10
- package/docs/reference/variables/listitem.md +0 -10
- package/docs/reference/variables/main.md +0 -10
- package/docs/reference/variables/match.md +0 -8
- package/docs/reference/variables/navigation.md +0 -10
- package/docs/reference/variables/openai.md +0 -21
- package/docs/reference/variables/option.md +0 -17
- package/docs/reference/variables/region.md +0 -10
- package/docs/reference/variables/row.md +0 -10
- package/docs/reference/variables/search.md +0 -10
- package/docs/reference/variables/specification.md +0 -8
- package/docs/reference/variables/status.md +0 -10
- package/docs/reference/variables/table.md +0 -10
package/dist/stubs.js
ADDED
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
import { t as isRecord } from "./shared-json-shape.js";
|
|
2
|
+
import { n as refusal } from "./shared-refusal.js";
|
|
3
|
+
import { a as defineStubs, d as structuralEquals, f as structuralSubset, i as defineStub, o as describeRoute, r as declared, t as STREAM_BODY, y as CaptureScope } from "./shared-exchange.js";
|
|
4
|
+
//#region src/stubs/kinds/http-filter.ts
|
|
5
|
+
function rawOf(body) {
|
|
6
|
+
return typeof body === "string" ? body : JSON.stringify(body ?? "");
|
|
7
|
+
}
|
|
8
|
+
function matchesBody(body, expected) {
|
|
9
|
+
if (typeof expected === "string") return rawOf(body).includes(expected);
|
|
10
|
+
if (expected instanceof RegExp) return expected.test(rawOf(body));
|
|
11
|
+
return structuralSubset(expected, body, new CaptureScope());
|
|
12
|
+
}
|
|
13
|
+
function matchesEntries(expected, lookup) {
|
|
14
|
+
return Object.entries(expected).every(([key, value]) => {
|
|
15
|
+
const actual = lookup(key);
|
|
16
|
+
if (actual === void 0) return false;
|
|
17
|
+
return value instanceof RegExp ? value.test(actual) : actual === value;
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function buildMatch(filter) {
|
|
21
|
+
if (!filter) return;
|
|
22
|
+
return (request) => {
|
|
23
|
+
if (filter.body !== void 0 && !matchesBody(request.body, filter.body)) return false;
|
|
24
|
+
if (filter.headers && !matchesEntries(filter.headers, (key) => request.headers[key.toLowerCase()])) return false;
|
|
25
|
+
if (filter.query) {
|
|
26
|
+
let params;
|
|
27
|
+
try {
|
|
28
|
+
params = new URL(request.url, "http://stub.invalid").searchParams;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (!matchesEntries(filter.query, (key) => params.get(key) ?? void 0)) return false;
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function absentHeadersOf(filter = {}) {
|
|
38
|
+
const absent = filter.without?.headers?.map((name) => name.toLowerCase()) ?? [];
|
|
39
|
+
if (absent.length === 0) return;
|
|
40
|
+
const required = new Set(Object.keys(filter.headers ?? {}).map((name) => name.toLowerCase()));
|
|
41
|
+
const contradicted = absent.filter((name) => required.has(name));
|
|
42
|
+
if (contradicted.length > 0) {
|
|
43
|
+
const names = contradicted.join(", ");
|
|
44
|
+
throw new Error(refusal({
|
|
45
|
+
fix: `state ${names} in \`headers\` or in \`without.headers\`, never both`,
|
|
46
|
+
found: `an http stub filter that both requires and forbids the header(s) ${names}`,
|
|
47
|
+
read: "docs/reference/stubs.md#http-any-url",
|
|
48
|
+
why: "A header both required and absent matches no request, so the stub could never fire."
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
return absent;
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/stubs/kinds/http.ts
|
|
55
|
+
function wrapJson(data) {
|
|
56
|
+
return {
|
|
57
|
+
status: 200,
|
|
58
|
+
body: data
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const TEXT_CONTENT_TYPE = "text/plain; charset=utf-8";
|
|
62
|
+
const STREAM_CONTENT_TYPE = "application/octet-stream";
|
|
63
|
+
const SSE_CONTENT_TYPE = "text/event-stream";
|
|
64
|
+
function frameEvent(event) {
|
|
65
|
+
const lines = [];
|
|
66
|
+
if (event.event !== void 0) lines.push(`event: ${event.event}`);
|
|
67
|
+
if (event.id !== void 0) lines.push(`id: ${event.id}`);
|
|
68
|
+
if (event.retry !== void 0) lines.push(`retry: ${event.retry}`);
|
|
69
|
+
const payload = typeof event.data === "string" ? event.data : JSON.stringify(event.data);
|
|
70
|
+
for (const line of payload.split("\n")) lines.push(`data: ${line}`);
|
|
71
|
+
return `${lines.join("\n")}\n\n`;
|
|
72
|
+
}
|
|
73
|
+
function declare(method, url, filter) {
|
|
74
|
+
const absentHeaders = absentHeadersOf(filter);
|
|
75
|
+
return {
|
|
76
|
+
...absentHeaders && { absentHeaders },
|
|
77
|
+
adapter: "http",
|
|
78
|
+
match: buildMatch(filter),
|
|
79
|
+
method,
|
|
80
|
+
url,
|
|
81
|
+
wrap: wrapJson
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** Generic HTTP stub helpers: an absolute URL, or a path form (`/articles/{{uuid}}`) matching any origin. */
|
|
85
|
+
const http = {
|
|
86
|
+
any(url, filter) {
|
|
87
|
+
return declare("*", url, filter);
|
|
88
|
+
},
|
|
89
|
+
delete(url, filter) {
|
|
90
|
+
return declare("DELETE", url, filter);
|
|
91
|
+
},
|
|
92
|
+
get(url, filter) {
|
|
93
|
+
return declare("GET", url, filter);
|
|
94
|
+
},
|
|
95
|
+
patch(url, filter) {
|
|
96
|
+
return declare("PATCH", url, filter);
|
|
97
|
+
},
|
|
98
|
+
post(url, filter) {
|
|
99
|
+
return declare("POST", url, filter);
|
|
100
|
+
},
|
|
101
|
+
put(url, filter) {
|
|
102
|
+
return declare("PUT", url, filter);
|
|
103
|
+
},
|
|
104
|
+
/** A stub the chain fails on if the subject reaches its route: `required` with `times: 0`. */
|
|
105
|
+
never(request) {
|
|
106
|
+
return {
|
|
107
|
+
request,
|
|
108
|
+
required: true,
|
|
109
|
+
response: {
|
|
110
|
+
status: 418,
|
|
111
|
+
body: { error: `@jterrazz/test: ${describeRoute(request)} is declared never requested` }
|
|
112
|
+
},
|
|
113
|
+
times: 0
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
/** Response: a body-less reply (204 by default). */
|
|
117
|
+
empty(status = 204) {
|
|
118
|
+
return {
|
|
119
|
+
status,
|
|
120
|
+
body: null
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
/** Response: an error status. Without a body, `{ error: 'HTTP <status>' }`. */
|
|
124
|
+
error(status, body) {
|
|
125
|
+
return {
|
|
126
|
+
status,
|
|
127
|
+
body: body === void 0 ? { error: `HTTP ${status}` } : body
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
/** Response: a JSON body (200 by default). */
|
|
131
|
+
json(body, init) {
|
|
132
|
+
return {
|
|
133
|
+
status: init?.status ?? 200,
|
|
134
|
+
body,
|
|
135
|
+
delay: init?.delay,
|
|
136
|
+
headers: init?.headers
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
/** Response: no response at all; `fetch` rejects, as when no server is running. */
|
|
140
|
+
unreachable() {
|
|
141
|
+
return {
|
|
142
|
+
body: null,
|
|
143
|
+
transport: "network-error"
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
/** Response: a body that arrives in pieces, written in order. */
|
|
147
|
+
stream(chunks, init) {
|
|
148
|
+
return {
|
|
149
|
+
status: init?.status ?? 200,
|
|
150
|
+
body: {
|
|
151
|
+
chunks: [...chunks],
|
|
152
|
+
contentType: init?.contentType ?? STREAM_CONTENT_TYPE,
|
|
153
|
+
delayBetweenChunks: init?.delay ?? 0,
|
|
154
|
+
kind: STREAM_BODY
|
|
155
|
+
},
|
|
156
|
+
headers: init?.headers
|
|
157
|
+
};
|
|
158
|
+
},
|
|
159
|
+
/** Response: a server-sent event stream, served as `text/event-stream`. */
|
|
160
|
+
sse(events, init) {
|
|
161
|
+
return {
|
|
162
|
+
status: init?.status ?? 200,
|
|
163
|
+
body: {
|
|
164
|
+
chunks: events.map(frameEvent),
|
|
165
|
+
contentType: SSE_CONTENT_TYPE,
|
|
166
|
+
delayBetweenChunks: init?.delay ?? 0,
|
|
167
|
+
kind: STREAM_BODY
|
|
168
|
+
},
|
|
169
|
+
headers: {
|
|
170
|
+
"cache-control": "no-cache",
|
|
171
|
+
...init?.headers
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
},
|
|
175
|
+
/** Response: a text body, served as `text/plain` (200 by default). */
|
|
176
|
+
text(body, init) {
|
|
177
|
+
return {
|
|
178
|
+
status: init?.status ?? 200,
|
|
179
|
+
body,
|
|
180
|
+
delay: init?.delay,
|
|
181
|
+
headers: {
|
|
182
|
+
"content-type": TEXT_CONTENT_TYPE,
|
|
183
|
+
...init?.headers
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/stubs/kinds/text-filter.ts
|
|
190
|
+
function matchesText(filter, actual) {
|
|
191
|
+
if (filter === void 0) return true;
|
|
192
|
+
if (typeof filter === "string") return actual === filter;
|
|
193
|
+
if (filter instanceof RegExp) return filter.test(actual);
|
|
194
|
+
return structuralEquals(filter, actual, new CaptureScope());
|
|
195
|
+
}
|
|
196
|
+
/** A field of a request body read as JSON: absent wherever the body is not an object holding it. */
|
|
197
|
+
function fieldOf(value, key) {
|
|
198
|
+
return isRecord(value) ? value[key] : void 0;
|
|
199
|
+
}
|
|
200
|
+
const listOf = (value) => Array.isArray(value) ? value : [];
|
|
201
|
+
const textOf = (value) => typeof value === "string" ? value : "";
|
|
202
|
+
/** The content of the first message of `role`, as text: a structured content is its JSON. */
|
|
203
|
+
function messageText(messages, role) {
|
|
204
|
+
const content = fieldOf(listOf(messages).find((message) => fieldOf(message, "role") === role), "content");
|
|
205
|
+
return content === void 0 || typeof content === "string" ? textOf(content) : JSON.stringify(content);
|
|
206
|
+
}
|
|
207
|
+
const namesOf = (tools, read) => listOf(tools).map((tool) => textOf(read(tool))).filter((name) => name !== "");
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region src/stubs/kinds/anthropic.ts
|
|
210
|
+
const ANTHROPIC_MESSAGES_URL = "https://api.anthropic.com/v1/messages";
|
|
211
|
+
function matchesFilter(body, filter) {
|
|
212
|
+
const names = namesOf(fieldOf(body, "tools"), (tool) => fieldOf(tool, "name"));
|
|
213
|
+
return matchesText(filter.model, textOf(fieldOf(body, "model"))) && matchesText(filter.system, textOf(fieldOf(body, "system"))) && matchesText(filter.user, messageText(fieldOf(body, "messages"), "user")) && (filter.tools?.every((tool) => names.includes(tool)) ?? true);
|
|
214
|
+
}
|
|
215
|
+
function buildReply(data) {
|
|
216
|
+
return {
|
|
217
|
+
status: 200,
|
|
218
|
+
body: {
|
|
219
|
+
id: "msg-test",
|
|
220
|
+
type: "message",
|
|
221
|
+
role: "assistant",
|
|
222
|
+
content: [{
|
|
223
|
+
type: "text",
|
|
224
|
+
text: typeof data === "string" ? data : JSON.stringify(data)
|
|
225
|
+
}],
|
|
226
|
+
model: "claude-sonnet-4-20250514",
|
|
227
|
+
stop_reason: "end_turn",
|
|
228
|
+
usage: {
|
|
229
|
+
input_tokens: 10,
|
|
230
|
+
output_tokens: 10
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function streamEvents(pieces) {
|
|
236
|
+
return [
|
|
237
|
+
{
|
|
238
|
+
type: "message_start",
|
|
239
|
+
message: {
|
|
240
|
+
id: "msg-test",
|
|
241
|
+
type: "message",
|
|
242
|
+
role: "assistant",
|
|
243
|
+
content: [],
|
|
244
|
+
model: "claude-sonnet-4-20250514",
|
|
245
|
+
stop_reason: null,
|
|
246
|
+
stop_sequence: null,
|
|
247
|
+
usage: {
|
|
248
|
+
input_tokens: 10,
|
|
249
|
+
output_tokens: 1
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
type: "content_block_start",
|
|
255
|
+
index: 0,
|
|
256
|
+
content_block: {
|
|
257
|
+
type: "text",
|
|
258
|
+
text: ""
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
{ type: "ping" },
|
|
262
|
+
...pieces.map((text) => ({
|
|
263
|
+
type: "content_block_delta",
|
|
264
|
+
index: 0,
|
|
265
|
+
delta: {
|
|
266
|
+
type: "text_delta",
|
|
267
|
+
text
|
|
268
|
+
}
|
|
269
|
+
})),
|
|
270
|
+
{
|
|
271
|
+
type: "content_block_stop",
|
|
272
|
+
index: 0
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
type: "message_delta",
|
|
276
|
+
delta: {
|
|
277
|
+
stop_reason: "end_turn",
|
|
278
|
+
stop_sequence: null
|
|
279
|
+
},
|
|
280
|
+
usage: { output_tokens: 10 }
|
|
281
|
+
},
|
|
282
|
+
{ type: "message_stop" }
|
|
283
|
+
].map((data) => ({
|
|
284
|
+
data,
|
|
285
|
+
event: data.type
|
|
286
|
+
}));
|
|
287
|
+
}
|
|
288
|
+
/** Anthropic API stub helpers. */
|
|
289
|
+
const anthropic = {
|
|
290
|
+
/** Match Messages API calls, optionally through a gateway URL; a string filter is exact equality. */
|
|
291
|
+
messages(filter, url) {
|
|
292
|
+
return {
|
|
293
|
+
adapter: "anthropic",
|
|
294
|
+
method: "POST",
|
|
295
|
+
url: url ?? ANTHROPIC_MESSAGES_URL,
|
|
296
|
+
match: filter ? ({ body }) => matchesFilter(body, filter) : void 0,
|
|
297
|
+
wrap(data) {
|
|
298
|
+
if (isRecord(data)) return {
|
|
299
|
+
status: 200,
|
|
300
|
+
body: data
|
|
301
|
+
};
|
|
302
|
+
return buildReply(data);
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
},
|
|
306
|
+
/** A stub the chain fails on if the subject reaches this endpoint: `http.never()`. */
|
|
307
|
+
never(request) {
|
|
308
|
+
return http.never(request);
|
|
309
|
+
},
|
|
310
|
+
/** Response: data wrapped in the Messages format. */
|
|
311
|
+
reply(data) {
|
|
312
|
+
return buildReply(data);
|
|
313
|
+
},
|
|
314
|
+
/** Response: the text streamed as server-sent events, one `text_delta` per piece. */
|
|
315
|
+
stream(pieces, init) {
|
|
316
|
+
return http.sse(streamEvents(pieces), { delay: init?.delay ?? 0 });
|
|
317
|
+
},
|
|
318
|
+
/** Response: return an Anthropic error. */
|
|
319
|
+
error(status, message) {
|
|
320
|
+
return {
|
|
321
|
+
status,
|
|
322
|
+
body: {
|
|
323
|
+
type: "error",
|
|
324
|
+
error: {
|
|
325
|
+
type: status === 429 ? "rate_limit_error" : "api_error",
|
|
326
|
+
message: message ?? `Anthropic error (${status})`
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
},
|
|
331
|
+
/** Response: simulate a timeout. */
|
|
332
|
+
timeout() {
|
|
333
|
+
return {
|
|
334
|
+
status: 200,
|
|
335
|
+
body: {},
|
|
336
|
+
delay: 3e4
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
//#endregion
|
|
341
|
+
//#region src/stubs/kinds/openai.ts
|
|
342
|
+
const OPENAI_CHAT_URL = "https://api.openai.com/v1/chat/completions";
|
|
343
|
+
const OPENAI_RESPONSES_URL = "https://api.openai.com/v1/responses";
|
|
344
|
+
function matchesChatFilter(body, filter) {
|
|
345
|
+
const messages = fieldOf(body, "messages");
|
|
346
|
+
const names = namesOf(fieldOf(body, "tools"), (tool) => fieldOf(fieldOf(tool, "function"), "name"));
|
|
347
|
+
return matchesText(filter.model, textOf(fieldOf(body, "model"))) && matchesText(filter.system, messageText(messages, "system")) && matchesText(filter.user, messageText(messages, "user")) && (filter.tools?.every((tool) => names.includes(tool)) ?? true) && (filter.temperature === void 0 || fieldOf(body, "temperature") === filter.temperature);
|
|
348
|
+
}
|
|
349
|
+
function matchesResponsesFilter(body, filter) {
|
|
350
|
+
const input = fieldOf(body, "input");
|
|
351
|
+
const instructions = textOf(fieldOf(body, "instructions"));
|
|
352
|
+
const userText = listOf(input).filter((message) => fieldOf(message, "role") === "user").map((message) => {
|
|
353
|
+
const content = fieldOf(message, "content");
|
|
354
|
+
return typeof content === "string" ? content : JSON.stringify(content);
|
|
355
|
+
}).join(" ");
|
|
356
|
+
const names = namesOf(fieldOf(body, "tools"), (tool) => fieldOf(tool, "name") ?? fieldOf(fieldOf(tool, "function"), "name"));
|
|
357
|
+
return matchesText(filter.model, textOf(fieldOf(body, "model"))) && matchesText(filter.system, instructions === "" ? messageText(input, "system") : instructions) && matchesText(filter.user, userText) && (filter.tools?.every((tool) => names.includes(tool)) ?? true);
|
|
358
|
+
}
|
|
359
|
+
function buildChatReply(data) {
|
|
360
|
+
const content = typeof data === "string" ? data : JSON.stringify(data);
|
|
361
|
+
return {
|
|
362
|
+
status: 200,
|
|
363
|
+
body: {
|
|
364
|
+
id: "chatcmpl-test",
|
|
365
|
+
object: "chat.completion",
|
|
366
|
+
created: Math.floor(Date.now() / 1e3),
|
|
367
|
+
model: "gpt-4o-test",
|
|
368
|
+
choices: [{
|
|
369
|
+
index: 0,
|
|
370
|
+
message: {
|
|
371
|
+
role: "assistant",
|
|
372
|
+
content
|
|
373
|
+
},
|
|
374
|
+
finish_reason: "stop"
|
|
375
|
+
}],
|
|
376
|
+
usage: {
|
|
377
|
+
prompt_tokens: 10,
|
|
378
|
+
completion_tokens: 10,
|
|
379
|
+
total_tokens: 20
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
function responsesEnvelope(text, usage = DEFAULT_USAGE) {
|
|
385
|
+
return {
|
|
386
|
+
id: "resp-test",
|
|
387
|
+
object: "response",
|
|
388
|
+
created_at: Math.floor(Date.now() / 1e3),
|
|
389
|
+
model: "gpt-4o-test",
|
|
390
|
+
output: [{
|
|
391
|
+
type: "message",
|
|
392
|
+
id: "msg-test",
|
|
393
|
+
role: "assistant",
|
|
394
|
+
content: [{
|
|
395
|
+
type: "output_text",
|
|
396
|
+
text,
|
|
397
|
+
annotations: []
|
|
398
|
+
}]
|
|
399
|
+
}],
|
|
400
|
+
usage: {
|
|
401
|
+
input_tokens: usage.promptTokens,
|
|
402
|
+
output_tokens: usage.completionTokens,
|
|
403
|
+
total_tokens: usage.promptTokens + usage.completionTokens
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
function buildResponsesReply(data) {
|
|
408
|
+
return {
|
|
409
|
+
status: 200,
|
|
410
|
+
body: responsesEnvelope(typeof data === "string" ? data : JSON.stringify(data))
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
const DEFAULT_USAGE = {
|
|
414
|
+
completionTokens: 10,
|
|
415
|
+
promptTokens: 10
|
|
416
|
+
};
|
|
417
|
+
function chatStreamEvents(pieces, usage) {
|
|
418
|
+
const created = Math.floor(Date.now() / 1e3);
|
|
419
|
+
const chunk = (delta, finishReason) => ({ data: {
|
|
420
|
+
id: "chatcmpl-test",
|
|
421
|
+
object: "chat.completion.chunk",
|
|
422
|
+
created,
|
|
423
|
+
model: "gpt-4o-test",
|
|
424
|
+
choices: [{
|
|
425
|
+
index: 0,
|
|
426
|
+
delta,
|
|
427
|
+
logprobs: null,
|
|
428
|
+
finish_reason: finishReason
|
|
429
|
+
}]
|
|
430
|
+
} });
|
|
431
|
+
return [
|
|
432
|
+
chunk({
|
|
433
|
+
role: "assistant",
|
|
434
|
+
content: ""
|
|
435
|
+
}, null),
|
|
436
|
+
...pieces.map((piece) => chunk({ content: piece }, null)),
|
|
437
|
+
chunk({}, "stop"),
|
|
438
|
+
...usage === void 0 ? [] : [{ data: {
|
|
439
|
+
id: "chatcmpl-test",
|
|
440
|
+
object: "chat.completion.chunk",
|
|
441
|
+
created,
|
|
442
|
+
model: "gpt-4o-test",
|
|
443
|
+
choices: [],
|
|
444
|
+
usage: {
|
|
445
|
+
prompt_tokens: usage.promptTokens,
|
|
446
|
+
completion_tokens: usage.completionTokens,
|
|
447
|
+
total_tokens: usage.promptTokens + usage.completionTokens
|
|
448
|
+
}
|
|
449
|
+
} }],
|
|
450
|
+
{ data: "[DONE]" }
|
|
451
|
+
];
|
|
452
|
+
}
|
|
453
|
+
function responsesStreamEvents(pieces, usage) {
|
|
454
|
+
const text = pieces.join("");
|
|
455
|
+
const part = {
|
|
456
|
+
type: "output_text",
|
|
457
|
+
text,
|
|
458
|
+
annotations: []
|
|
459
|
+
};
|
|
460
|
+
const item = {
|
|
461
|
+
id: "msg-test",
|
|
462
|
+
type: "message",
|
|
463
|
+
role: "assistant"
|
|
464
|
+
};
|
|
465
|
+
const done = {
|
|
466
|
+
...item,
|
|
467
|
+
status: "completed",
|
|
468
|
+
content: [part]
|
|
469
|
+
};
|
|
470
|
+
const completed = {
|
|
471
|
+
...responsesEnvelope(text, usage),
|
|
472
|
+
status: "completed"
|
|
473
|
+
};
|
|
474
|
+
const at = {
|
|
475
|
+
item_id: "msg-test",
|
|
476
|
+
output_index: 0,
|
|
477
|
+
content_index: 0
|
|
478
|
+
};
|
|
479
|
+
return [
|
|
480
|
+
{
|
|
481
|
+
type: "response.created",
|
|
482
|
+
response: {
|
|
483
|
+
...completed,
|
|
484
|
+
status: "in_progress",
|
|
485
|
+
output: [],
|
|
486
|
+
usage: null
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
type: "response.output_item.added",
|
|
491
|
+
output_index: 0,
|
|
492
|
+
item: {
|
|
493
|
+
...item,
|
|
494
|
+
status: "in_progress",
|
|
495
|
+
content: []
|
|
496
|
+
}
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
type: "response.content_part.added",
|
|
500
|
+
...at,
|
|
501
|
+
part: {
|
|
502
|
+
...part,
|
|
503
|
+
text: ""
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
...pieces.map((delta) => ({
|
|
507
|
+
type: "response.output_text.delta",
|
|
508
|
+
...at,
|
|
509
|
+
delta
|
|
510
|
+
})),
|
|
511
|
+
{
|
|
512
|
+
type: "response.output_text.done",
|
|
513
|
+
...at,
|
|
514
|
+
text
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
type: "response.content_part.done",
|
|
518
|
+
...at,
|
|
519
|
+
part
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
type: "response.output_item.done",
|
|
523
|
+
output_index: 0,
|
|
524
|
+
item: done
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
type: "response.completed",
|
|
528
|
+
response: {
|
|
529
|
+
...completed,
|
|
530
|
+
output: [done]
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
].map((body, index) => ({
|
|
534
|
+
data: {
|
|
535
|
+
...body,
|
|
536
|
+
sequence_number: index
|
|
537
|
+
},
|
|
538
|
+
event: body.type
|
|
539
|
+
}));
|
|
540
|
+
}
|
|
541
|
+
/** OpenAI API stub helpers. */
|
|
542
|
+
const openai = {
|
|
543
|
+
/** Match Chat Completions calls, at `url` where the client reaches another host; a string filter is exact equality, a RegExp or `match.includes` loosens it. */
|
|
544
|
+
chat(filter, url) {
|
|
545
|
+
return {
|
|
546
|
+
adapter: "openai",
|
|
547
|
+
method: "POST",
|
|
548
|
+
url: url ?? OPENAI_CHAT_URL,
|
|
549
|
+
match: filter ? ({ body }) => matchesChatFilter(body, filter) : void 0,
|
|
550
|
+
wrap: buildChatReply
|
|
551
|
+
};
|
|
552
|
+
},
|
|
553
|
+
/** Match Responses API calls; a JSON file is wrapped in the Responses envelope. */
|
|
554
|
+
responses(filter, url) {
|
|
555
|
+
return {
|
|
556
|
+
adapter: "openai",
|
|
557
|
+
method: "POST",
|
|
558
|
+
url: url ?? OPENAI_RESPONSES_URL,
|
|
559
|
+
match: filter ? ({ body }) => matchesResponsesFilter(body, filter) : void 0,
|
|
560
|
+
wrap: buildResponsesReply
|
|
561
|
+
};
|
|
562
|
+
},
|
|
563
|
+
/** A stub the chain fails on if the subject reaches this endpoint: `http.never()`. */
|
|
564
|
+
never(request) {
|
|
565
|
+
return http.never(request);
|
|
566
|
+
},
|
|
567
|
+
/** Response: data wrapped in the Chat Completions format. */
|
|
568
|
+
reply(data) {
|
|
569
|
+
return buildChatReply(data);
|
|
570
|
+
},
|
|
571
|
+
/** Response: the text streamed as server-sent events; `api` picks Chat Completions (default) or Responses. */
|
|
572
|
+
stream(pieces, init) {
|
|
573
|
+
const events = init?.api === "responses" ? responsesStreamEvents(pieces, init.usage) : chatStreamEvents(pieces, init?.usage);
|
|
574
|
+
return http.sse(events, { delay: init?.delay ?? 0 });
|
|
575
|
+
},
|
|
576
|
+
/** Response: return an OpenAI error. */
|
|
577
|
+
error(status, message) {
|
|
578
|
+
return {
|
|
579
|
+
status,
|
|
580
|
+
body: { error: {
|
|
581
|
+
message: message ?? `OpenAI error (${status})`,
|
|
582
|
+
type: status === 429 ? "rate_limit_error" : "api_error",
|
|
583
|
+
code: status === 429 ? "rate_limit_exceeded" : null
|
|
584
|
+
} }
|
|
585
|
+
};
|
|
586
|
+
},
|
|
587
|
+
/** Response: return malformed content. */
|
|
588
|
+
malformed(content) {
|
|
589
|
+
return buildChatReply(content);
|
|
590
|
+
},
|
|
591
|
+
/** Response: simulate a timeout. */
|
|
592
|
+
timeout() {
|
|
593
|
+
return {
|
|
594
|
+
status: 200,
|
|
595
|
+
body: {},
|
|
596
|
+
delay: 3e4
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
//#endregion
|
|
601
|
+
//#region src/stubs/intercept/intercept.ts
|
|
602
|
+
function isOptions(value) {
|
|
603
|
+
return isRecord(value) && "origin" in value;
|
|
604
|
+
}
|
|
605
|
+
function resolveRelativeAgainst(origin) {
|
|
606
|
+
let base;
|
|
607
|
+
try {
|
|
608
|
+
base = new URL(origin);
|
|
609
|
+
} catch {
|
|
610
|
+
throw new Error(refusal({
|
|
611
|
+
fix: "state an absolute URL: intercept(stubs, { origin: 'http://console.test' })",
|
|
612
|
+
found: `intercept(): \`origin\` is \`${origin}\`, not an absolute URL`,
|
|
613
|
+
read: "docs/reference/stubs.md#intercept-the-same-double-with-no-chain",
|
|
614
|
+
why: "`origin` states where a relative request resolves, so it is an absolute URL."
|
|
615
|
+
}));
|
|
616
|
+
}
|
|
617
|
+
return () => {
|
|
618
|
+
const underlying = globalThis.fetch;
|
|
619
|
+
globalThis.fetch = async (input, init) => typeof input === "string" ? await underlying(new URL(input, base).toString(), init) : await underlying(input, init);
|
|
620
|
+
return () => {
|
|
621
|
+
globalThis.fetch = underlying;
|
|
622
|
+
};
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
function interceptThrough(register) {
|
|
626
|
+
async function intercept(requestOrStubs, maybeResponseOrOptions, maybeOptions) {
|
|
627
|
+
const options = isOptions(maybeOptions) ? maybeOptions : isOptions(maybeResponseOrOptions) ? maybeResponseOrOptions : void 0;
|
|
628
|
+
const stubs = declared("intercept", requestOrStubs, isOptions(maybeResponseOrOptions) ? void 0 : maybeResponseOrOptions);
|
|
629
|
+
if (stubs.length === 0) throw new Error(refusal({
|
|
630
|
+
fix: "declare at least one stub, or drop the intercept: a subject with no network needs none",
|
|
631
|
+
found: "intercept(), and the list of stubs is empty",
|
|
632
|
+
read: "docs/reference/stubs.md#intercept-the-same-double-with-no-chain",
|
|
633
|
+
why: "An empty list guards nothing."
|
|
634
|
+
}));
|
|
635
|
+
const installOrigin = options?.origin === void 0 ? void 0 : resolveRelativeAgainst(options.origin);
|
|
636
|
+
const registration = await register(stubs);
|
|
637
|
+
const restoreOrigin = installOrigin?.();
|
|
638
|
+
return { [Symbol.asyncDispose]: async () => {
|
|
639
|
+
restoreOrigin?.();
|
|
640
|
+
const violation = registration.violation();
|
|
641
|
+
registration.cleanup();
|
|
642
|
+
if (violation) throw violation;
|
|
643
|
+
} };
|
|
644
|
+
}
|
|
645
|
+
return intercept;
|
|
646
|
+
}
|
|
647
|
+
//#endregion
|
|
648
|
+
//#region src/stubs/delivery/same-process/intercept.ts
|
|
649
|
+
/** The module-scope network double, on msw's node server. */
|
|
650
|
+
const intercept = interceptThrough(async (stubs) => {
|
|
651
|
+
const { registerStubs } = await import("./shared-stub-server.js");
|
|
652
|
+
return await registerStubs(stubs);
|
|
653
|
+
});
|
|
654
|
+
//#endregion
|
|
655
|
+
export { anthropic, defineStub, defineStubs, http, intercept, openai };
|
|
656
|
+
|
|
657
|
+
//# sourceMappingURL=stubs.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/chain/paths/temp-root.global-setup.d.ts
|
|
2
|
+
/** Named by uid: a temp directory users share never holds a folder one of them cannot sweep. */
|
|
3
|
+
export declare const TEMP_ROOTS_PARENT: string;
|
|
4
|
+
export declare function tempRootSetup(): string;
|
|
5
|
+
export default function setup(): () => void;
|
|
6
|
+
//#endregion
|
|
7
|
+
//# sourceMappingURL=temp-root.global-setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { t as TEMP_ROOTS_PARENT, n as default, r as tempRootSetup } from "./shared-temp-root.global-setup.js";
|