@uncefact/untp-utils 0.2.0 → 0.4.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 +40 -12
- package/artefacts/context/untp/0.6.0/dcc.json +1241 -0
- package/artefacts/context/untp/0.6.0/dfr.json +619 -0
- package/artefacts/context/untp/0.6.0/dia.json +162 -0
- package/artefacts/context/untp/0.6.0/dpp.json +1078 -0
- package/artefacts/context/untp/0.6.0/dte.json +1285 -0
- package/artefacts/context/untp/0.6.1/dcc.json +1388 -0
- package/artefacts/context/untp/0.6.1/dfr.json +690 -0
- package/artefacts/context/untp/0.6.1/dia.json +196 -0
- package/artefacts/context/untp/0.6.1/dpp.json +1164 -0
- package/artefacts/context/untp/0.6.1/dte.json +1340 -0
- package/artefacts/context/untp/0.7.0/untp.json +3493 -0
- package/artefacts/context/vcdm/2/credentials.json +301 -0
- package/artefacts/manifest.json +214 -0
- package/artefacts/schema/untp/0.6.0/dcc.json +1131 -0
- package/artefacts/schema/untp/0.6.0/dfr.json +1028 -0
- package/artefacts/schema/untp/0.6.0/dia.json +236 -0
- package/artefacts/schema/untp/0.6.0/dpp.json +1219 -0
- package/artefacts/schema/untp/0.6.0/dte.json +845 -0
- package/artefacts/schema/untp/0.6.1/dcc.json +1196 -0
- package/artefacts/schema/untp/0.6.1/dfr.json +1037 -0
- package/artefacts/schema/untp/0.6.1/dia.json +236 -0
- package/artefacts/schema/untp/0.6.1/dpp.json +1229 -0
- package/artefacts/schema/untp/0.6.1/dte.json +845 -0
- package/artefacts/schema/untp/0.7.0/cvc.json +701 -0
- package/artefacts/schema/untp/0.7.0/dcc.json +1133 -0
- package/artefacts/schema/untp/0.7.0/dfr.json +1245 -0
- package/artefacts/schema/untp/0.7.0/dia.json +443 -0
- package/artefacts/schema/untp/0.7.0/dpp.json +1300 -0
- package/artefacts/schema/untp/0.7.0/dte.json +1226 -0
- package/artefacts/schema/untp/0.7.0/linkset.json +119 -0
- package/artefacts/schema/vcdm/2/verifiable-credential.json +318 -0
- package/build/artefacts/detect-version-from-context.d.ts +7 -2
- package/build/artefacts/detect-version-from-context.js +17 -6
- package/build/artefacts/detect-version-from-context.js.map +1 -1
- package/build/artefacts/detect-version-from-context.test.js +59 -0
- package/build/artefacts/detect-version-from-context.test.js.map +1 -1
- package/build/artefacts/urls.d.ts +8 -1
- package/build/artefacts/urls.js +17 -5
- package/build/artefacts/urls.js.map +1 -1
- package/build/artefacts/urls.test.js +63 -1
- package/build/artefacts/urls.test.js.map +1 -1
- package/build/bundle/artefacts/context/untp/0.6.0/dcc.d.ts +2 -0
- package/build/bundle/artefacts/context/untp/0.6.0/dcc.js +1244 -0
- package/build/bundle/artefacts/context/untp/0.6.0/dcc.js.map +1 -0
- package/build/bundle/artefacts/context/untp/0.6.0/dfr.d.ts +2 -0
- package/build/bundle/artefacts/context/untp/0.6.0/dfr.js +622 -0
- package/build/bundle/artefacts/context/untp/0.6.0/dfr.js.map +1 -0
- package/build/bundle/artefacts/context/untp/0.6.0/dia.d.ts +2 -0
- package/build/bundle/artefacts/context/untp/0.6.0/dia.js +165 -0
- package/build/bundle/artefacts/context/untp/0.6.0/dia.js.map +1 -0
- package/build/bundle/artefacts/context/untp/0.6.0/dpp.d.ts +2 -0
- package/build/bundle/artefacts/context/untp/0.6.0/dpp.js +1081 -0
- package/build/bundle/artefacts/context/untp/0.6.0/dpp.js.map +1 -0
- package/build/bundle/artefacts/context/untp/0.6.0/dte.d.ts +2 -0
- package/build/bundle/artefacts/context/untp/0.6.0/dte.js +1288 -0
- package/build/bundle/artefacts/context/untp/0.6.0/dte.js.map +1 -0
- package/build/bundle/artefacts/context/untp/0.6.1/dcc.d.ts +2 -0
- package/build/bundle/artefacts/context/untp/0.6.1/dcc.js +1391 -0
- package/build/bundle/artefacts/context/untp/0.6.1/dcc.js.map +1 -0
- package/build/bundle/artefacts/context/untp/0.6.1/dfr.d.ts +2 -0
- package/build/bundle/artefacts/context/untp/0.6.1/dfr.js +693 -0
- package/build/bundle/artefacts/context/untp/0.6.1/dfr.js.map +1 -0
- package/build/bundle/artefacts/context/untp/0.6.1/dia.d.ts +2 -0
- package/build/bundle/artefacts/context/untp/0.6.1/dia.js +199 -0
- package/build/bundle/artefacts/context/untp/0.6.1/dia.js.map +1 -0
- package/build/bundle/artefacts/context/untp/0.6.1/dpp.d.ts +2 -0
- package/build/bundle/artefacts/context/untp/0.6.1/dpp.js +1167 -0
- package/build/bundle/artefacts/context/untp/0.6.1/dpp.js.map +1 -0
- package/build/bundle/artefacts/context/untp/0.6.1/dte.d.ts +2 -0
- package/build/bundle/artefacts/context/untp/0.6.1/dte.js +1343 -0
- package/build/bundle/artefacts/context/untp/0.6.1/dte.js.map +1 -0
- package/build/bundle/artefacts/context/untp/0.7.0/untp.d.ts +2 -0
- package/build/bundle/artefacts/context/untp/0.7.0/untp.js +3496 -0
- package/build/bundle/artefacts/context/untp/0.7.0/untp.js.map +1 -0
- package/build/bundle/artefacts/context/vcdm/2/credentials.d.ts +2 -0
- package/build/bundle/artefacts/context/vcdm/2/credentials.js +304 -0
- package/build/bundle/artefacts/context/vcdm/2/credentials.js.map +1 -0
- package/build/bundle/artefacts/schema/untp/0.6.0/dcc.d.ts +2 -0
- package/build/bundle/artefacts/schema/untp/0.6.0/dcc.js +1134 -0
- package/build/bundle/artefacts/schema/untp/0.6.0/dcc.js.map +1 -0
- package/build/bundle/artefacts/schema/untp/0.6.0/dfr.d.ts +2 -0
- package/build/bundle/artefacts/schema/untp/0.6.0/dfr.js +1031 -0
- package/build/bundle/artefacts/schema/untp/0.6.0/dfr.js.map +1 -0
- package/build/bundle/artefacts/schema/untp/0.6.0/dia.d.ts +2 -0
- package/build/bundle/artefacts/schema/untp/0.6.0/dia.js +239 -0
- package/build/bundle/artefacts/schema/untp/0.6.0/dia.js.map +1 -0
- package/build/bundle/artefacts/schema/untp/0.6.0/dpp.d.ts +2 -0
- package/build/bundle/artefacts/schema/untp/0.6.0/dpp.js +1222 -0
- package/build/bundle/artefacts/schema/untp/0.6.0/dpp.js.map +1 -0
- package/build/bundle/artefacts/schema/untp/0.6.0/dte.d.ts +2 -0
- package/build/bundle/artefacts/schema/untp/0.6.0/dte.js +848 -0
- package/build/bundle/artefacts/schema/untp/0.6.0/dte.js.map +1 -0
- package/build/bundle/artefacts/schema/untp/0.6.1/dcc.d.ts +2 -0
- package/build/bundle/artefacts/schema/untp/0.6.1/dcc.js +1199 -0
- package/build/bundle/artefacts/schema/untp/0.6.1/dcc.js.map +1 -0
- package/build/bundle/artefacts/schema/untp/0.6.1/dfr.d.ts +2 -0
- package/build/bundle/artefacts/schema/untp/0.6.1/dfr.js +1040 -0
- package/build/bundle/artefacts/schema/untp/0.6.1/dfr.js.map +1 -0
- package/build/bundle/artefacts/schema/untp/0.6.1/dia.d.ts +2 -0
- package/build/bundle/artefacts/schema/untp/0.6.1/dia.js +239 -0
- package/build/bundle/artefacts/schema/untp/0.6.1/dia.js.map +1 -0
- package/build/bundle/artefacts/schema/untp/0.6.1/dpp.d.ts +2 -0
- package/build/bundle/artefacts/schema/untp/0.6.1/dpp.js +1232 -0
- package/build/bundle/artefacts/schema/untp/0.6.1/dpp.js.map +1 -0
- package/build/bundle/artefacts/schema/untp/0.6.1/dte.d.ts +2 -0
- package/build/bundle/artefacts/schema/untp/0.6.1/dte.js +848 -0
- package/build/bundle/artefacts/schema/untp/0.6.1/dte.js.map +1 -0
- package/build/bundle/artefacts/schema/untp/0.7.0/cvc.d.ts +2 -0
- package/build/bundle/artefacts/schema/untp/0.7.0/cvc.js +704 -0
- package/build/bundle/artefacts/schema/untp/0.7.0/cvc.js.map +1 -0
- package/build/bundle/artefacts/schema/untp/0.7.0/dcc.d.ts +2 -0
- package/build/bundle/artefacts/schema/untp/0.7.0/dcc.js +1136 -0
- package/build/bundle/artefacts/schema/untp/0.7.0/dcc.js.map +1 -0
- package/build/bundle/artefacts/schema/untp/0.7.0/dfr.d.ts +2 -0
- package/build/bundle/artefacts/schema/untp/0.7.0/dfr.js +1248 -0
- package/build/bundle/artefacts/schema/untp/0.7.0/dfr.js.map +1 -0
- package/build/bundle/artefacts/schema/untp/0.7.0/dia.d.ts +2 -0
- package/build/bundle/artefacts/schema/untp/0.7.0/dia.js +446 -0
- package/build/bundle/artefacts/schema/untp/0.7.0/dia.js.map +1 -0
- package/build/bundle/artefacts/schema/untp/0.7.0/dpp.d.ts +2 -0
- package/build/bundle/artefacts/schema/untp/0.7.0/dpp.js +1303 -0
- package/build/bundle/artefacts/schema/untp/0.7.0/dpp.js.map +1 -0
- package/build/bundle/artefacts/schema/untp/0.7.0/dte.d.ts +2 -0
- package/build/bundle/artefacts/schema/untp/0.7.0/dte.js +1229 -0
- package/build/bundle/artefacts/schema/untp/0.7.0/dte.js.map +1 -0
- package/build/bundle/artefacts/schema/untp/0.7.0/linkset.d.ts +2 -0
- package/build/bundle/artefacts/schema/untp/0.7.0/linkset.js +122 -0
- package/build/bundle/artefacts/schema/untp/0.7.0/linkset.js.map +1 -0
- package/build/bundle/artefacts/schema/vcdm/2/verifiable-credential.d.ts +2 -0
- package/build/bundle/artefacts/schema/vcdm/2/verifiable-credential.js +321 -0
- package/build/bundle/artefacts/schema/vcdm/2/verifiable-credential.js.map +1 -0
- package/build/bundle/fallback.d.ts +41 -0
- package/build/bundle/fallback.js +62 -0
- package/build/bundle/fallback.js.map +1 -0
- package/build/bundle/fallback.test.d.ts +1 -0
- package/build/bundle/fallback.test.js +124 -0
- package/build/bundle/fallback.test.js.map +1 -0
- package/build/bundle/index.d.ts +4 -0
- package/build/bundle/index.js +77 -0
- package/build/bundle/index.js.map +1 -0
- package/build/bundle/lookup.d.ts +14 -0
- package/build/bundle/lookup.js +40 -0
- package/build/bundle/lookup.js.map +1 -0
- package/build/bundle/lookup.test.d.ts +1 -0
- package/build/bundle/lookup.test.js +70 -0
- package/build/bundle/lookup.test.js.map +1 -0
- package/build/bundle/public.d.ts +29 -0
- package/build/bundle/public.js +60 -0
- package/build/bundle/public.js.map +1 -0
- package/build/bundle/public.test.d.ts +1 -0
- package/build/bundle/public.test.js +41 -0
- package/build/bundle/public.test.js.map +1 -0
- package/build/bundle/refresh-classify.test.d.ts +1 -0
- package/build/bundle/refresh-classify.test.js +20 -0
- package/build/bundle/refresh-classify.test.js.map +1 -0
- package/build/bundle/refresh-main.test.d.ts +1 -0
- package/build/bundle/refresh-main.test.js +98 -0
- package/build/bundle/refresh-main.test.js.map +1 -0
- package/build/common/as-date-time.d.ts +13 -0
- package/build/common/as-date-time.js +44 -0
- package/build/common/as-date-time.js.map +1 -0
- package/build/common/as-date-time.test.d.ts +1 -0
- package/build/common/as-date-time.test.js +42 -0
- package/build/common/as-date-time.test.js.map +1 -0
- package/build/common/as-non-empty-string.d.ts +11 -4
- package/build/common/as-non-empty-string.js +15 -5
- package/build/common/as-non-empty-string.js.map +1 -1
- package/build/common/as-non-empty-string.test.js +7 -2
- package/build/common/as-non-empty-string.test.js.map +1 -1
- package/build/common/canonical-json.d.ts +10 -0
- package/build/common/canonical-json.js +29 -0
- package/build/common/canonical-json.js.map +1 -0
- package/build/common/canonical-json.test.d.ts +1 -0
- package/build/common/canonical-json.test.js +20 -0
- package/build/common/canonical-json.test.js.map +1 -0
- package/build/common/index.d.ts +5 -0
- package/build/common/index.js +6 -0
- package/build/common/index.js.map +1 -0
- package/build/common/validity-window.d.ts +45 -0
- package/build/common/validity-window.js +68 -0
- package/build/common/validity-window.js.map +1 -0
- package/build/common/validity-window.test.d.ts +1 -0
- package/build/common/validity-window.test.js +67 -0
- package/build/common/validity-window.test.js.map +1 -0
- package/build/conformity-vocabulary/codes.d.ts +4 -0
- package/build/conformity-vocabulary/codes.js +4 -0
- package/build/conformity-vocabulary/codes.js.map +1 -1
- package/build/conformity-vocabulary/parse-conformity-scheme.test.js +150 -0
- package/build/conformity-vocabulary/parse-conformity-scheme.test.js.map +1 -1
- package/build/conformity-vocabulary/parsers/v0-7-0.parser.js +225 -0
- package/build/conformity-vocabulary/parsers/v0-7-0.parser.js.map +1 -1
- package/build/conformity-vocabulary/types.d.ts +69 -15
- package/build/conformity-vocabulary/validate-conformity-claim.d.ts +17 -11
- package/build/conformity-vocabulary/validate-conformity-claim.js +118 -13
- package/build/conformity-vocabulary/validate-conformity-claim.js.map +1 -1
- package/build/conformity-vocabulary/validate-conformity-claim.test.js +265 -0
- package/build/conformity-vocabulary/validate-conformity-claim.test.js.map +1 -1
- package/build/loaders/index.d.ts +2 -0
- package/build/loaders/index.js +1 -0
- package/build/loaders/index.js.map +1 -1
- package/build/loaders/jsonld-document-loader.d.ts +7 -5
- package/build/loaders/jsonld-document-loader.js +11 -2
- package/build/loaders/jsonld-document-loader.js.map +1 -1
- package/build/loaders/jsonld-document-loader.test.js +41 -1
- package/build/loaders/jsonld-document-loader.test.js.map +1 -1
- package/build/loaders/schema-loader.d.ts +13 -3
- package/build/loaders/schema-loader.js +24 -8
- package/build/loaders/schema-loader.js.map +1 -1
- package/build/loaders/schema-loader.test.js +56 -1
- package/build/loaders/schema-loader.test.js.map +1 -1
- package/build/node/errors.d.ts +9 -3
- package/build/node/errors.js +9 -3
- package/build/node/errors.js.map +1 -1
- package/build/node/index.d.ts +1 -1
- package/build/node/index.js +1 -1
- package/build/node/index.js.map +1 -1
- package/build/node/is-private-ip.d.ts +8 -4
- package/build/node/is-private-ip.fail-closed.test.d.ts +1 -0
- package/build/node/is-private-ip.fail-closed.test.js +36 -0
- package/build/node/is-private-ip.fail-closed.test.js.map +1 -0
- package/build/node/is-private-ip.js +77 -12
- package/build/node/is-private-ip.js.map +1 -1
- package/build/node/is-private-ip.test.js +28 -0
- package/build/node/is-private-ip.test.js.map +1 -1
- package/build/node/validate-public-url.d.ts +43 -11
- package/build/node/validate-public-url.js +68 -34
- package/build/node/validate-public-url.js.map +1 -1
- package/build/node/validate-public-url.test.js +161 -14
- package/build/node/validate-public-url.test.js.map +1 -1
- package/build/resolvers/errors.d.ts +36 -6
- package/build/resolvers/errors.js +41 -7
- package/build/resolvers/errors.js.map +1 -1
- package/build/resolvers/resolve-document-if-changed.test.js +7 -0
- package/build/resolvers/resolve-document-if-changed.test.js.map +1 -1
- package/build/resolvers/resolve-document.d.ts +29 -9
- package/build/resolvers/resolve-document.integration.test.d.ts +1 -0
- package/build/resolvers/resolve-document.integration.test.js +108 -0
- package/build/resolvers/resolve-document.integration.test.js.map +1 -0
- package/build/resolvers/resolve-document.js +129 -49
- package/build/resolvers/resolve-document.js.map +1 -1
- package/build/resolvers/resolve-document.test.js +462 -12
- package/build/resolvers/resolve-document.test.js.map +1 -1
- package/build/resolvers/resolve-json-document.d.ts +11 -8
- package/build/resolvers/resolve-json-document.js +7 -6
- package/build/resolvers/resolve-json-document.js.map +1 -1
- package/build/resolvers/resolve-json-document.test.js +21 -4
- package/build/resolvers/resolve-json-document.test.js.map +1 -1
- package/build/validation/describe-jsonld-failure.d.ts +48 -8
- package/build/validation/describe-jsonld-failure.js +94 -27
- package/build/validation/describe-jsonld-failure.js.map +1 -1
- package/build/validation/describe-jsonld-failure.test.js +98 -13
- package/build/validation/describe-jsonld-failure.test.js.map +1 -1
- package/build/validation/index.d.ts +3 -2
- package/build/validation/index.js +2 -2
- package/build/validation/index.js.map +1 -1
- package/build/validation/validate-jsonld.d.ts +29 -3
- package/build/validation/validate-jsonld.js +49 -22
- package/build/validation/validate-jsonld.js.map +1 -1
- package/build/validation/validate-jsonld.test.js +62 -5
- package/build/validation/validate-jsonld.test.js.map +1 -1
- package/package.json +23 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-document.js","sourceRoot":"","sources":["../../src/resolvers/resolve-document.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EACd,YAAY,EACZ,cAAc,EACd,kBAAkB,EAClB,kBAAkB,GAEnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,eAAe,EAA8C,MAAM,8BAA8B,CAAC;AAC3G,OAAO,EAAE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"resolve-document.js","sourceRoot":"","sources":["../../src/resolvers/resolve-document.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
import { jest } from '@jest/globals';
|
|
2
2
|
import { ReadableStream } from 'node:stream/web';
|
|
3
|
-
import {
|
|
3
|
+
import { MultibaseDigest } from '../multibase-digest/index.js';
|
|
4
|
+
import { PrivateAddressError, ResolutionFailedError } from '../node/index.js';
|
|
4
5
|
import { ResolverHttpError, ResolverNetworkError, ResolverRedirectMissingLocationError, ResolverTimedOutError, ResolverTooLargeError, ResolverTooManyRedirectsError, } from './errors.js';
|
|
5
6
|
const undiciFetch = jest.fn();
|
|
7
|
+
const NativeDOMException = globalThis.DOMException;
|
|
6
8
|
let lastAgentClose = jest.fn(() => Promise.resolve());
|
|
9
|
+
let agentConstructionError;
|
|
10
|
+
let agentCloseImplementation = () => Promise.resolve();
|
|
11
|
+
// Constructor options of every Agent created during a test, in creation
|
|
12
|
+
// order. The `connect.lookup` inside is the IP pin under test.
|
|
13
|
+
let agentOptions = [];
|
|
14
|
+
// The FakeAgent instances themselves, so tests can assert the dispatcher
|
|
15
|
+
// passed to fetch IS the pinned agent, not merely that a pinned agent exists.
|
|
16
|
+
let agentInstances = [];
|
|
7
17
|
class FakeAgent {
|
|
8
18
|
close;
|
|
9
|
-
constructor() {
|
|
10
|
-
|
|
19
|
+
constructor(options) {
|
|
20
|
+
if (agentConstructionError !== undefined)
|
|
21
|
+
throw agentConstructionError;
|
|
22
|
+
agentOptions.push(options);
|
|
23
|
+
agentInstances.push(this);
|
|
24
|
+
this.close = jest.fn(() => agentCloseImplementation());
|
|
11
25
|
lastAgentClose = this.close;
|
|
12
26
|
}
|
|
13
27
|
}
|
|
@@ -45,11 +59,20 @@ function makeResponse(opts) {
|
|
|
45
59
|
body: bodyStream,
|
|
46
60
|
};
|
|
47
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* The shape `validatePublicUrl` returns for a single validated address:
|
|
64
|
+
* `address` / `family` repeat the first entry of `addresses`, and the
|
|
65
|
+
* resolver hands the whole of `addresses` to the connector.
|
|
66
|
+
*/
|
|
48
67
|
function resolvedAddress(address = '1.1.1.1', family = 4) {
|
|
49
|
-
return { address, family };
|
|
68
|
+
return { address, family, addresses: [{ address, family }] };
|
|
50
69
|
}
|
|
51
70
|
describe('resolveDocument', () => {
|
|
52
71
|
beforeEach(() => {
|
|
72
|
+
agentOptions = [];
|
|
73
|
+
agentInstances = [];
|
|
74
|
+
agentConstructionError = undefined;
|
|
75
|
+
agentCloseImplementation = () => Promise.resolve();
|
|
53
76
|
undiciFetch.mockReset();
|
|
54
77
|
validatePublicUrl.mockReset();
|
|
55
78
|
});
|
|
@@ -61,6 +84,35 @@ describe('resolveDocument', () => {
|
|
|
61
84
|
});
|
|
62
85
|
});
|
|
63
86
|
describe('happy path', () => {
|
|
87
|
+
it('keeps the default DNS lookup path unchanged when no lookup is supplied', async () => {
|
|
88
|
+
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
89
|
+
undiciFetch.mockResolvedValue(makeResponse({ body: 'ok' }));
|
|
90
|
+
await resolveDocument('https://example.com/doc');
|
|
91
|
+
expect(validatePublicUrl).toHaveBeenCalledWith('https://example.com/doc', {
|
|
92
|
+
allowedSchemes: undefined,
|
|
93
|
+
allowPrivateAddresses: undefined,
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
it('forwards a supplied lookup to validation and pins its result into the connector', async () => {
|
|
97
|
+
const lookup = jest
|
|
98
|
+
.fn()
|
|
99
|
+
.mockResolvedValueOnce([{ address: '203.0.113.10', family: 4 }])
|
|
100
|
+
.mockResolvedValueOnce([{ address: '203.0.113.20', family: 4 }]);
|
|
101
|
+
validatePublicUrl.mockImplementation(async (_url, options) => {
|
|
102
|
+
const records = await options.lookup('example.com', { family: 0, all: true });
|
|
103
|
+
const [first] = records;
|
|
104
|
+
return { address: first.address, family: first.family, addresses: records };
|
|
105
|
+
});
|
|
106
|
+
undiciFetch.mockResolvedValue(makeResponse({ body: 'ok' }));
|
|
107
|
+
await resolveDocument('https://example.com/doc', { lookup });
|
|
108
|
+
expect(validatePublicUrl).toHaveBeenCalledWith('https://example.com/doc', expect.objectContaining({ lookup }));
|
|
109
|
+
const connectorLookup = agentOptions[0].connect.lookup;
|
|
110
|
+
const pinned = await new Promise((resolve, reject) => {
|
|
111
|
+
connectorLookup('example.com', {}, (error, addresses) => (error ? reject(error) : resolve(addresses)));
|
|
112
|
+
});
|
|
113
|
+
expect(pinned).toEqual([{ address: '203.0.113.10', family: 4 }]);
|
|
114
|
+
expect(lookup).toHaveBeenCalledTimes(1);
|
|
115
|
+
});
|
|
64
116
|
it('returns a LoadResult with body, digest, and the allowlisted headers', async () => {
|
|
65
117
|
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
66
118
|
undiciFetch.mockResolvedValue(makeResponse({
|
|
@@ -87,6 +139,17 @@ describe('resolveDocument', () => {
|
|
|
87
139
|
expect(valueAsRecord['x-internal-trace']).toBeUndefined();
|
|
88
140
|
});
|
|
89
141
|
});
|
|
142
|
+
describe('outbound headers', () => {
|
|
143
|
+
it('sends only Accept-free defaults with a User-Agent and no correlation header', async () => {
|
|
144
|
+
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
145
|
+
undiciFetch.mockResolvedValue(makeResponse({ body: 'ok' }));
|
|
146
|
+
await resolveDocument('https://example.com/doc');
|
|
147
|
+
const headers = undiciFetch.mock.calls[0][1].headers;
|
|
148
|
+
const names = Object.keys(headers).map((name) => name.toLowerCase());
|
|
149
|
+
expect(names).toContain('user-agent');
|
|
150
|
+
expect(names).not.toContain('x-correlation-id');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
90
153
|
describe('HTTP errors', () => {
|
|
91
154
|
it('throws ResolverHttpError for a 4xx response with status attached', async () => {
|
|
92
155
|
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
@@ -94,6 +157,16 @@ describe('resolveDocument', () => {
|
|
|
94
157
|
const error = (await resolveDocument('https://example.com/missing').catch((e) => e));
|
|
95
158
|
expect(error).toBeInstanceOf(ResolverHttpError);
|
|
96
159
|
expect(error.status).toBe(404);
|
|
160
|
+
expect(error.url).toBe('https://example.com/missing');
|
|
161
|
+
});
|
|
162
|
+
it('carries the failing hop URL, not the original, when a redirect ends in an HTTP error', async () => {
|
|
163
|
+
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
164
|
+
undiciFetch
|
|
165
|
+
.mockResolvedValueOnce(makeResponse({ status: 301, headers: { location: 'https://example.com/moved' }, body: null }))
|
|
166
|
+
.mockResolvedValueOnce(makeResponse({ status: 404, ok: false, body: 'gone' }));
|
|
167
|
+
const error = (await resolveDocument('https://example.com/start').catch((e) => e));
|
|
168
|
+
expect(error).toBeInstanceOf(ResolverHttpError);
|
|
169
|
+
expect(error.url).toBe('https://example.com/moved');
|
|
97
170
|
});
|
|
98
171
|
it('throws ResolverHttpError for a 5xx response', async () => {
|
|
99
172
|
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
@@ -111,13 +184,6 @@ describe('resolveDocument', () => {
|
|
|
111
184
|
expect(error).toBeInstanceOf(ResolverNetworkError);
|
|
112
185
|
expect(error.cause).toBeInstanceOf(Error);
|
|
113
186
|
});
|
|
114
|
-
it('throws ResolverTimedOutError when the fetch is aborted', async () => {
|
|
115
|
-
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
116
|
-
const abort = new Error('aborted');
|
|
117
|
-
abort.name = 'AbortError';
|
|
118
|
-
undiciFetch.mockRejectedValue(abort);
|
|
119
|
-
await expect(resolveDocument('https://example.com/')).rejects.toBeInstanceOf(ResolverTimedOutError);
|
|
120
|
-
});
|
|
121
187
|
it('throws ResolverNetworkError when the response body stream rejects mid-read', async () => {
|
|
122
188
|
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
123
189
|
const erroringStream = new ReadableStream({
|
|
@@ -133,6 +199,224 @@ describe('resolveDocument', () => {
|
|
|
133
199
|
});
|
|
134
200
|
await expect(resolveDocument('https://example.com/')).rejects.toBeInstanceOf(ResolverNetworkError);
|
|
135
201
|
});
|
|
202
|
+
// A foreign abort-shaped rejection, synthesised here rather than observed
|
|
203
|
+
// from undici: any abort that is not ours can arrive while our budget is
|
|
204
|
+
// still running. Classifying on the name alone would report it as "timed
|
|
205
|
+
// out after 10000ms" when our signal never fired. Fails if the deadline
|
|
206
|
+
// check is dropped.
|
|
207
|
+
it('reports an abort-named fetch rejection as a network error while our deadline has not passed', async () => {
|
|
208
|
+
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
209
|
+
const abort = new NativeDOMException('The operation was aborted.', 'AbortError');
|
|
210
|
+
undiciFetch.mockRejectedValue(abort);
|
|
211
|
+
const error = (await resolveDocument('https://example.com/').catch((e) => e));
|
|
212
|
+
expect(error).toBeInstanceOf(ResolverNetworkError);
|
|
213
|
+
expect(error.cause).toBe(abort);
|
|
214
|
+
});
|
|
215
|
+
// Same rule at the body-read await: an upstream body timeout is not our
|
|
216
|
+
// budget expiring. Fails if `readWithLimit` classifies on the name alone.
|
|
217
|
+
it('reports an abort-named body-read rejection as a network error while our deadline has not passed', async () => {
|
|
218
|
+
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
219
|
+
const abort = new NativeDOMException('The operation was aborted.', 'AbortError');
|
|
220
|
+
const erroringStream = new ReadableStream({
|
|
221
|
+
pull(controller) {
|
|
222
|
+
controller.error(abort);
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
undiciFetch.mockResolvedValue({
|
|
226
|
+
status: 200,
|
|
227
|
+
ok: true,
|
|
228
|
+
headers: new Headers(),
|
|
229
|
+
body: erroringStream,
|
|
230
|
+
});
|
|
231
|
+
const error = (await resolveDocument('https://example.com/').catch((e) => e));
|
|
232
|
+
expect(error).toBeInstanceOf(ResolverNetworkError);
|
|
233
|
+
expect(error.cause).toBe(abort);
|
|
234
|
+
});
|
|
235
|
+
it('reports an abort-named body-read rejection as a timeout once our deadline has passed', async () => {
|
|
236
|
+
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
237
|
+
const abort = new NativeDOMException('The operation was aborted.', 'AbortError');
|
|
238
|
+
const reader = {
|
|
239
|
+
read: jest.fn(() => new Promise((_resolve, reject) => setTimeout(() => reject(abort), 40))),
|
|
240
|
+
cancel: jest.fn(() => Promise.resolve()),
|
|
241
|
+
};
|
|
242
|
+
undiciFetch.mockResolvedValue({
|
|
243
|
+
status: 200,
|
|
244
|
+
ok: true,
|
|
245
|
+
headers: new Headers(),
|
|
246
|
+
body: { getReader: () => reader },
|
|
247
|
+
});
|
|
248
|
+
const error = (await resolveDocument('https://example.com/', { totalTimeoutMs: 10 }).catch((e) => e));
|
|
249
|
+
expect(error).toBeInstanceOf(ResolverTimedOutError);
|
|
250
|
+
expect(error.cause).toBe(abort);
|
|
251
|
+
});
|
|
252
|
+
it('times out while the first-hop guard is unresolved without creating an Agent or fetching', async () => {
|
|
253
|
+
let releaseGuard;
|
|
254
|
+
const guard = new Promise((resolve) => {
|
|
255
|
+
releaseGuard = resolve;
|
|
256
|
+
});
|
|
257
|
+
validatePublicUrl.mockReturnValue(guard);
|
|
258
|
+
const resolution = resolveDocument('https://slow-dns.example/', { totalTimeoutMs: 20 });
|
|
259
|
+
await expect(resolution).rejects.toBeInstanceOf(ResolverTimedOutError);
|
|
260
|
+
expect(agentInstances).toHaveLength(0);
|
|
261
|
+
expect(undiciFetch).not.toHaveBeenCalled();
|
|
262
|
+
releaseGuard(resolvedAddress());
|
|
263
|
+
await Promise.resolve();
|
|
264
|
+
});
|
|
265
|
+
it('does not report a late guard rejection as unhandled after timeout', async () => {
|
|
266
|
+
let rejectGuard;
|
|
267
|
+
const guard = new Promise((_resolve, reject) => {
|
|
268
|
+
rejectGuard = reject;
|
|
269
|
+
});
|
|
270
|
+
validatePublicUrl.mockReturnValue(guard);
|
|
271
|
+
await expect(resolveDocument('https://slow-dns.example/', { totalTimeoutMs: 20 })).rejects.toBeInstanceOf(ResolverTimedOutError);
|
|
272
|
+
const unhandledRejection = jest.fn();
|
|
273
|
+
process.on('unhandledRejection', unhandledRejection);
|
|
274
|
+
try {
|
|
275
|
+
rejectGuard(new Error('late DNS failure'));
|
|
276
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
277
|
+
}
|
|
278
|
+
finally {
|
|
279
|
+
process.off('unhandledRejection', unhandledRejection);
|
|
280
|
+
}
|
|
281
|
+
expect(unhandledRejection).not.toHaveBeenCalled();
|
|
282
|
+
});
|
|
283
|
+
it('keeps the on-time DNS rejection unchanged', async () => {
|
|
284
|
+
const dnsError = new ResolutionFailedError('example.com', new Error('ENOTFOUND'));
|
|
285
|
+
validatePublicUrl.mockRejectedValue(dnsError);
|
|
286
|
+
await expect(resolveDocument('https://example.com/')).rejects.toBe(dnsError);
|
|
287
|
+
expect(undiciFetch).not.toHaveBeenCalled();
|
|
288
|
+
});
|
|
289
|
+
// The paired case for the fetch await: the same abort-shaped rejection is
|
|
290
|
+
// our timeout once the signal has fired.
|
|
291
|
+
it('classifies an in-flight fetch abort as a timeout', async () => {
|
|
292
|
+
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
293
|
+
const abort = new NativeDOMException('The operation was aborted.', 'AbortError');
|
|
294
|
+
undiciFetch.mockImplementation((_url, rawInit) => {
|
|
295
|
+
const init = rawInit;
|
|
296
|
+
return new Promise((_resolve, reject) => {
|
|
297
|
+
init.signal.addEventListener('abort', () => reject(abort), { once: true });
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
const error = (await resolveDocument('https://example.com/', { totalTimeoutMs: 20 }).catch((e) => e));
|
|
301
|
+
expect(error).toBeInstanceOf(ResolverTimedOutError);
|
|
302
|
+
expect(error.cause).toBe(abort);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
describe('defect propagation', () => {
|
|
306
|
+
it('propagates a header-processing defect after a successful fetch unchanged', async () => {
|
|
307
|
+
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
308
|
+
const sentinel = new TypeError('header parser defect');
|
|
309
|
+
const headers = {
|
|
310
|
+
get: jest.fn(() => {
|
|
311
|
+
throw sentinel;
|
|
312
|
+
}),
|
|
313
|
+
};
|
|
314
|
+
undiciFetch.mockResolvedValue({ status: 200, ok: true, headers, body: null });
|
|
315
|
+
await expect(resolveDocument('https://example.com/')).rejects.toBe(sentinel);
|
|
316
|
+
expect(lastAgentClose).toHaveBeenCalled();
|
|
317
|
+
});
|
|
318
|
+
it('propagates an Agent construction defect unchanged', async () => {
|
|
319
|
+
const sentinel = new TypeError('agent construction defect');
|
|
320
|
+
agentConstructionError = sentinel;
|
|
321
|
+
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
322
|
+
await expect(resolveDocument('https://example.com/')).rejects.toBe(sentinel);
|
|
323
|
+
expect(undiciFetch).not.toHaveBeenCalled();
|
|
324
|
+
});
|
|
325
|
+
it('propagates a digest defect unchanged', async () => {
|
|
326
|
+
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
327
|
+
undiciFetch.mockResolvedValue(makeResponse({ body: 'ok' }));
|
|
328
|
+
const sentinel = new Error('digest defect');
|
|
329
|
+
const digestSpy = jest.spyOn(MultibaseDigest, 'fromData').mockRejectedValue(sentinel);
|
|
330
|
+
try {
|
|
331
|
+
await expect(resolveDocument('https://example.com/')).rejects.toBe(sentinel);
|
|
332
|
+
}
|
|
333
|
+
finally {
|
|
334
|
+
digestSpy.mockRestore();
|
|
335
|
+
}
|
|
336
|
+
expect(lastAgentClose).toHaveBeenCalled();
|
|
337
|
+
});
|
|
338
|
+
it('propagates a request-header construction defect before dispatch unchanged', async () => {
|
|
339
|
+
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
340
|
+
const sentinel = new TypeError('request header defect');
|
|
341
|
+
const headers = Object.defineProperty({}, 'User-Agent', {
|
|
342
|
+
enumerable: true,
|
|
343
|
+
get: () => {
|
|
344
|
+
throw sentinel;
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
await expect(resolveDocument('https://example.com/', { headers })).rejects.toBe(sentinel);
|
|
348
|
+
expect(undiciFetch).not.toHaveBeenCalled();
|
|
349
|
+
// The Agent is constructed before the headers are materialised, so the
|
|
350
|
+
// defect must still leave through the dispatcher's finally. Fails if the
|
|
351
|
+
// header work moves outside that try and leaks the Agent.
|
|
352
|
+
expect(lastAgentClose).toHaveBeenCalled();
|
|
353
|
+
});
|
|
354
|
+
// The sibling case above supplies an explicit `User-Agent`, which takes the
|
|
355
|
+
// early-return branch of withUserAgent. This one has no caller
|
|
356
|
+
// `User-Agent`, so the implicit branch spreads the caller's object to add
|
|
357
|
+
// the generated header, and must read the throwing `Accept` getter there.
|
|
358
|
+
// Fails if that branch stops materialising the caller's headers, which
|
|
359
|
+
// would defer the defect into the transport instead of raising it here.
|
|
360
|
+
it('propagates a request-header defect from the implicit User-Agent branch unchanged', async () => {
|
|
361
|
+
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
362
|
+
const sentinel = new TypeError('request header defect');
|
|
363
|
+
const headers = Object.defineProperty({}, 'Accept', {
|
|
364
|
+
enumerable: true,
|
|
365
|
+
get: () => {
|
|
366
|
+
throw sentinel;
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
await expect(resolveDocument('https://example.com/', { headers })).rejects.toBe(sentinel);
|
|
370
|
+
expect(undiciFetch).not.toHaveBeenCalled();
|
|
371
|
+
expect(lastAgentClose).toHaveBeenCalled();
|
|
372
|
+
});
|
|
373
|
+
it('does not classify an abort-named processing defect as a timeout', async () => {
|
|
374
|
+
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
375
|
+
const sentinel = new TypeError('processing defect');
|
|
376
|
+
sentinel.name = 'AbortError';
|
|
377
|
+
const headers = {
|
|
378
|
+
get: jest.fn(() => {
|
|
379
|
+
throw sentinel;
|
|
380
|
+
}),
|
|
381
|
+
};
|
|
382
|
+
undiciFetch.mockResolvedValue({ status: 200, ok: true, headers, body: null });
|
|
383
|
+
await expect(resolveDocument('https://example.com/')).rejects.toBe(sentinel);
|
|
384
|
+
});
|
|
385
|
+
it('propagates a reader acquisition defect unchanged', async () => {
|
|
386
|
+
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
387
|
+
const sentinel = new TypeError('reader acquisition defect');
|
|
388
|
+
undiciFetch.mockResolvedValue({
|
|
389
|
+
status: 200,
|
|
390
|
+
ok: true,
|
|
391
|
+
headers: new Headers(),
|
|
392
|
+
body: {
|
|
393
|
+
getReader: () => {
|
|
394
|
+
throw sentinel;
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
});
|
|
398
|
+
await expect(resolveDocument('https://example.com/')).rejects.toBe(sentinel);
|
|
399
|
+
});
|
|
400
|
+
it('propagates a chunk-processing defect unchanged', async () => {
|
|
401
|
+
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
402
|
+
const sentinel = new TypeError('chunk processing defect');
|
|
403
|
+
const value = Object.defineProperty({}, 'byteLength', {
|
|
404
|
+
get: () => {
|
|
405
|
+
throw sentinel;
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
const reader = { read: jest.fn(), cancel: jest.fn() };
|
|
409
|
+
reader.read.mockResolvedValue({ value, done: false });
|
|
410
|
+
reader.cancel.mockResolvedValue(undefined);
|
|
411
|
+
undiciFetch.mockResolvedValue({
|
|
412
|
+
status: 200,
|
|
413
|
+
ok: true,
|
|
414
|
+
headers: new Headers(),
|
|
415
|
+
body: { getReader: () => reader },
|
|
416
|
+
});
|
|
417
|
+
await expect(resolveDocument('https://example.com/')).rejects.toBe(sentinel);
|
|
418
|
+
expect(reader.cancel).toHaveBeenCalledTimes(1);
|
|
419
|
+
});
|
|
136
420
|
});
|
|
137
421
|
describe('size limit', () => {
|
|
138
422
|
it('throws ResolverTooLargeError with limit attached when the body exceeds maxResponseBytes', async () => {
|
|
@@ -143,6 +427,19 @@ describe('resolveDocument', () => {
|
|
|
143
427
|
expect(error).toBeInstanceOf(ResolverTooLargeError);
|
|
144
428
|
expect(error.limit).toBe(1024);
|
|
145
429
|
});
|
|
430
|
+
// The relaxed setting permits private destinations and nothing else. Fails
|
|
431
|
+
// if the size cap is bypassed or widened when allowPrivateAddresses is on.
|
|
432
|
+
it('applies the same size cap with allowPrivateAddresses on', async () => {
|
|
433
|
+
validatePublicUrl.mockResolvedValue(resolvedAddress('10.0.0.1'));
|
|
434
|
+
const big = new Uint8Array(2048);
|
|
435
|
+
undiciFetch.mockResolvedValue(makeResponse({ body: big }));
|
|
436
|
+
const error = (await resolveDocument('http://db.internal/big', {
|
|
437
|
+
maxResponseBytes: 1024,
|
|
438
|
+
allowPrivateAddresses: true,
|
|
439
|
+
}).catch((e) => e));
|
|
440
|
+
expect(error).toBeInstanceOf(ResolverTooLargeError);
|
|
441
|
+
expect(error.limit).toBe(1024);
|
|
442
|
+
});
|
|
146
443
|
});
|
|
147
444
|
describe('redirects', () => {
|
|
148
445
|
it('follows a redirect chain within the cap', async () => {
|
|
@@ -155,6 +452,75 @@ describe('resolveDocument', () => {
|
|
|
155
452
|
expect(new TextDecoder().decode(result.body)).toBe('final');
|
|
156
453
|
expect(undiciFetch).toHaveBeenCalledTimes(2);
|
|
157
454
|
});
|
|
455
|
+
it('pins each hop connection to the addresses validatePublicUrl resolved', async () => {
|
|
456
|
+
validatePublicUrl
|
|
457
|
+
.mockResolvedValueOnce(resolvedAddress('203.0.113.10', 4))
|
|
458
|
+
.mockResolvedValueOnce(resolvedAddress('2606:4700:4700::1111', 6));
|
|
459
|
+
undiciFetch
|
|
460
|
+
.mockResolvedValueOnce(makeResponse({ status: 301, headers: { location: 'https://example.com/next' }, body: null }))
|
|
461
|
+
.mockResolvedValueOnce(makeResponse({ body: 'final' }));
|
|
462
|
+
await resolveDocument('https://example.com/start');
|
|
463
|
+
expect(agentOptions).toHaveLength(2);
|
|
464
|
+
// Each hop's request must be dispatched through the agent that holds
|
|
465
|
+
// that hop's pin; constructing a pinned agent and fetching without it
|
|
466
|
+
// would satisfy the lookup assertions alone.
|
|
467
|
+
expect(undiciFetch.mock.calls[0][1]).toMatchObject({ dispatcher: agentInstances[0] });
|
|
468
|
+
expect(undiciFetch.mock.calls[1][1]).toMatchObject({ dispatcher: agentInstances[1] });
|
|
469
|
+
const pins = await Promise.all(agentOptions.map((options) => new Promise((resolve, reject) => {
|
|
470
|
+
const lookup = options.connect.lookup;
|
|
471
|
+
lookup('example.com', {}, (err, addresses) => (err ? reject(err) : resolve(addresses)));
|
|
472
|
+
})));
|
|
473
|
+
// Each hop's lookup must return exactly the addresses its own
|
|
474
|
+
// validatePublicUrl call resolved, never a fresh DNS answer.
|
|
475
|
+
expect(pins[0]).toEqual([{ address: '203.0.113.10', family: 4 }]);
|
|
476
|
+
expect(pins[1]).toEqual([{ address: '2606:4700:4700::1111', family: 6 }]);
|
|
477
|
+
});
|
|
478
|
+
// The dual-stack `localhost` case: the guard validated both addresses, so
|
|
479
|
+
// both must reach the connector, which is what lets Node's default
|
|
480
|
+
// autoSelectFamily try the second when the first refuses the connection.
|
|
481
|
+
it('hands the connector every address the guard validated for a hop', async () => {
|
|
482
|
+
validatePublicUrl.mockResolvedValueOnce({
|
|
483
|
+
address: '::1',
|
|
484
|
+
family: 6,
|
|
485
|
+
addresses: [
|
|
486
|
+
{ address: '::1', family: 6 },
|
|
487
|
+
{ address: '127.0.0.1', family: 4 },
|
|
488
|
+
],
|
|
489
|
+
});
|
|
490
|
+
undiciFetch.mockResolvedValueOnce(makeResponse({ body: 'final' }));
|
|
491
|
+
await resolveDocument('http://localhost/doc', { allowPrivateAddresses: true });
|
|
492
|
+
expect(agentOptions).toHaveLength(1);
|
|
493
|
+
expect(undiciFetch.mock.calls[0][1]).toMatchObject({ dispatcher: agentInstances[0] });
|
|
494
|
+
const pin = await new Promise((resolve, reject) => {
|
|
495
|
+
const lookup = agentOptions[0].connect.lookup;
|
|
496
|
+
lookup('localhost', {}, (err, addresses) => (err ? reject(err) : resolve(addresses)));
|
|
497
|
+
});
|
|
498
|
+
expect(pin).toEqual([
|
|
499
|
+
{ address: '::1', family: 6 },
|
|
500
|
+
{ address: '127.0.0.1', family: 4 },
|
|
501
|
+
]);
|
|
502
|
+
});
|
|
503
|
+
it('forwards private permission and pins each redirect hop through the guard', async () => {
|
|
504
|
+
validatePublicUrl
|
|
505
|
+
.mockResolvedValueOnce(resolvedAddress('10.0.0.1', 4))
|
|
506
|
+
.mockResolvedValueOnce(resolvedAddress('10.0.0.2', 4));
|
|
507
|
+
undiciFetch
|
|
508
|
+
.mockResolvedValueOnce(makeResponse({ status: 301, headers: { location: 'http://db.internal/next' }, body: null }))
|
|
509
|
+
.mockResolvedValueOnce(makeResponse({ body: 'final' }));
|
|
510
|
+
await resolveDocument('http://localhost/start', { allowPrivateAddresses: true });
|
|
511
|
+
expect(validatePublicUrl).toHaveBeenNthCalledWith(1, 'http://localhost/start', expect.objectContaining({ allowPrivateAddresses: true }));
|
|
512
|
+
expect(validatePublicUrl).toHaveBeenNthCalledWith(2, 'http://db.internal/next', expect.objectContaining({ allowPrivateAddresses: true }));
|
|
513
|
+
// Relaxed mode must dispatch through the pinned Agents too, exactly as
|
|
514
|
+
// the strict sibling above asserts. Constructing a pinned Agent and then
|
|
515
|
+
// fetching without it would satisfy the pin assertions below alone.
|
|
516
|
+
expect(undiciFetch.mock.calls[0][1]).toMatchObject({ dispatcher: agentInstances[0] });
|
|
517
|
+
expect(undiciFetch.mock.calls[1][1]).toMatchObject({ dispatcher: agentInstances[1] });
|
|
518
|
+
const pins = await Promise.all(agentOptions.map((options) => new Promise((resolve, reject) => {
|
|
519
|
+
const lookup = options.connect.lookup;
|
|
520
|
+
lookup('ignored.example', {}, (err, addresses) => err ? reject(err) : resolve(addresses));
|
|
521
|
+
})));
|
|
522
|
+
expect(pins).toEqual([[{ address: '10.0.0.1', family: 4 }], [{ address: '10.0.0.2', family: 4 }]]);
|
|
523
|
+
});
|
|
158
524
|
it('re-validates each redirect target through validatePublicUrl', async () => {
|
|
159
525
|
validatePublicUrl
|
|
160
526
|
.mockResolvedValueOnce(resolvedAddress('1.1.1.1'))
|
|
@@ -176,10 +542,94 @@ describe('resolveDocument', () => {
|
|
|
176
542
|
});
|
|
177
543
|
it('throws ResolverTooManyRedirectsError with limit attached when the chain exceeds the cap', async () => {
|
|
178
544
|
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
179
|
-
undiciFetch
|
|
545
|
+
undiciFetch
|
|
546
|
+
.mockResolvedValueOnce(makeResponse({ status: 301, headers: { location: 'https://example.com/loop' }, body: null }))
|
|
547
|
+
.mockResolvedValueOnce(makeResponse({ status: 301, headers: { location: 'https://example.com/final' }, body: null }));
|
|
180
548
|
const error = (await resolveDocument('https://example.com/start', { maxRedirects: 1 }).catch((e) => e));
|
|
181
549
|
expect(error).toBeInstanceOf(ResolverTooManyRedirectsError);
|
|
182
550
|
expect(error.limit).toBe(1);
|
|
551
|
+
expect(error.lastHopUrl).toBe('https://example.com/loop');
|
|
552
|
+
// The message names where the chain began and `.lastHopUrl` names who
|
|
553
|
+
// answered last. Fails if the two are transposed, which would report the hop that
|
|
554
|
+
// redirected as the start of the chain.
|
|
555
|
+
expect(error.message).toContain('starting from https://example.com/start');
|
|
556
|
+
expect(error.message).not.toContain('https://example.com/loop');
|
|
557
|
+
});
|
|
558
|
+
// Same cap, same responder, with the relaxed setting on. Fails if the hop
|
|
559
|
+
// cap is bypassed or overridden when allowPrivateAddresses is on.
|
|
560
|
+
it('applies the same redirect cap with allowPrivateAddresses on', async () => {
|
|
561
|
+
validatePublicUrl.mockResolvedValue(resolvedAddress('10.0.0.1'));
|
|
562
|
+
undiciFetch
|
|
563
|
+
.mockResolvedValueOnce(makeResponse({ status: 301, headers: { location: 'http://db.internal/loop' }, body: null }))
|
|
564
|
+
.mockResolvedValueOnce(makeResponse({ status: 301, headers: { location: 'http://db.internal/final' }, body: null }));
|
|
565
|
+
const error = (await resolveDocument('http://db.internal/start', {
|
|
566
|
+
maxRedirects: 1,
|
|
567
|
+
allowPrivateAddresses: true,
|
|
568
|
+
}).catch((e) => e));
|
|
569
|
+
expect(error).toBeInstanceOf(ResolverTooManyRedirectsError);
|
|
570
|
+
expect(error.limit).toBe(1);
|
|
571
|
+
expect(error.lastHopUrl).toBe('http://db.internal/loop');
|
|
572
|
+
expect(undiciFetch).toHaveBeenCalledTimes(2);
|
|
573
|
+
});
|
|
574
|
+
// R6: the disclosed default of three additional hops, exercised rather
|
|
575
|
+
// than restated as a constant. Four consecutive redirects must exhaust it,
|
|
576
|
+
// and the error must name the hop that answered the fourth request. Fails
|
|
577
|
+
// if the default changes or the responder is transposed.
|
|
578
|
+
it('exhausts the default redirect cap of three additional hops', async () => {
|
|
579
|
+
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
580
|
+
for (const location of [
|
|
581
|
+
'https://example.com/r1',
|
|
582
|
+
'https://example.com/r2',
|
|
583
|
+
'https://example.com/r3',
|
|
584
|
+
'https://example.com/r4',
|
|
585
|
+
]) {
|
|
586
|
+
undiciFetch.mockResolvedValueOnce(makeResponse({ status: 301, headers: { location }, body: null }));
|
|
587
|
+
}
|
|
588
|
+
const error = (await resolveDocument('https://example.com/start').catch((e) => e));
|
|
589
|
+
expect(error).toBeInstanceOf(ResolverTooManyRedirectsError);
|
|
590
|
+
expect(error.limit).toBe(3);
|
|
591
|
+
expect(undiciFetch).toHaveBeenCalledTimes(4);
|
|
592
|
+
expect(undiciFetch.mock.calls[3][0]).toBe('https://example.com/r3');
|
|
593
|
+
expect(error.lastHopUrl).toBe('https://example.com/r3');
|
|
594
|
+
});
|
|
595
|
+
// The post-loop throw is reachable only when the caller supplies a
|
|
596
|
+
// negative limit, so no hop was ever validated and there is no responder
|
|
597
|
+
// to name. Fails if that path is given a default `lastHopUrl`, which would
|
|
598
|
+
// claim a response that never happened, or if the loop runs at all.
|
|
599
|
+
it('throws ResolverTooManyRedirectsError with no lastHopUrl when the caller supplies a negative limit', async () => {
|
|
600
|
+
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
601
|
+
const error = (await resolveDocument('https://example.com/start', { maxRedirects: -1 }).catch((e) => e));
|
|
602
|
+
expect(error).toBeInstanceOf(ResolverTooManyRedirectsError);
|
|
603
|
+
expect(error.lastHopUrl).toBeUndefined();
|
|
604
|
+
expect(error.limit).toBe(-1);
|
|
605
|
+
expect(error.message).toContain('https://example.com/start');
|
|
606
|
+
expect(validatePublicUrl).not.toHaveBeenCalled();
|
|
607
|
+
expect(undiciFetch).not.toHaveBeenCalled();
|
|
608
|
+
});
|
|
609
|
+
it('does not call the second-hop guard after redirect cleanup consumes the timeout budget', async () => {
|
|
610
|
+
validatePublicUrl.mockResolvedValue(resolvedAddress());
|
|
611
|
+
undiciFetch.mockResolvedValue(makeResponse({ status: 301, headers: { location: 'https://example.com/next' }, body: null }));
|
|
612
|
+
agentCloseImplementation = () => new Promise((resolve) => setTimeout(resolve, 30));
|
|
613
|
+
await expect(resolveDocument('https://example.com/start', { totalTimeoutMs: 5 })).rejects.toBeInstanceOf(ResolverTimedOutError);
|
|
614
|
+
expect(validatePublicUrl).toHaveBeenCalledTimes(1);
|
|
615
|
+
expect(agentInstances).toHaveLength(1);
|
|
616
|
+
expect(undiciFetch).toHaveBeenCalledTimes(1);
|
|
617
|
+
});
|
|
618
|
+
it('uses the live redirect hop when a second-hop guard remains unresolved at timeout', async () => {
|
|
619
|
+
let releaseGuard;
|
|
620
|
+
const guard = new Promise((resolve) => {
|
|
621
|
+
releaseGuard = resolve;
|
|
622
|
+
});
|
|
623
|
+
validatePublicUrl.mockResolvedValueOnce(resolvedAddress()).mockReturnValueOnce(guard);
|
|
624
|
+
undiciFetch.mockResolvedValueOnce(makeResponse({ status: 301, headers: { location: 'https://example.com/next' }, body: null }));
|
|
625
|
+
const error = (await resolveDocument('https://example.com/start', { totalTimeoutMs: 20 }).catch((e) => e));
|
|
626
|
+
expect(error).toBeInstanceOf(ResolverTimedOutError);
|
|
627
|
+
expect(error.message).toContain('https://example.com/next');
|
|
628
|
+
expect(validatePublicUrl).toHaveBeenCalledTimes(2);
|
|
629
|
+
expect(agentInstances).toHaveLength(1);
|
|
630
|
+
expect(undiciFetch).toHaveBeenCalledTimes(1);
|
|
631
|
+
releaseGuard(resolvedAddress());
|
|
632
|
+
await Promise.resolve();
|
|
183
633
|
});
|
|
184
634
|
it('throws ResolverRedirectMissingLocationError when a 3xx has no Location', async () => {
|
|
185
635
|
validatePublicUrl.mockResolvedValue(resolvedAddress());
|