Skip to content

Commit a545f57

Browse files
committed
Improved spec testing
1 parent e116374 commit a545f57

File tree

2 files changed

+127
-77
lines changed

2 files changed

+127
-77
lines changed

src/connection_string.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -797,7 +797,10 @@ function parseURI(uri: string): { srv: boolean; url: URL; hosts: string[] } {
797797
throw new MongoParseError('Password contains unescaped characters');
798798
}
799799

800-
const authString = username ? (password ? `${username}:${password}` : `${username}`) : '';
800+
let authString = '';
801+
if (typeof username === 'string') authString += username;
802+
if (typeof password === 'string') authString += `:${password}`;
803+
801804
const srv = protocol.includes('srv');
802805
const hostList = hosts.split(',');
803806
const url = new URL(`${protocol.toLowerCase()}://${authString}@dummyHostname${rest}`);
@@ -922,12 +925,14 @@ export function parseOptions(
922925
mongoOptions.dbName = decodeURIComponent(
923926
url.pathname[0] === '/' ? url.pathname.slice(1) : url.pathname
924927
);
928+
mongoOptions.credentials = new MongoCredentials({
929+
...mongoOptions.credentials,
930+
source: mongoOptions.dbName,
931+
username: typeof url.username === 'string' ? decodeURIComponent(url.username) : undefined,
932+
password: typeof url.password === 'string' ? decodeURIComponent(url.password) : undefined
933+
});
925934

926935
const urlOptions = new Map();
927-
928-
if (url.username) urlOptions.set('username', [url.username]);
929-
if (url.password) urlOptions.set('password', [url.password]);
930-
931936
for (const key of url.searchParams.keys()) {
932937
const loweredKey = key.toLowerCase();
933938
const values = [...url.searchParams.getAll(key)];
@@ -1036,13 +1041,18 @@ function toHostArray(hostString: string) {
10361041
socketPath = decodeURIComponent(hostString);
10371042
}
10381043

1044+
let ipv6SanitizedHostName;
1045+
if (parsedHost.hostname.startsWith('[') && parsedHost.hostname.endsWith(']')) {
1046+
ipv6SanitizedHostName = parsedHost.hostname.substring(1, parsedHost.hostname.length - 1);
1047+
}
1048+
10391049
const result: Host = socketPath
10401050
? {
10411051
host: socketPath,
10421052
type: 'unix'
10431053
}
10441054
: {
1045-
host: parsedHost.hostname,
1055+
host: decodeURIComponent(ipv6SanitizedHostName ?? parsedHost.hostname),
10461056
port: parsedHost.port ? parseInt(parsedHost.port) : 27017,
10471057
type: 'tcp'
10481058
};
@@ -1095,16 +1105,26 @@ export const OPTIONS: Record<keyof MongoClientOptions, OptionDescriptor> = {
10951105
if (!mechanism) {
10961106
throw new TypeError(`authMechanism one of ${mechanisms}, got ${value}`);
10971107
}
1098-
let db; // some mechanisms have '$external' as the Auth Source
1108+
let source = options.credentials.source; // some mechanisms have '$external' as the Auth Source
10991109
if (
11001110
mechanism === 'PLAIN' ||
11011111
mechanism === 'GSSAPI' ||
11021112
mechanism === 'MONGODB-AWS' ||
11031113
mechanism === 'MONGODB-X509'
11041114
) {
1105-
db = '$external';
1115+
source = '$external';
1116+
}
1117+
1118+
let password: string | undefined = options.credentials.password;
1119+
if (mechanism === 'MONGODB-X509' && password === '') {
1120+
password = undefined;
11061121
}
1107-
return new MongoCredentials({ ...options.credentials, mechanism, db });
1122+
return new MongoCredentials({
1123+
...options.credentials,
1124+
mechanism,
1125+
source,
1126+
password: password as any
1127+
});
11081128
}
11091129
},
11101130
authMechanismProperties: {

test/unit/core/connection_string.test.js

Lines changed: 98 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -214,88 +214,118 @@ describe('Connection String', function () {
214214
});
215215

216216
describe('spec tests', function () {
217+
/** @type {import('../../spec/connection-string/valid-auth.json')[]} */
217218
const suites = loadSpecTests('connection-string').concat(loadSpecTests('auth'));
218219

219220
suites.forEach(suite => {
220221
describe(suite.name, function () {
221222
suite.tests.forEach(test => {
222-
it(test.description, {
223-
metadata: { requires: { topology: 'single' } },
224-
test: function (done) {
225-
if (skipTests.indexOf(test.description) !== -1) {
226-
return this.skip();
227-
}
228-
229-
const valid = test.valid;
230-
parseConnectionString(test.uri, { caseTranslate: false }, function (err, result) {
231-
if (valid === false) {
232-
expect(err).to.exist;
233-
expect(err).to.be.instanceOf(MongoParseError);
234-
expect(result).to.not.exist;
235-
} else {
236-
expect(err).to.not.exist;
237-
expect(result).to.exist;
238-
239-
// remove data we don't track
240-
if (test.auth && test.auth.password === '') {
241-
test.auth.password = null;
242-
}
243-
244-
if (test.hosts != null) {
245-
test.hosts = test.hosts.map(host => {
246-
delete host.type;
247-
host.host = punycode.toASCII(host.host);
248-
return host;
249-
});
250-
251-
// remove values that require no validation
252-
test.hosts.forEach(host => {
253-
Object.keys(host).forEach(key => {
254-
if (host[key] == null) delete host[key];
255-
});
256-
});
257-
258-
expect(result.hosts).to.containSubset(test.hosts);
259-
}
260-
261-
if (test.auth) {
262-
if (test.auth.db != null) {
263-
expect(result.auth).to.have.property('db');
264-
expect(result.auth.db).to.eql(test.auth.db);
265-
}
266-
267-
if (test.auth.username != null) {
268-
expect(result.auth).to.have.property('username');
269-
expect(result.auth.username).to.eql(test.auth.username);
270-
}
271-
272-
if (test.auth.password != null) {
273-
expect(result.auth).to.have.property('password');
274-
expect(result.auth.password).to.eql(test.auth.password);
275-
}
276-
}
277-
278-
if (test.options != null) {
279-
// it's possible we have options which are not explicitly included in the spec test
280-
expect(result.options).to.deep.include(test.options);
281-
}
282-
}
283-
284-
done();
285-
});
286-
}
287-
});
223+
// it(test.description, {
224+
// metadata: { requires: { topology: 'single' } },
225+
// test: function (done) {
226+
// if (skipTests.indexOf(test.description) !== -1) {
227+
// return this.skip();
228+
// }
229+
230+
// const valid = test.valid;
231+
// parseConnectionString(test.uri, { caseTranslate: false }, function (err, result) {
232+
// if (valid === false) {
233+
// expect(err).to.exist;
234+
// expect(err).to.be.instanceOf(MongoParseError);
235+
// expect(result).to.not.exist;
236+
// } else {
237+
// expect(err).to.not.exist;
238+
// expect(result).to.exist;
239+
240+
// // remove data we don't track
241+
// if (test.auth && test.auth.password === '') {
242+
// test.auth.password = null;
243+
// }
244+
245+
// if (test.hosts != null) {
246+
// test.hosts = test.hosts.map(host => {
247+
// delete host.type;
248+
// host.host = punycode.toASCII(host.host);
249+
// return host;
250+
// });
251+
252+
// // remove values that require no validation
253+
// test.hosts.forEach(host => {
254+
// Object.keys(host).forEach(key => {
255+
// if (host[key] == null) delete host[key];
256+
// });
257+
// });
258+
259+
// expect(result.hosts).to.containSubset(test.hosts);
260+
// }
261+
262+
// if (test.auth) {
263+
// if (test.auth.db != null) {
264+
// expect(result.auth).to.have.property('db');
265+
// expect(result.auth.db).to.eql(test.auth.db);
266+
// }
267+
268+
// if (test.auth.username != null) {
269+
// expect(result.auth).to.have.property('username');
270+
// expect(result.auth.username).to.eql(test.auth.username);
271+
// }
272+
273+
// if (test.auth.password != null) {
274+
// expect(result.auth).to.have.property('password');
275+
// expect(result.auth.password).to.eql(test.auth.password);
276+
// }
277+
// }
278+
279+
// if (test.options != null) {
280+
// // it's possible we have options which are not explicitly included in the spec test
281+
// expect(result.options).to.deep.include(test.options);
282+
// }
283+
// }
284+
285+
// done();
286+
// });
287+
// }
288+
// });
288289

289290
it(`${test.description} -- new MongoOptions parser`, function () {
290291
if (skipTests.includes(test.description)) {
291292
return this.skip();
292293
}
293294

295+
const message = `"${test.uri}"`;
296+
294297
const valid = test.valid;
295298
if (valid) {
296-
expect(parseOptions(test.uri), `"${test.uri}"`).to.be.ok;
299+
const options = parseOptions(test.uri);
300+
expect(options, message).to.be.ok;
301+
302+
if (test.hosts) {
303+
for (const [index, { host, port }] of test.hosts.entries()) {
304+
expect(options.hosts[index].host, message).to.equal(host);
305+
if (typeof port === 'number') expect(options.hosts[index].port).to.equal(port);
306+
}
307+
}
308+
309+
if (test.auth) {
310+
if (test.auth.db != null) {
311+
expect(options.credentials.source, message).to.equal(test.auth.db);
312+
}
313+
314+
if (test.auth.username != null) {
315+
expect(options.credentials.username, message).to.equal(test.auth.username);
316+
}
317+
318+
if (test.auth.password != null) {
319+
expect(options.credentials.password, message).to.equal(test.auth.password);
320+
}
321+
}
322+
323+
// Going to have to get clever here...
324+
// if (test.options) {
325+
// expect(options, message).to.deep.include(test.options);
326+
// }
297327
} else {
298-
expect(() => parseOptions(test.uri), `"${test.uri}"`).to.throw();
328+
expect(() => parseOptions(test.uri), message).to.throw();
299329
}
300330
});
301331
});

0 commit comments

Comments
 (0)