From 7c38c648fa93eda8d77f57192cf017c494ddf723 Mon Sep 17 00:00:00 2001 From: Kyzgor Date: Tue, 23 Jun 2026 22:48:01 +0100 Subject: [PATCH 1/7] refactor(deps): replace url-parse with native URL API url-parse was used only to build the OPA client base URL. Node's native WHATWG URL (available since v10; engines.node already requires >=10) does the same, so extract a buildOpaBaseUrl() helper and drop url-parse and @types/url-parse. yarn.lock is pruned of url-parse and its now-orphaned transitive dependencies (querystringify, requires-port) only; every other entry is left byte-for-byte unchanged. --- package.json | 5 ++--- src/enforcement/enforcer.ts | 28 ++++++++++++++++++++++------ yarn.lock | 23 ----------------------- 3 files changed, 24 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index c8efff71..4e2bad8a 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "fix:prettier": "prettier --config .prettierrc \"src/**/*.{ts,css,less,scss,js}\" --write", "fix:lint": "eslint src --ext .ts --fix", "test": "run-s test:*", + "test:unit": "run-s build && ava --verbose build/tests/enforcer.spec.js", "test:integration": "run-s build && ava --verbose build/tests/endpoints/**/*.spec.js", "test:module-imports": "run-s build && ava --verbose build/tests/module-imports/**/*.spec.js", "test:e2e:rbac": "run-s build && ava --verbose build/tests/e2e/rbac.e2e.spec.js", @@ -63,8 +64,7 @@ "path-to-regexp": "^6.2.1", "pino": "8.11.0", "pino-pretty": "10.2.0", - "require-in-the-middle": "^5.1.0", - "url-parse": "^1.5.10" + "require-in-the-middle": "^5.1.0" }, "devDependencies": { "@ava/typescript": "^1.1.1", @@ -74,7 +74,6 @@ "@types/express": "^4.17.9", "@types/lodash": "^4.14.166", "@types/node": "^14.14.14", - "@types/url-parse": "^1.4.11", "@typescript-eslint/eslint-plugin": "^4.0.1", "@typescript-eslint/parser": "^4.0.1", "ava": "^3.12.1", diff --git a/src/enforcement/enforcer.ts b/src/enforcement/enforcer.ts index cf1ec9fb..0c466724 100644 --- a/src/enforcement/enforcer.ts +++ b/src/enforcement/enforcer.ts @@ -1,6 +1,5 @@ import axios, { AxiosInstance } from 'axios'; import { Logger } from 'pino'; -import URL from 'url-parse'; import { IPermitConfig } from '../config'; import { CheckConfig, Context, ContextStore } from '../utils/context'; @@ -118,6 +117,25 @@ export interface IEnforcer { ): Promise; } +/** + * Builds the OPA client base URL from the configured PDP URL by forcing the OPA + * port (8181) and appending the OPA data path, using the native WHATWG `URL` + * (Node >= 10) in place of the previous `url-parse` dependency (#106). + * + * @param pdp - The configured PDP base URL (e.g. `http://localhost:7766`). + * @returns The OPA base URL (e.g. `http://localhost:8181/v1/data/permit/`). A PDP + * path with no trailing slash is glued to the data path (`/prefix` -> + * `/prefixv1/data/permit/`), preserved from `url-parse` and locked by a test. + * @throws {TypeError} on input without a scheme (e.g. `localhost`); a `host:port` + * value like `localhost:7766` is misparsed, not rejected — pass a full URL. + */ +export function buildOpaBaseUrl(pdp: string): string { + const opaBaseUrl = new URL(pdp); + opaBaseUrl.port = '8181'; + opaBaseUrl.pathname = `${opaBaseUrl.pathname}v1/data/permit/`; + return opaBaseUrl.toString(); +} + /** * The {@link Enforcer} class is responsible for performing permission checks against the PDP. * It implements the {@link IEnforcer} interface. @@ -133,9 +151,7 @@ export class Enforcer implements IEnforcer { * @param logger - The logger instance for logging. */ constructor(private config: IPermitConfig, private logger: Logger) { - const opaBaseUrl = new URL(this.config.pdp); - opaBaseUrl.set('port', '8181'); - opaBaseUrl.set('pathname', `${opaBaseUrl.pathname}v1/data/permit/`); + const opaBaseUrl = buildOpaBaseUrl(this.config.pdp); const version = process.env.npm_package_version ?? 'unknown'; if (config.axiosInstance) { this.client = config.axiosInstance; @@ -151,11 +167,11 @@ export class Enforcer implements IEnforcer { } if (config.opaAxiosInstance) { this.opaClient = config.opaAxiosInstance; - this.opaClient.defaults.baseURL = opaBaseUrl.toString(); + this.opaClient.defaults.baseURL = opaBaseUrl; this.opaClient.defaults.headers.common['X-Permit-SDK-Version'] = `node:${version}`; } else { this.opaClient = axios.create({ - baseURL: opaBaseUrl.toString(), + baseURL: opaBaseUrl, headers: { 'X-Permit-SDK-Version': `node:${version}`, }, diff --git a/yarn.lock b/yarn.lock index bf3d8fb3..9ddb0935 100644 --- a/yarn.lock +++ b/yarn.lock @@ -807,11 +807,6 @@ "@types/mime" "*" "@types/node" "*" -"@types/url-parse@^1.4.11": - version "1.4.11" - resolved "https://registry.npmjs.org/@types/url-parse/-/url-parse-1.4.11.tgz" - integrity sha512-FKvKIqRaykZtd4n47LbK/W/5fhQQ1X7cxxzG9A48h0BGN+S04NH7ervcCjM8tyR0lyGru83FAHSmw2ObgKoESg== - "@typescript-eslint/eslint-plugin@^4.0.1": version "4.33.0" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz" @@ -5311,11 +5306,6 @@ q@^1.5.1: resolved "https://registry.npmjs.org/q/-/q-1.5.1.tgz" integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== -querystringify@^2.1.1: - version "2.2.0" - resolved "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz" - integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== - queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" @@ -5527,11 +5517,6 @@ require-main-filename@^2.0.0: resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" @@ -6699,14 +6684,6 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -url-parse@^1.5.10: - version "1.5.10" - resolved "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz" - integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - urlgrey@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/urlgrey/-/urlgrey-1.0.0.tgz" From 575f1b3029bd36fff4dcd8bce0dc6bcb51113e4c Mon Sep 17 00:00:00 2001 From: Kyzgor Date: Tue, 23 Jun 2026 22:48:03 +0100 Subject: [PATCH 2/7] test(enforcer): cover OPA base URL construction and wiring Lock the exact OPA base URL produced for the default PDP, trailing-slash, explicit-port, https, and path-prefix inputs so the url-parse -> native URL refactor is proven behaviour-equivalent on valid input and any regression fails here; assert a scheme-less PDP (bare host or //host:port) throws; and assert the Enforcer wires the OPA client baseURL to buildOpaBaseUrl(pdp). --- src/tests/enforcer.spec.ts | 57 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/tests/enforcer.spec.ts diff --git a/src/tests/enforcer.spec.ts b/src/tests/enforcer.spec.ts new file mode 100644 index 00000000..62780c8b --- /dev/null +++ b/src/tests/enforcer.spec.ts @@ -0,0 +1,57 @@ +import test from 'ava'; +import axios from 'axios'; + +import { buildOpaBaseUrl } from '../enforcement/enforcer'; +import { Permit } from '../index'; + +// The OPA client base URL is derived from the configured PDP URL by forcing the +// OPA port (8181) and appending the OPA data path. This was previously built +// with the `url-parse` package and is now built with the native WHATWG `URL` +// (#106). These assertions lock the produced string so the refactor is proven +// behaviour-equivalent and any regression in the construction logic fails here. + +test('buildOpaBaseUrl: default PDP', (t) => { + t.is(buildOpaBaseUrl('http://localhost:7766'), 'http://localhost:8181/v1/data/permit/'); +}); + +test('buildOpaBaseUrl: trailing slash yields the same URL as no trailing slash', (t) => { + t.is(buildOpaBaseUrl('http://localhost:7766/'), 'http://localhost:8181/v1/data/permit/'); +}); + +test('buildOpaBaseUrl: https host without an explicit port', (t) => { + t.is(buildOpaBaseUrl('https://pdp.example.com'), 'https://pdp.example.com:8181/v1/data/permit/'); +}); + +test('buildOpaBaseUrl: an existing port is overridden with 8181', (t) => { + t.is( + buildOpaBaseUrl('https://pdp.example.com:1234'), + 'https://pdp.example.com:8181/v1/data/permit/', + ); +}); + +test('buildOpaBaseUrl: a path-prefixed PDP preserves the pre-existing concatenation behaviour', (t) => { + // Pre-existing url-parse quirk: a path with no trailing slash glues onto the data path. + t.is( + buildOpaBaseUrl('http://localhost:7766/prefix'), + 'http://localhost:8181/prefixv1/data/permit/', + ); + t.is( + buildOpaBaseUrl('http://localhost:7766/prefix/'), + 'http://localhost:8181/prefix/v1/data/permit/', + ); +}); + +test('buildOpaBaseUrl: a PDP without a scheme throws (invalid absolute URL)', (t) => { + // Scheme-less input (bare host or `//host:port`) throws at construction; `localhost:7766` is misparsed, not rejected. + t.throws(() => buildOpaBaseUrl('localhost'), { instanceOf: TypeError }); + t.throws(() => buildOpaBaseUrl('//localhost:7766'), { instanceOf: TypeError }); +}); + +test('Enforcer wires the OPA client base URL to buildOpaBaseUrl(pdp)', (t) => { + // Guards the integration the refactor actually edits: the constructed OPA axios + // client must take its baseURL from the helper. Passing an opaAxiosInstance lets + // us read what the Enforcer set, without a live PDP or network call. + const opaAxiosInstance = axios.create(); + new Permit({ token: 'test-token', pdp: 'http://localhost:7766', opaAxiosInstance }); + t.is(opaAxiosInstance.defaults.baseURL, buildOpaBaseUrl('http://localhost:7766')); +}); From b56a9b2c9339feb0769f826528dfef6215cbb267 Mon Sep 17 00:00:00 2001 From: Zeev Manilovich Date: Tue, 29 Sep 2026 23:04:46 +0300 Subject: [PATCH 3/7] test(enforcer): move enforcer spec under tests/unit Main's test:unit glob (build/tests/unit/**/*.spec.js) now picks up the OPA base URL tests, so the script matches main again and both the retry and enforcer suites run from one place. Co-Authored-By: Claude Opus 5.5 --- package.json | 2 +- src/tests/{ => unit}/enforcer.spec.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/tests/{ => unit}/enforcer.spec.ts (96%) diff --git a/package.json b/package.json index e8bd5859..f133d69d 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "fix:prettier": "prettier --config .prettierrc \"src/**/*.{ts,css,less,scss,js}\" --write", "fix:lint": "eslint src --ext .ts --fix", "test": "run-s test:*", - "test:unit": "run-s build && ava --verbose 'build/tests/unit/**/*.spec.js' build/tests/enforcer.spec.js", + "test:unit": "run-s build && ava --verbose 'build/tests/unit/**/*.spec.js'", "test:integration": "run-s build && ava --verbose build/tests/endpoints/**/*.spec.js", "test:module-imports": "run-s build && ava --verbose build/tests/module-imports/**/*.spec.js", "test:e2e:rbac": "run-s build && ava --verbose build/tests/e2e/rbac.e2e.spec.js", diff --git a/src/tests/enforcer.spec.ts b/src/tests/unit/enforcer.spec.ts similarity index 96% rename from src/tests/enforcer.spec.ts rename to src/tests/unit/enforcer.spec.ts index 62780c8b..5aef0d46 100644 --- a/src/tests/enforcer.spec.ts +++ b/src/tests/unit/enforcer.spec.ts @@ -1,8 +1,8 @@ import test from 'ava'; import axios from 'axios'; -import { buildOpaBaseUrl } from '../enforcement/enforcer'; -import { Permit } from '../index'; +import { buildOpaBaseUrl } from '../../enforcement/enforcer'; +import { Permit } from '../../index'; // The OPA client base URL is derived from the configured PDP URL by forcing the // OPA port (8181) and appending the OPA data path. This was previously built From e5abd86c65ba6dde1608a1003dd93bf53f7a6596 Mon Sep 17 00:00:00 2001 From: Zeev Manilovich Date: Tue, 29 Sep 2026 23:06:09 +0300 Subject: [PATCH 4/7] fix(enforcer): throw PermitError without the value on invalid PDP URL new URL() throws a TypeError whose enumerable input property holds the whole PDP URL, so a caller logging the new Permit() failure with pino wrote any user:password in the URL to its logs. url-parse never threw here, so this path is new with the native URL switch. buildOpaBaseUrl now throws a PermitError that names the pdp option and the expected format, and does not carry the configured value. Co-Authored-By: Claude Opus 5.5 --- src/enforcement/enforcer.ts | 15 ++++++++++++--- src/tests/unit/enforcer.spec.ts | 22 ++++++++++++++++++---- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/enforcement/enforcer.ts b/src/enforcement/enforcer.ts index 8114db9a..ed2783bf 100644 --- a/src/enforcement/enforcer.ts +++ b/src/enforcement/enforcer.ts @@ -128,11 +128,20 @@ export interface IEnforcer { * @returns The OPA base URL (e.g. `http://localhost:8181/v1/data/permit/`). A PDP * path with no trailing slash is glued to the data path (`/prefix` -> * `/prefixv1/data/permit/`), preserved from `url-parse` and locked by a test. - * @throws {TypeError} on input without a scheme (e.g. `localhost`); a `host:port` - * value like `localhost:7766` is misparsed, not rejected — pass a full URL. + * @throws {PermitError} on input without a scheme (e.g. `localhost`); a `host:port` + * value like `localhost:7766` is misparsed, not rejected — pass a full URL. The + * error omits the configured value because it may carry credentials. */ export function buildOpaBaseUrl(pdp: string): string { - const opaBaseUrl = new URL(pdp); + let opaBaseUrl: URL; + try { + opaBaseUrl = new URL(pdp); + } catch { + throw new PermitError( + 'Invalid PDP URL in the "pdp" option: expected an absolute http(s) URL, ' + + 'e.g. "http://localhost:7766".', + ); + } opaBaseUrl.port = '8181'; opaBaseUrl.pathname = `${opaBaseUrl.pathname}v1/data/permit/`; return opaBaseUrl.toString(); diff --git a/src/tests/unit/enforcer.spec.ts b/src/tests/unit/enforcer.spec.ts index 5aef0d46..71f18313 100644 --- a/src/tests/unit/enforcer.spec.ts +++ b/src/tests/unit/enforcer.spec.ts @@ -1,8 +1,9 @@ import test from 'ava'; import axios from 'axios'; +import pino from 'pino'; import { buildOpaBaseUrl } from '../../enforcement/enforcer'; -import { Permit } from '../../index'; +import { Permit, PermitError } from '../../index'; // The OPA client base URL is derived from the configured PDP URL by forcing the // OPA port (8181) and appending the OPA data path. This was previously built @@ -41,10 +42,23 @@ test('buildOpaBaseUrl: a path-prefixed PDP preserves the pre-existing concatenat ); }); -test('buildOpaBaseUrl: a PDP without a scheme throws (invalid absolute URL)', (t) => { +test('buildOpaBaseUrl: a PDP without a scheme throws a PermitError naming the pdp option', (t) => { // Scheme-less input (bare host or `//host:port`) throws at construction; `localhost:7766` is misparsed, not rejected. - t.throws(() => buildOpaBaseUrl('localhost'), { instanceOf: TypeError }); - t.throws(() => buildOpaBaseUrl('//localhost:7766'), { instanceOf: TypeError }); + for (const pdp of ['localhost', '//localhost:7766']) { + t.throws(() => buildOpaBaseUrl(pdp), { + instanceOf: PermitError, + message: /"pdp" option.*absolute http\(s\) URL/, + }); + } +}); + +test('new Permit with an invalid credential-bearing PDP URL does not expose the credentials', (t) => { + const error = t.throws( + () => new Permit({ token: 'test-token', pdp: 'http://pdp-user:pdp-secret@localhost:bad' }), + { instanceOf: PermitError }, + ); + // Serialize the way the SDK's pino logger would, so enumerable error fields are covered too. + t.false(JSON.stringify(pino.stdSerializers.err(error)).includes('pdp-secret')); }); test('Enforcer wires the OPA client base URL to buildOpaBaseUrl(pdp)', (t) => { From a9d4dab367b7b563f6d50d1031254b172d4df5ad Mon Sep 17 00:00:00 2001 From: Zeev Manilovich Date: Tue, 29 Sep 2026 23:07:38 +0300 Subject: [PATCH 5/7] test(enforcer): assert useOpa check URL for both OPA client paths The only wiring test used the default PDP and read back the baseURL of an injected instance, so a constructor that ignored config.pdp, or an SDK-created OPA client that ignored the derived base URL, still passed. Run a useOpa check against a non-default https PDP with a port and path through both the SDK-created client and an injected opaAxiosInstance, and assert the request URL at the adapter boundary. Co-Authored-By: Claude Opus 5.5 --- src/tests/unit/enforcer.spec.ts | 43 +++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/src/tests/unit/enforcer.spec.ts b/src/tests/unit/enforcer.spec.ts index 71f18313..84955d13 100644 --- a/src/tests/unit/enforcer.spec.ts +++ b/src/tests/unit/enforcer.spec.ts @@ -1,5 +1,5 @@ import test from 'ava'; -import axios from 'axios'; +import axios, { AxiosInstance, InternalAxiosRequestConfig } from 'axios'; import pino from 'pino'; import { buildOpaBaseUrl } from '../../enforcement/enforcer'; @@ -61,11 +61,40 @@ test('new Permit with an invalid credential-bearing PDP URL does not expose the t.false(JSON.stringify(pino.stdSerializers.err(error)).includes('pdp-secret')); }); -test('Enforcer wires the OPA client base URL to buildOpaBaseUrl(pdp)', (t) => { - // Guards the integration the refactor actually edits: the constructed OPA axios - // client must take its baseURL from the helper. Passing an opaAxiosInstance lets - // us read what the Enforcer set, without a live PDP or network call. +// A non-default scheme, port and path, so a constructor that ignores the configured +// PDP (or an OPA client that ignores the derived base URL) fails the tests below. +const CONFIGURED_PDP = 'https://pdp.example.com:1234/prefix/'; +const EXPECTED_OPA_CHECK_URL = 'https://pdp.example.com:8181/prefix/v1/data/permit/root'; + +// Reaches the SDK-created OPA client, as retry-interceptor.spec.ts does for enforcer.client. +interface PermitInternals { + enforcer: { opaClient: AxiosInstance }; +} + +// Records the URL axios would request and answers with an OPA allow decision, so the +// check runs end to end without a network call. +function captureRequestUrls(instance: AxiosInstance): string[] { + const urls: string[] = []; + instance.defaults.adapter = async (config: InternalAxiosRequestConfig) => { + urls.push(axios.getUri(config)); + return { status: 200, statusText: 'OK', headers: {}, config, data: { allow: true } }; + }; + return urls; +} + +test('a useOpa check posts to the OPA root derived from the configured PDP', async (t) => { + const permit = new Permit({ token: 'test-token', pdp: CONFIGURED_PDP }); + const urls = captureRequestUrls((permit as unknown as PermitInternals).enforcer.opaClient); + + t.true(await permit.check('user', 'read', 'document', {}, { useOpa: true })); + t.deepEqual(urls, [EXPECTED_OPA_CHECK_URL]); +}); + +test('a useOpa check through an injected opaAxiosInstance posts to the same OPA root', async (t) => { const opaAxiosInstance = axios.create(); - new Permit({ token: 'test-token', pdp: 'http://localhost:7766', opaAxiosInstance }); - t.is(opaAxiosInstance.defaults.baseURL, buildOpaBaseUrl('http://localhost:7766')); + const urls = captureRequestUrls(opaAxiosInstance); + const permit = new Permit({ token: 'test-token', pdp: CONFIGURED_PDP, opaAxiosInstance }); + + t.true(await permit.check('user', 'read', 'document', {}, { useOpa: true })); + t.deepEqual(urls, [EXPECTED_OPA_CHECK_URL]); }); From 8cc5474388d3e546408e5c656e00523622f8ea57 Mon Sep 17 00:00:00 2001 From: Zeev Manilovich Date: Tue, 29 Sep 2026 23:08:08 +0300 Subject: [PATCH 6/7] test(enforcer): scope the url-parse equivalence claim The spec header said the native URL refactor was proven equivalent to url-parse. That holds for canonical absolute http(s) PDP URLs only: the WHATWG parser resolves dot segments, so https://host/a/.. now yields /v1/data/permit/ instead of /a/..v1/data/permit/. Narrow the comment and pin the dot-segment behaviour with a test. Co-Authored-By: Claude Opus 5.5 --- src/tests/unit/enforcer.spec.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/tests/unit/enforcer.spec.ts b/src/tests/unit/enforcer.spec.ts index 84955d13..bf6a9b63 100644 --- a/src/tests/unit/enforcer.spec.ts +++ b/src/tests/unit/enforcer.spec.ts @@ -8,8 +8,10 @@ import { Permit, PermitError } from '../../index'; // The OPA client base URL is derived from the configured PDP URL by forcing the // OPA port (8181) and appending the OPA data path. This was previously built // with the `url-parse` package and is now built with the native WHATWG `URL` -// (#106). These assertions lock the produced string so the refactor is proven -// behaviour-equivalent and any regression in the construction logic fails here. +// (#106). For absolute http(s) PDP URLs in canonical form the result matches what +// url-parse produced, and these assertions lock that string. WHATWG parsing also +// normalizes input that url-parse kept verbatim (dot segments, percent-encoding, +// IDN hosts); the dot-segment test below pins that intended difference. test('buildOpaBaseUrl: default PDP', (t) => { t.is(buildOpaBaseUrl('http://localhost:7766'), 'http://localhost:8181/v1/data/permit/'); @@ -42,6 +44,14 @@ test('buildOpaBaseUrl: a path-prefixed PDP preserves the pre-existing concatenat ); }); +test('buildOpaBaseUrl: dot segments in the PDP path are resolved before the data path', (t) => { + // url-parse kept them verbatim and produced `/a/..v1/data/permit/`. + t.is( + buildOpaBaseUrl('https://pdp.example.com/a/..'), + 'https://pdp.example.com:8181/v1/data/permit/', + ); +}); + test('buildOpaBaseUrl: a PDP without a scheme throws a PermitError naming the pdp option', (t) => { // Scheme-less input (bare host or `//host:port`) throws at construction; `localhost:7766` is misparsed, not rejected. for (const pdp of ['localhost', '//localhost:7766']) { From 362b660f9885b6a80e92529924446d455c2c9d54 Mon Sep 17 00:00:00 2001 From: Zeev Manilovich Date: Tue, 29 Sep 2026 23:40:43 +0300 Subject: [PATCH 7/7] Keep enforcer test lines within the 100-character limit Co-Authored-By: Codex Co-Authored-By: Claude Opus 5.5 --- src/tests/unit/enforcer.spec.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/tests/unit/enforcer.spec.ts b/src/tests/unit/enforcer.spec.ts index bf6a9b63..3b64a1de 100644 --- a/src/tests/unit/enforcer.spec.ts +++ b/src/tests/unit/enforcer.spec.ts @@ -32,7 +32,7 @@ test('buildOpaBaseUrl: an existing port is overridden with 8181', (t) => { ); }); -test('buildOpaBaseUrl: a path-prefixed PDP preserves the pre-existing concatenation behaviour', (t) => { +test('buildOpaBaseUrl: a path prefix preserves the existing concatenation behaviour', (t) => { // Pre-existing url-parse quirk: a path with no trailing slash glues onto the data path. t.is( buildOpaBaseUrl('http://localhost:7766/prefix'), @@ -53,7 +53,8 @@ test('buildOpaBaseUrl: dot segments in the PDP path are resolved before the data }); test('buildOpaBaseUrl: a PDP without a scheme throws a PermitError naming the pdp option', (t) => { - // Scheme-less input (bare host or `//host:port`) throws at construction; `localhost:7766` is misparsed, not rejected. + // Bare hosts and `//host:port` throw at construction; `localhost:7766` is misparsed, + // not rejected. for (const pdp of ['localhost', '//localhost:7766']) { t.throws(() => buildOpaBaseUrl(pdp), { instanceOf: PermitError, @@ -62,7 +63,7 @@ test('buildOpaBaseUrl: a PDP without a scheme throws a PermitError naming the pd } }); -test('new Permit with an invalid credential-bearing PDP URL does not expose the credentials', (t) => { +test('new Permit does not expose credentials from an invalid PDP URL', (t) => { const error = t.throws( () => new Permit({ token: 'test-token', pdp: 'http://pdp-user:pdp-secret@localhost:bad' }), { instanceOf: PermitError }, @@ -100,7 +101,7 @@ test('a useOpa check posts to the OPA root derived from the configured PDP', asy t.deepEqual(urls, [EXPECTED_OPA_CHECK_URL]); }); -test('a useOpa check through an injected opaAxiosInstance posts to the same OPA root', async (t) => { +test('a useOpa check through an injected opaAxiosInstance posts to the OPA root', async (t) => { const opaAxiosInstance = axios.create(); const urls = captureRequestUrls(opaAxiosInstance); const permit = new Permit({ token: 'test-token', pdp: CONFIGURED_PDP, opaAxiosInstance });