diff --git a/test/integration/auth/auth.prose.test.ts b/test/integration/auth/auth.prose.test.ts index e9d0fc651ff..77fa40562d2 100644 --- a/test/integration/auth/auth.prose.test.ts +++ b/test/integration/auth/auth.prose.test.ts @@ -305,7 +305,6 @@ describe('Authentication Spec Prose Tests', function () { ); }); - // todo(NODE-5621): fix the issue with unicode characters. describe('Step 4', function () { /** * Step 4 @@ -340,9 +339,15 @@ describe('Authentication Spec Prose Tests', function () { ]; beforeEach(async function () { - utilClient = this.configuration.newClient(); + utilClient = this.configuration.newClient(this.configuration.url()); const db = utilClient.db('admin'); + try { + await Promise.all(users.map(user => db.removeUser(user.username))); + } catch (err) { + /** We ensure that users are deleted. No action needed. */ + } + const createUserCommands = users.map(user => ({ createUser: user.username, pwd: user.password, @@ -354,33 +359,189 @@ describe('Authentication Spec Prose Tests', function () { }); afterEach(async function () { - const db = utilClient.db('admin'); - await Promise.all(users.map(user => db.removeUser(user.username))); await utilClient?.close(); + await client?.close(); }); - for (const { username, password } of [ - { username: 'IX', password: 'IX' }, - { username: 'IX', password: 'I\u00ADX' }, - { username: '\u2168', password: 'IV' }, - { username: '\u2168', password: 'I\u00ADV' } - ]) { - it.skip( - `logs in with username "${username}" and password "${password}"`, + context('auth credentials in options', () => { + it('logs in with non-normalized username and password', metadata, async function () { + const options = { + auth: { username: 'IX', password: 'IX' }, + authSource: 'admin', + authMechanism: 'SCRAM-SHA-256' + }; + + client = this.configuration.newClient({}, options); + const stats = await client.db('admin').stats(); + expect(stats).to.exist; + }); + + it( + 'logs in with non-normalized username and normalized password', metadata, async function () { const options = { - auth: { username, password }, + auth: { username: 'IX', password: 'I\u00ADX' }, authSource: 'admin', authMechanism: 'SCRAM-SHA-256' }; - client = this.configuration.newClient(options); + client = this.configuration.newClient({}, options); const stats = await client.db('admin').stats(); expect(stats).to.exist; } - ).skipReason = 'todo(NODE-5621): fix the issue with unicode characters.'; - } + ); + + it( + 'logs in with normalized username and non-normalized password', + metadata, + async function () { + const options = { + auth: { username: '\u2168', password: 'IV' }, + authSource: 'admin', + authMechanism: 'SCRAM-SHA-256' + }; + + client = this.configuration.newClient({}, options); + const stats = await client.db('admin').stats(); + expect(stats).to.exist; + } + ); + + it('logs in with normalized username and normalized password', metadata, async function () { + const options = { + auth: { username: '\u2168', password: 'I\u00ADV' }, + authSource: 'admin', + authMechanism: 'SCRAM-SHA-256' + }; + + client = this.configuration.newClient({}, options); + const stats = await client.db('admin').stats(); + expect(stats).to.exist; + }); + }); + + context('auth credentials in url', () => { + context('encoded', () => { + it('logs in with not encoded username and password', metadata, async function () { + const options = { + authSource: 'admin', + authMechanism: 'SCRAM-SHA-256' + }; + client = this.configuration.newClient( + this.configuration.url({ username: 'IX', password: 'IX' }), + options + ); + const stats = await client.db('admin').stats(); + expect(stats).to.exist; + }); + + it('logs in with not encoded username and encoded password', metadata, async function () { + const options = { + authSource: 'admin', + authMechanism: 'SCRAM-SHA-256' + }; + client = this.configuration.newClient( + this.configuration.url({ username: 'IX', password: 'I%C2%ADX' }), + options + ); + const stats = await client.db('admin').stats(); + expect(stats).to.exist; + }); + + it('logs in with encoded username and not encoded password', metadata, async function () { + const options = { + authSource: 'admin', + authMechanism: 'SCRAM-SHA-256' + }; + client = this.configuration.newClient( + this.configuration.url({ username: '%E2%85%A8', password: 'IV' }), + options + ); + const stats = await client.db('admin').stats(); + expect(stats).to.exist; + }); + + it('logs in with encoded username and encoded password', metadata, async function () { + const options = { + authSource: 'admin', + authMechanism: 'SCRAM-SHA-256' + }; + client = this.configuration.newClient( + this.configuration.url({ username: '%E2%85%A8', password: 'I%C2%ADV' }), + options + ); + const stats = await client.db('admin').stats(); + expect(stats).to.exist; + }); + }); + + context('normalized', () => { + it('logs in with non-normalized username and password', metadata, async function () { + const options = { + authSource: 'admin', + authMechanism: 'SCRAM-SHA-256' + }; + client = this.configuration.newClient( + this.configuration.url({ username: 'IX', password: 'IX' }), + options + ); + const stats = await client.db('admin').stats(); + expect(stats).to.exist; + }); + + it( + 'logs in with non-normalized username and normalized password', + metadata, + async function () { + const options = { + authSource: 'admin', + authMechanism: 'SCRAM-SHA-256' + }; + client = this.configuration.newClient( + this.configuration.url({ username: 'IX', password: 'I\u00ADX' }), + options + ); + const stats = await client.db('admin').stats(); + expect(stats).to.exist; + } + ); + + it( + 'logs in with normalized username and non-normalized password', + metadata, + async function () { + const options = { + authSource: 'admin', + authMechanism: 'SCRAM-SHA-256' + }; + client = this.configuration.newClient( + this.configuration.url({ username: '\u2168', password: 'I\u00ADV' }), + options + ); + const stats = await client.db('admin').stats(); + expect(stats).to.exist; + } + ); + + it( + 'logs in with normalized username and normalized password', + metadata, + async function () { + const options = { + authSource: 'admin', + authMechanism: 'SCRAM-SHA-256' + }; + client = this.configuration.newClient( + this.configuration.url({ username: '\u2168', password: 'I\u00ADV' }), + options + ); + const stats = await client.db('admin').stats(); + expect(stats).to.exist; + } + ); + }); + }); }); }); }); diff --git a/test/tools/runner/config.ts b/test/tools/runner/config.ts index b24a9e77d87..e4f3dd52a3f 100644 --- a/test/tools/runner/config.ts +++ b/test/tools/runner/config.ts @@ -158,54 +158,59 @@ export class TestConfiguration { return uri.indexOf('MONGODB-OIDC') > -1 && uri.indexOf('PROVIDER_NAME:azure') > -1; } - newClient(dbOptions?: string | Record, serverOptions?: Record) { + newClient(urlOrQueryOptions?: string | Record, serverOptions?: Record) { serverOptions = Object.assign({}, getEnvironmentalOptions(), serverOptions); - // support MongoClient constructor form (url, options) for `newClient` - if (typeof dbOptions === 'string') { - return new MongoClient(dbOptions, serverOptions); + // Support MongoClient constructor form (url, options) for `newClient`. + if (typeof urlOrQueryOptions === 'string') { + if (Reflect.has(serverOptions, 'host') || Reflect.has(serverOptions, 'port')) { + throw new Error(`Cannot use options to specify host/port, must be in ${urlOrQueryOptions}`); + } + + return new MongoClient(urlOrQueryOptions, serverOptions); } - dbOptions = dbOptions || {}; - // Fall back - let dbHost = (serverOptions && serverOptions.host) || this.options.host; - const dbPort = (serverOptions && serverOptions.port) || this.options.port; + const queryOptions = urlOrQueryOptions || {}; + + // Fall back. + let dbHost = serverOptions.host || this.options.host; if (dbHost.indexOf('.sock') !== -1) { dbHost = qs.escape(dbHost); } + const dbPort = serverOptions.port || this.options.port; - if (this.options.authMechanism) { - Object.assign(dbOptions, { + if (this.options.authMechanism && !serverOptions.authMechanism) { + Object.assign(queryOptions, { authMechanism: this.options.authMechanism }); } - if (this.options.authMechanismProperties) { - Object.assign(dbOptions, { + if (this.options.authMechanismProperties && !serverOptions.authMechanismProperties) { + Object.assign(queryOptions, { authMechanismProperties: convertToConnStringMap(this.options.authMechanismProperties) }); } - if (this.options.replicaSet) { - Object.assign(dbOptions, { replicaSet: this.options.replicaSet }); + if (this.options.replicaSet && !serverOptions.replicaSet) { + Object.assign(queryOptions, { replicaSet: this.options.replicaSet }); } if (this.options.proxyURIParams) { for (const [name, value] of Object.entries(this.options.proxyURIParams)) { if (value) { - dbOptions[name] = value; + queryOptions[name] = value; } } } - // Flatten any options nested under `writeConcern` before we make the connection string - if (dbOptions.writeConcern) { - Object.assign(dbOptions, dbOptions.writeConcern); - delete dbOptions.writeConcern; + // Flatten any options nested under `writeConcern` before we make the connection string. + if (queryOptions.writeConcern && !serverOptions.writeConcern) { + Object.assign(queryOptions, queryOptions.writeConcern); + delete queryOptions.writeConcern; } if (this.topologyType === TopologyType.LoadBalanced && !this.isServerless) { - dbOptions.loadBalanced = true; + queryOptions.loadBalanced = true; } const urlOptions: url.UrlObject = { @@ -213,33 +218,30 @@ export class TestConfiguration { slashes: true, hostname: dbHost, port: this.isServerless ? null : dbPort, - query: dbOptions, + query: queryOptions, pathname: '/' }; - if (this.options.auth) { + if (this.options.auth && !serverOptions.auth) { const { username, password } = this.options.auth; if (username) { urlOptions.auth = `${encodeURIComponent(username)}:${encodeURIComponent(password)}`; } } - if (dbOptions.auth) { - const { username, password } = dbOptions.auth; + if (queryOptions.auth) { + const { username, password } = queryOptions.auth; if (username) { urlOptions.auth = `${encodeURIComponent(username)}:${encodeURIComponent(password)}`; } } if (typeof urlOptions.query === 'object') { - // Auth goes at the top of the uri, not in the searchParams - delete urlOptions.query.auth; + // Auth goes at the top of the uri, not in the searchParams. + delete urlOptions.query?.auth; } const connectionString = url.format(urlOptions); - if (Reflect.has(serverOptions, 'host') || Reflect.has(serverOptions, 'port')) { - throw new Error(`Cannot use options to specify host/port, must be in ${connectionString}`); - } return new MongoClient(connectionString, serverOptions); } @@ -277,8 +279,8 @@ export class TestConfiguration { url.pathname = `/${options.db}`; - const username = this.options.username || (this.options.auth && this.options.auth.username); - const password = this.options.password || (this.options.auth && this.options.auth.password); + const username = options.username || this.options.auth?.username; + const password = options.password || this.options.auth?.password; if (username) { url.username = username;