diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index fb511a4..0000000 --- a/.eslintrc +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": [ - "semistandard", - "plugin:promise/recommended", - "plugin:@typescript-eslint/recommended" - ], - "rules": { - "@typescript-eslint/no-explicit-any": 0, - "@typescript-eslint/no-empty-function": 0, - "no-return-assign": 0, - "space-before-function-paren": ["error", "never"] - } -} diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..714eb49 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,112 @@ +{ + "root": true, + "extends": [ + "eslint:recommended", + "plugin:prettier/recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "env": { + "node": true, + "mocha": true, + "es6": true + }, + "parserOptions": { + "ecmaVersion": 2019 + }, + "plugins": [ + "@typescript-eslint", + "prettier" + ], + "parser": "@typescript-eslint/parser", + "rules": { + "no-restricted-properties": [ + "error", + { + "object": "describe", + "property": "only" + }, + { + "object": "it", + "property": "only" + }, + { + "object": "context", + "property": "only" + } + ], + "prettier/prettier": "error", + "no-console": "error", + "valid-typeof": "error", + "eqeqeq": [ + "error", + "always", + { + "null": "ignore" + } + ], + "strict": [ + "error", + "global" + ], + "no-restricted-syntax": [ + "error", + { + "selector": "TSEnumDeclaration", + "message": "Do not declare enums" + }, + { + "selector": "BinaryExpression[operator=/[=!]==/] Identifier[name='undefined']", + "message": "Do not strictly check undefined" + }, + { + "selector": "BinaryExpression[operator=/[=!]==/] Literal[raw='null']", + "message": "Do not strictly check null" + }, + { + "selector": "BinaryExpression[operator=/[=!]==?/] Literal[value='undefined']", + "message": "Do not strictly check typeof undefined (NOTE: currently this rule only detects the usage of 'undefined' string literal so this could be a misfire)" + } + ], + "@typescript-eslint/no-require-imports": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_", + "destructuredArrayIgnorePattern": "^_", + "varsIgnorePattern": "^_" + } + ] + }, + "overrides": [ + { + "files": [ + "lib/*.js" + ], + "parserOptions": { + "ecmaVersion": 2019, + "sourceType": "commonjs" + } + }, + { + "files": [ + "test/**/*ts" + ], + "rules": { + // chat `expect(..)` style chaining is considered + // an unused expression + "@typescript-eslint/no-unused-expressions": "off" + } + }, + { + // json configuration files + "files": [ + ".*.json" + ], + "rules": { + "@typescript-eslint/no-unused-expressions": "off" + } + } + ] +} \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 22ef6a2..bba2e94 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -22,6 +22,8 @@ updates: versions: [">=16.0.0"] # we ignore TS as a part of quarterly dependency updates. - dependency-name: "typescript" + - dependency-name: "@types/node" + versions: [">=24.0.0"] groups: development-dependencies: dependency-type: "development" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..67fc1ff --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,20 @@ +on: [push, pull_request] + +name: CI +permissions: + contents: read + +jobs: + test: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Use Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Install Dependencies + run: npm install + - name: Lint + run: npm run lint diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..169dcfb --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "singleQuote": true, + "tabWidth": 2, + "printWidth": 100, + "arrowParens": "avoid", + "trailingComma": "none" +} \ No newline at end of file diff --git a/package.json b/package.json index edc1e08..019821d 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,8 @@ ".esm-wrapper.mjs" ], "scripts": { - "lint": "eslint \"{src,test}/**/*.ts\"", - "test": "npm run lint && npm run build && nyc mocha --colors -r ts-node/register test/*.ts", + "lint": "ESLINT_USE_FLAT_CONFIG=false eslint \"{src,test}/**/*.ts\"", + "test": "npm run build && nyc mocha --colors -r ts-node/register test/*.ts", "build": "npm run compile-ts && gen-esm-wrapper . ./.esm-wrapper.mjs", "prepack": "npm run build", "compile-ts": "tsc -p tsconfig.json" @@ -39,16 +39,15 @@ "@types/chai": "^5.0.1", "@types/mocha": "^10.0.10", "@types/node": "^22.9.0", - "@typescript-eslint/eslint-plugin": "^4.2.0", - "@typescript-eslint/parser": "^4.2.0", + "@typescript-eslint/eslint-plugin": "^8.39.1", + "@typescript-eslint/parser": "^8.39.1", "chai": "^4.2.0", - "eslint": "^7.9.0", - "eslint-config-semistandard": "^15.0.1", - "eslint-config-standard": "^14.1.1", + "eslint": "^9.33.0", "eslint-plugin-import": "^2.22.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^7.1.0", - "eslint-plugin-standard": "^5.0.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.4", "gen-esm-wrapper": "^1.1.3", "mocha": "^11.0.1", "nyc": "^15.1.0", @@ -59,4 +58,4 @@ "@types/whatwg-url": "^13.0.0", "whatwg-url": "^14.1.0 || ^13.0.0" } -} +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 1c459f6..f661f4d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { URL, URLSearchParams } from 'whatwg-url'; import { redactValidConnectionString, @@ -9,10 +10,7 @@ export { redactConnectionString, ConnectionStringRedactionOptions }; const DUMMY_HOSTNAME = '__this_is_a_placeholder__'; function connectionStringHasValidScheme(connectionString: string) { - return ( - connectionString.startsWith('mongodb://') || - connectionString.startsWith('mongodb+srv://') - ); + return connectionString.startsWith('mongodb://') || connectionString.startsWith('mongodb+srv://'); } // Adapted from the Node.js driver code: @@ -130,7 +128,9 @@ export class ConnectionString extends URLWithoutHost { constructor(uri: string, options: ConnectionStringParsingOptions = {}) { const { looseValidation } = options; if (!looseValidation && !connectionStringHasValidScheme(uri)) { - throw new MongoParseError('Invalid scheme, expected connection string to start with "mongodb://" or "mongodb+srv://"'); + throw new MongoParseError( + 'Invalid scheme, expected connection string to start with "mongodb://" or "mongodb+srv://"' + ); } const match = uri.match(HOSTS_REGEX); @@ -205,20 +205,39 @@ export class ConnectionString extends URLWithoutHost { if (!this.pathname) { this.pathname = '/'; } - Object.setPrototypeOf(this.searchParams, caseInsenstiveURLSearchParams(this.searchParams.constructor as any).prototype); + Object.setPrototypeOf( + this.searchParams, + caseInsenstiveURLSearchParams(this.searchParams.constructor as any).prototype + ); } // The getters here should throw, but that would break .toString() because of // https://github.com/nodejs/node/issues/36887. Using 'never' as the type // should be enough to stop anybody from using them in TypeScript, though. - get host(): never { return DUMMY_HOSTNAME as never; } - set host(_ignored: never) { throw new Error('No single host for connection string'); } - get hostname(): never { return DUMMY_HOSTNAME as never; } - set hostname(_ignored: never) { throw new Error('No single host for connection string'); } - get port(): never { return '' as never; } - set port(_ignored: never) { throw new Error('No single host for connection string'); } - get href(): string { return this.toString(); } - set href(_ignored: string) { throw new Error('Cannot set href for connection strings'); } + get host(): never { + return DUMMY_HOSTNAME as never; + } + set host(_ignored: never) { + throw new Error('No single host for connection string'); + } + get hostname(): never { + return DUMMY_HOSTNAME as never; + } + set hostname(_ignored: never) { + throw new Error('No single host for connection string'); + } + get port(): never { + return '' as never; + } + set port(_ignored: never) { + throw new Error('No single host for connection string'); + } + get href(): string { + return this.toString(); + } + set href(_ignored: string) { + throw new Error('Cannot set href for connection strings'); + } get isSRV(): boolean { return this.protocol.includes('srv'); @@ -246,15 +265,37 @@ export class ConnectionString extends URLWithoutHost { return redactValidConnectionString(this, options); } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/ban-types - typedSearchParams() { - const sametype = (false as true) && new (caseInsenstiveURLSearchParams(URLSearchParams))(); - return this.searchParams as unknown as typeof sametype; + typedSearchParams>() { + const _sametype = + (false as true) && new (caseInsenstiveURLSearchParams(URLSearchParams))(); + return this.searchParams as unknown as typeof _sametype; } [Symbol.for('nodejs.util.inspect.custom')](): any { - const { href, origin, protocol, username, password, hosts, pathname, search, searchParams, hash } = this; - return { href, origin, protocol, username, password, hosts, pathname, search, searchParams, hash }; + const { + href, + origin, + protocol, + username, + password, + hosts, + pathname, + search, + searchParams, + hash + } = this; + return { + href, + origin, + protocol, + username, + password, + hosts, + pathname, + search, + searchParams, + hash + }; } } @@ -262,8 +303,9 @@ export class ConnectionString extends URLWithoutHost { * Parses and serializes the format of the authMechanismProperties or * readPreferenceTags connection string parameters. */ -// eslint-disable-next-line @typescript-eslint/ban-types -export class CommaAndColonSeparatedRecord> extends CaseInsensitiveMap { +export class CommaAndColonSeparatedRecord< + K extends Record = Record +> extends CaseInsensitiveMap { constructor(from?: string | null) { super(); for (const entry of (from ?? '').split(',')) { @@ -271,9 +313,9 @@ export class CommaAndColonSeparatedRecord const colonIndex = entry.indexOf(':'); // Use .set() to properly account for case insensitivity if (colonIndex === -1) { - this.set(entry as (keyof K & string), ''); + this.set(entry as keyof K & string, ''); } else { - this.set(entry.slice(0, colonIndex) as (keyof K & string), entry.slice(colonIndex + 1)); + this.set(entry.slice(0, colonIndex) as keyof K & string, entry.slice(colonIndex + 1)); } } } diff --git a/src/redact.ts b/src/redact.ts index f9636bb..4897c4d 100644 --- a/src/redact.ts +++ b/src/redact.ts @@ -7,7 +7,8 @@ export interface ConnectionStringRedactionOptions { export function redactValidConnectionString( inputUrl: Readonly, - options?: ConnectionStringRedactionOptions): ConnectionString { + options?: ConnectionStringRedactionOptions +): ConnectionString { const url = inputUrl.clone(); const replacementString = options?.replacementString ?? '_credentials_'; const redactUsernames = options?.redactUsernames ?? true; @@ -39,19 +40,25 @@ export function redactValidConnectionString( export function redactConnectionString( uri: string, - options?: ConnectionStringRedactionOptions): string { + options?: ConnectionStringRedactionOptions +): string { const replacementString = options?.replacementString ?? ''; const redactUsernames = options?.redactUsernames ?? true; let parsed: ConnectionString | undefined; try { parsed = new ConnectionString(uri); - } catch {} + } catch { + // squash errors + } if (parsed) { // If we can parse the connection string, use the more precise // redaction logic. options = { ...options, replacementString: '___credentials___' }; - return parsed.redact(options).toString().replace(/___credentials___/g, replacementString); + return parsed + .redact(options) + .toString() + .replace(/___credentials___/g, replacementString); } // Note: The regexes here used to use lookbehind assertions, but we dropped that since @@ -65,7 +72,7 @@ export function redactConnectionString( // tlsCertificateKeyFilePassword query parameter uri => uri.replace(/(tlsCertificateKeyFilePassword=)([^&]+)/gi, `$1${R}`), // proxyUsername query parameter - uri => redactUsernames ? uri.replace(/(proxyUsername=)([^&]+)/gi, `$1${R}`) : uri, + uri => (redactUsernames ? uri.replace(/(proxyUsername=)([^&]+)/gi, `$1${R}`) : uri), // proxyPassword query parameter uri => uri.replace(/(proxyPassword=)([^&]+)/gi, `$1${R}`) ]; diff --git a/test/index.ts b/test/index.ts index 115bd22..1b1ce2c 100644 --- a/test/index.ts +++ b/test/index.ts @@ -82,7 +82,8 @@ describe('ConnectionString', () => { username: 'database-meow', password: '', pathname: '/', - search: '?ssl=true&replicaSet=globaldb&retrywrites=false&maxIdleTimeMS=120000&appName=@database-haha@', + search: + '?ssl=true&replicaSet=globaldb&retrywrites=false&maxIdleTimeMS=120000&appName=@database-haha@', hash: '', isSRV: false, hosts: ['database-haha.mongo.blah.blah.com:8888'] @@ -104,7 +105,9 @@ describe('ConnectionString', () => { // eslint-disable-next-line no-new new ConnectionString('totallynotamongodb://outerspace'); } catch (err) { - expect((err as Error).message).to.equal('Invalid scheme, expected connection string to start with "mongodb://" or "mongodb+srv://"'); + expect((err as Error).message).to.equal( + 'Invalid scheme, expected connection string to start with "mongodb://" or "mongodb+srv://"' + ); expect((err as Error).name).to.equal('MongoParseError'); return; } @@ -163,7 +166,9 @@ describe('ConnectionString', () => { cs.searchParams.set('serverSelectionTimeoutMS', '200'); cs.searchParams.append('serverSelectionTimeoutMS', '300'); - expect(cs.toString()).to.equal('mongodb://localhost/?SERVERSELECTIONTIMEOUTMS=200&SERVERSELECTIONTIMEOUTMS=300'); + expect(cs.toString()).to.equal( + 'mongodb://localhost/?SERVERSELECTIONTIMEOUTMS=200&SERVERSELECTIONTIMEOUTMS=300' + ); expect(cs.searchParams.has('serverSelectionTimeoutMS')).to.equal(true); expect(cs.searchParams.has('SERVERSELECTIONTIMEOUTMS')).to.equal(true); expect(cs.searchParams.get('serverSelectionTimeoutMS')).to.equal('200'); @@ -185,21 +190,33 @@ describe('ConnectionString', () => { context('URL methods that do not apply to connection strings as-is', () => { it('throws/returns dummy values', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const cs: any = new ConnectionString('mongodb://localhost'); expect(cs.host).not.to.equal('localhost'); expect(cs.hostname).not.to.equal('localhost'); expect(cs.port).to.equal(''); expect(cs.href).to.equal('mongodb://localhost/'); - expect(() => { cs.host = 'abc'; }).to.throw(Error); - expect(() => { cs.hostname = 'abc'; }).to.throw(Error); - expect(() => { cs.port = '1000'; }).to.throw(Error); - expect(() => { cs.href = 'mongodb://localhost'; }).to.throw(Error); + expect(() => { + cs.host = 'abc'; + }).to.throw(Error); + expect(() => { + cs.hostname = 'abc'; + }).to.throw(Error); + expect(() => { + cs.port = '1000'; + }).to.throw(Error); + expect(() => { + cs.href = 'mongodb://localhost'; + }).to.throw(Error); }); }); context('with loose validation', () => { it('allows odd connection strings', () => { - const cs: any = new ConnectionString('mongodb://:password@x', { looseValidation: true }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const cs: any = new ConnectionString('mongodb://:password@x', { + looseValidation: true + }); expect(cs.username).to.equal(''); expect(cs.password).to.equal('password'); expect(cs.port).to.equal(''); @@ -208,11 +225,13 @@ describe('ConnectionString', () => { it('throws good error messages for invalid URLs', () => { try { - // eslint-disable-next-line no-new new ConnectionString('-://:password@x', { looseValidation: true }); expect.fail('missed exception'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err: any) { - expect(err.message).to.equal('Invalid scheme, expected connection string to start with "mongodb://" or "mongodb+srv://"'); + expect(err.message).to.equal( + 'Invalid scheme, expected connection string to start with "mongodb://" or "mongodb+srv://"' + ); } }); }); diff --git a/test/redact.ts b/test/redact.ts index 501674e..f7e7ff4 100644 --- a/test/redact.ts +++ b/test/redact.ts @@ -5,58 +5,114 @@ describe('redact credentials', () => { for (const protocol of ['mongodb', 'mongodb+srv', '+invalid+']) { context(`when url contains credentials (protocol: ${protocol})`, () => { it('returns the in output instead of password', () => { - expect(redactConnectionString(`${protocol}://admin:catsc@tscat3ca1s@cats-data-sets-e08dy.mongodb.net/admin`)) - .to.equal(`${protocol}://@cats-data-sets-e08dy.mongodb.net/admin`); + expect( + redactConnectionString( + `${protocol}://admin:catsc@tscat3ca1s@cats-data-sets-e08dy.mongodb.net/admin` + ) + ).to.equal(`${protocol}://@cats-data-sets-e08dy.mongodb.net/admin`); }); it('returns the keeping the username if desired', () => { - expect(redactConnectionString(`${protocol}://admin:catsc@tscat3ca1s@cats-data-sets-e08dy.mongodb.net/admin`, { redactUsernames: false })) - .to.equal(`${protocol}://admin:@cats-data-sets-e08dy.mongodb.net/admin`); + expect( + redactConnectionString( + `${protocol}://admin:catsc@tscat3ca1s@cats-data-sets-e08dy.mongodb.net/admin`, + { redactUsernames: false } + ) + ).to.equal(`${protocol}://admin:@cats-data-sets-e08dy.mongodb.net/admin`); }); it('returns the in output instead of IAM session token', () => { - expect(redactConnectionString(`${protocol}://cats-data-sets-e08dy.mongodb.net/admin?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN%3Asampletoken,else%3Amiau¶m=true`).replace(/%2C/g, ',')) - .to.equal(`${protocol}://cats-data-sets-e08dy.mongodb.net/admin?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN%3A,else%3Amiau¶m=true`); - expect(redactConnectionString(`${protocol}://cats-data-sets-e08dy.mongodb.net/admin?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN%3Asampletoken¶m=true`)) - .to.equal(`${protocol}://cats-data-sets-e08dy.mongodb.net/admin?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN%3A¶m=true`); - expect(redactConnectionString(`${protocol}://cats-data-sets-e08dy.mongodb.net/admin?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN%3Asampletoken`)) - .to.equal(`${protocol}://cats-data-sets-e08dy.mongodb.net/admin?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN%3A`); + expect( + redactConnectionString( + `${protocol}://cats-data-sets-e08dy.mongodb.net/admin?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN%3Asampletoken,else%3Amiau¶m=true` + ).replace(/%2C/g, ',') + ).to.equal( + `${protocol}://cats-data-sets-e08dy.mongodb.net/admin?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN%3A,else%3Amiau¶m=true` + ); + expect( + redactConnectionString( + `${protocol}://cats-data-sets-e08dy.mongodb.net/admin?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN%3Asampletoken¶m=true` + ) + ).to.equal( + `${protocol}://cats-data-sets-e08dy.mongodb.net/admin?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN%3A¶m=true` + ); + expect( + redactConnectionString( + `${protocol}://cats-data-sets-e08dy.mongodb.net/admin?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN%3Asampletoken` + ) + ).to.equal( + `${protocol}://cats-data-sets-e08dy.mongodb.net/admin?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN%3A` + ); }); it('returns the in output instead of password and IAM session token', () => { - expect(redactConnectionString(`${protocol}://admin:tscat3ca1s@cats-data-sets-e08dy.mongodb.net/admin?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN%3Asampletoken¶m=true`)) - .to.equal(`${protocol}://@cats-data-sets-e08dy.mongodb.net/admin?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN%3A¶m=true`); + expect( + redactConnectionString( + `${protocol}://admin:tscat3ca1s@cats-data-sets-e08dy.mongodb.net/admin?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN%3Asampletoken¶m=true` + ) + ).to.equal( + `${protocol}://@cats-data-sets-e08dy.mongodb.net/admin?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN%3A¶m=true` + ); }); it('returns the in output instead of tlsCertificateKeyFilePassword', () => { - expect(redactConnectionString(`${protocol}://admin:tscat3ca1s@cats-data-sets-e08dy.mongodb.net/admin?tls=true&tlsCertificateKeyFilePassword=p4ssw0rd`)) - .to.equal(`${protocol}://@cats-data-sets-e08dy.mongodb.net/admin?tls=true&tlsCertificateKeyFilePassword=`); + expect( + redactConnectionString( + `${protocol}://admin:tscat3ca1s@cats-data-sets-e08dy.mongodb.net/admin?tls=true&tlsCertificateKeyFilePassword=p4ssw0rd` + ) + ).to.equal( + `${protocol}://@cats-data-sets-e08dy.mongodb.net/admin?tls=true&tlsCertificateKeyFilePassword=` + ); }); it('returns the in output instead of proxyPassword and proxyUsername', () => { - expect(redactConnectionString(`${protocol}://admin:tscat3ca1s@cats-data-sets-e08dy.mongodb.net/admin?proxyUsername=foo&proxyPassword=bar¶m=true`)) - .to.equal(`${protocol}://@cats-data-sets-e08dy.mongodb.net/admin?proxyUsername=&proxyPassword=¶m=true`); - expect(redactConnectionString(`${protocol}://admin:tscat3ca1s@cats-data-sets-e08dy.mongodb.net/admin?proxyUsername=foo&proxyPassword=bar`)) - .to.equal(`${protocol}://@cats-data-sets-e08dy.mongodb.net/admin?proxyUsername=&proxyPassword=`); - expect(redactConnectionString(`${protocol}://admin:tscat3ca1s@cats-data-sets-e08dy.mongodb.net/admin?proxyUsername=foo&proxyPassword=bar`, { redactUsernames: false })) - .to.equal(`${protocol}://admin:@cats-data-sets-e08dy.mongodb.net/admin?proxyUsername=foo&proxyPassword=`); - expect(redactConnectionString(`${protocol}://admin:tscat3ca1s@cats-data-sets-e08dy.mongodb.net/admin?proxyUsername=foo&proxyPassword=bar`, { replacementString: '****' })) - .to.equal(`${protocol}://****@cats-data-sets-e08dy.mongodb.net/admin?proxyUsername=****&proxyPassword=****`); + expect( + redactConnectionString( + `${protocol}://admin:tscat3ca1s@cats-data-sets-e08dy.mongodb.net/admin?proxyUsername=foo&proxyPassword=bar¶m=true` + ) + ).to.equal( + `${protocol}://@cats-data-sets-e08dy.mongodb.net/admin?proxyUsername=&proxyPassword=¶m=true` + ); + expect( + redactConnectionString( + `${protocol}://admin:tscat3ca1s@cats-data-sets-e08dy.mongodb.net/admin?proxyUsername=foo&proxyPassword=bar` + ) + ).to.equal( + `${protocol}://@cats-data-sets-e08dy.mongodb.net/admin?proxyUsername=&proxyPassword=` + ); + expect( + redactConnectionString( + `${protocol}://admin:tscat3ca1s@cats-data-sets-e08dy.mongodb.net/admin?proxyUsername=foo&proxyPassword=bar`, + { redactUsernames: false } + ) + ).to.equal( + `${protocol}://admin:@cats-data-sets-e08dy.mongodb.net/admin?proxyUsername=foo&proxyPassword=` + ); + expect( + redactConnectionString( + `${protocol}://admin:tscat3ca1s@cats-data-sets-e08dy.mongodb.net/admin?proxyUsername=foo&proxyPassword=bar`, + { replacementString: '****' } + ) + ).to.equal( + `${protocol}://****@cats-data-sets-e08dy.mongodb.net/admin?proxyUsername=****&proxyPassword=****` + ); }); it('redacts credentials when username is empty', () => { - expect( - redactConnectionString(`${protocol}://:password@mongodb.net/`) - ).to.equal(`${protocol}://@mongodb.net/`); + expect(redactConnectionString(`${protocol}://:password@mongodb.net/`)).to.equal( + `${protocol}://@mongodb.net/` + ); }); }); context('when url contains no credentials', () => { it('does not alter input', () => { - expect(redactConnectionString(`${protocol}://127.0.0.1:27017/`)) - .to.equal(`${protocol}://127.0.0.1:27017/`); - expect(redactConnectionString(`${protocol}://127.0.0.1:27017/?authMechanismProperties=IGNORE:ME`)) - .to.equal(`${protocol}://127.0.0.1:27017/?authMechanismProperties=IGNORE:ME`); + expect(redactConnectionString(`${protocol}://127.0.0.1:27017/`)).to.equal( + `${protocol}://127.0.0.1:27017/` + ); + expect( + redactConnectionString(`${protocol}://127.0.0.1:27017/?authMechanismProperties=IGNORE:ME`) + ).to.equal(`${protocol}://127.0.0.1:27017/?authMechanismProperties=IGNORE:ME`); }); }); }