From 4e89a87b000e7f3dd21cdbce3a7e30728ddff327 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 22 Jan 2026 20:50:33 +0700 Subject: [PATCH 01/12] refactor(NODE-7313): only use dns.resolve for all types --- src/cmap/auth/gssapi.ts | 4 +-- src/connection_string.ts | 14 ++++----- src/sdam/srv_polling.ts | 2 +- .../dns_seedlist.test.ts | 30 +++++++++---------- ...itial_dns_seedlist_discovery.prose.test.ts | 2 +- 5 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/cmap/auth/gssapi.ts b/src/cmap/auth/gssapi.ts index d18cb6b360e..e4216351696 100644 --- a/src/cmap/auth/gssapi.ts +++ b/src/cmap/auth/gssapi.ts @@ -166,7 +166,7 @@ export async function performGSSAPICanonicalizeHostName( try { // Perform a reverse ptr lookup on the ip address. - const results = await dns.promises.resolvePtr(address); + const results = await dns.promises.resolve(address, 'PTR'); // If the ptr did not error but had no results, return the host. return results.length > 0 ? results[0] : host; } catch { @@ -185,7 +185,7 @@ export async function performGSSAPICanonicalizeHostName( export async function resolveCname(host: string): Promise { // Attempt to resolve the host name try { - const results = await dns.promises.resolveCname(host); + const results = await dns.promises.resolve(host, 'CNAME'); // Get the first resolved host id return results.length > 0 ? results[0] : host; } catch { diff --git a/src/connection_string.ts b/src/connection_string.ts index df6dfc607a0..ebe728f1b63 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -41,17 +41,17 @@ const LB_REPLICA_SET_ERROR = 'loadBalanced option not supported with a replicaSe const LB_DIRECT_CONNECTION_ERROR = 'loadBalanced option not supported when directConnection is provided'; -function retryDNSTimeoutFor(api: 'resolveSrv'): (a: string) => Promise; -function retryDNSTimeoutFor(api: 'resolveTxt'): (a: string) => Promise; +function retryDNSTimeoutFor(api: 'SRV'): (a: string) => Promise; +function retryDNSTimeoutFor(api: 'TXT'): (a: string) => Promise; function retryDNSTimeoutFor( - api: 'resolveSrv' | 'resolveTxt' + api: 'SRV' | 'TXT' ): (a: string) => Promise { return async function dnsReqRetryTimeout(lookupAddress: string) { try { - return await dns.promises[api](lookupAddress); + return (await dns.promises.resolve(lookupAddress, api)) as dns.SrvRecord[] | string[][]; } catch (firstDNSError) { if (firstDNSError.code === dns.TIMEOUT) { - return await dns.promises[api](lookupAddress); + return (await dns.promises.resolve(lookupAddress, api)) as dns.SrvRecord[] | string[][]; } else { throw firstDNSError; } @@ -59,8 +59,8 @@ function retryDNSTimeoutFor( }; } -const resolveSrv = retryDNSTimeoutFor('resolveSrv'); -const resolveTxt = retryDNSTimeoutFor('resolveTxt'); +const resolveSrv = retryDNSTimeoutFor('SRV'); +const resolveTxt = retryDNSTimeoutFor('TXT'); /** * Lookup a `mongodb+srv` connection string, combine the parts and reparse it as a normal diff --git a/src/sdam/srv_polling.ts b/src/sdam/srv_polling.ts index 583c82d1398..cfc4779cf25 100644 --- a/src/sdam/srv_polling.ts +++ b/src/sdam/srv_polling.ts @@ -116,7 +116,7 @@ export class SrvPoller extends TypedEventEmitter { let srvRecords; try { - srvRecords = await dns.promises.resolveSrv(this.srvAddress); + srvRecords = await dns.promises.resolve(this.srvAddress, 'SRV'); } catch { this.failure(); return; diff --git a/test/integration/initial-dns-seedlist-discovery/dns_seedlist.test.ts b/test/integration/initial-dns-seedlist-discovery/dns_seedlist.test.ts index 15ad016cc3d..389333f6a37 100644 --- a/test/integration/initial-dns-seedlist-discovery/dns_seedlist.test.ts +++ b/test/integration/initial-dns-seedlist-discovery/dns_seedlist.test.ts @@ -31,21 +31,19 @@ describe('DNS timeout errors', () => { await client.close(); }); - const restoreDNS = - api => - async (...args) => { - sinon.restore(); - return await dns.promises[api](...args); - }; + const restoreDNS = api => async args => { + sinon.restore(); + return await dns.promises.resolve(args, api); + }; describe('when SRV record look up times out', () => { beforeEach(() => { stub = sinon - .stub(dns.promises, 'resolveSrv') + .stub(dns.promises, 'resolve') .onFirstCall() .rejects(new DNSTimeoutError()) .onSecondCall() - .callsFake(restoreDNS('resolveSrv')); + .callsFake(restoreDNS('SRV')); }); afterEach(async function () { @@ -61,11 +59,11 @@ describe('DNS timeout errors', () => { describe('when TXT record look up times out', () => { beforeEach(() => { stub = sinon - .stub(dns.promises, 'resolveTxt') + .stub(dns.promises, 'resolve') .onFirstCall() .rejects(new DNSTimeoutError()) .onSecondCall() - .callsFake(restoreDNS('resolveTxt')); + .callsFake(restoreDNS('TXT')); }); afterEach(async function () { @@ -81,7 +79,7 @@ describe('DNS timeout errors', () => { describe('when SRV record look up times out twice', () => { beforeEach(() => { stub = sinon - .stub(dns.promises, 'resolveSrv') + .stub(dns.promises, 'resolve') .onFirstCall() .rejects(new DNSTimeoutError()) .onSecondCall() @@ -102,7 +100,7 @@ describe('DNS timeout errors', () => { describe('when TXT record look up times out twice', () => { beforeEach(() => { stub = sinon - .stub(dns.promises, 'resolveTxt') + .stub(dns.promises, 'resolve') .onFirstCall() .rejects(new DNSTimeoutError()) .onSecondCall() @@ -123,11 +121,11 @@ describe('DNS timeout errors', () => { describe('when SRV record look up throws a non-timeout error', () => { beforeEach(() => { stub = sinon - .stub(dns.promises, 'resolveSrv') + .stub(dns.promises, 'resolve') .onFirstCall() .rejects(new DNSSomethingError()) .onSecondCall() - .callsFake(restoreDNS('resolveSrv')); + .callsFake(restoreDNS('SRV')); }); afterEach(async function () { @@ -144,11 +142,11 @@ describe('DNS timeout errors', () => { describe('when TXT record look up throws a non-timeout error', () => { beforeEach(() => { stub = sinon - .stub(dns.promises, 'resolveTxt') + .stub(dns.promises, 'resolve') .onFirstCall() .rejects(new DNSSomethingError()) .onSecondCall() - .callsFake(restoreDNS('resolveTxt')); + .callsFake(restoreDNS('TXT')); }); afterEach(async function () { diff --git a/test/integration/initial-dns-seedlist-discovery/initial_dns_seedlist_discovery.prose.test.ts b/test/integration/initial-dns-seedlist-discovery/initial_dns_seedlist_discovery.prose.test.ts index c36e675ae58..992b43d9ca4 100644 --- a/test/integration/initial-dns-seedlist-discovery/initial_dns_seedlist_discovery.prose.test.ts +++ b/test/integration/initial-dns-seedlist-discovery/initial_dns_seedlist_discovery.prose.test.ts @@ -23,7 +23,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { beforeEach(async function () { // this fn stubs DNS resolution to always pass - so we are only checking pre-DNS validation - sinon.stub(dns.promises, 'resolveSrv').callsFake(async () => { + sinon.stub(dns.promises, 'resolve').callsFake(async () => { return [ { name: 'resolved.mongo.localhost', From e024ec6dbaec3f8004a7043149dac20714767ee6 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Wed, 4 Feb 2026 13:21:04 +0700 Subject: [PATCH 02/12] test: stub only resolve --- ...itial_dns_seedlist_discovery.prose.test.ts | 24 +++++++++---------- ...records_for_mongos_discovery.prose.test.ts | 2 +- test/unit/connection_string.test.ts | 8 +++---- test/unit/sdam/srv_polling.test.ts | 8 +++---- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/test/integration/initial-dns-seedlist-discovery/initial_dns_seedlist_discovery.prose.test.ts b/test/integration/initial-dns-seedlist-discovery/initial_dns_seedlist_discovery.prose.test.ts index 992b43d9ca4..fce47c89844 100644 --- a/test/integration/initial-dns-seedlist-discovery/initial_dns_seedlist_discovery.prose.test.ts +++ b/test/integration/initial-dns-seedlist-discovery/initial_dns_seedlist_discovery.prose.test.ts @@ -34,7 +34,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { ]; }); - sinon.stub(dns.promises, 'resolveTxt').callsFake(async () => { + sinon.stub(dns.promises, 'resolve').callsFake(async () => { throw { code: 'ENODATA' }; }); @@ -86,7 +86,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { */ beforeEach(async function () { - sinon.stub(dns.promises, 'resolveTxt').callsFake(async () => { + sinon.stub(dns.promises, 'resolve').callsFake(async () => { throw { code: 'ENODATA' }; }); }); @@ -96,7 +96,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with one domain level causes a runtime error', async function () { - sinon.stub(dns.promises, 'resolveSrv').callsFake(async () => { + sinon.stub(dns.promises, 'resolve').callsFake(async () => { return [ { name: 'localhost.mongodb', @@ -115,7 +115,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with two domain levels causes a runtime error', async function () { - sinon.stub(dns.promises, 'resolveSrv').callsFake(async () => { + sinon.stub(dns.promises, 'resolve').callsFake(async () => { return [ { name: 'test_1.evil.local', // this string only ends with part of the domain, not all of it! @@ -134,7 +134,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with three or more domain levels causes a runtime error', async function () { - sinon.stub(dns.promises, 'resolveSrv').callsFake(async () => { + sinon.stub(dns.promises, 'resolve').callsFake(async () => { return [ { name: 'blogs.evil.com', @@ -167,7 +167,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { 'when given a host from DNS resolution that is identical to the original SRVs hostname', function () { beforeEach(async function () { - sinon.stub(dns.promises, 'resolveTxt').callsFake(async () => { + sinon.stub(dns.promises, 'resolve').callsFake(async () => { throw { code: 'ENODATA' }; }); }); @@ -177,7 +177,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with one domain level causes a runtime error', async function () { - sinon.stub(dns.promises, 'resolveSrv').callsFake(async () => { + sinon.stub(dns.promises, 'resolve').callsFake(async () => { return [ { name: 'localhost', @@ -198,7 +198,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with two domain levels causes a runtime error', async function () { - sinon.stub(dns.promises, 'resolveSrv').callsFake(async () => { + sinon.stub(dns.promises, 'resolve').callsFake(async () => { return [ { name: 'mongo.local', @@ -234,7 +234,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { 'when given a returned address that does NOT share the domain name of the SRV record because its missing a `.`', function () { beforeEach(async function () { - sinon.stub(dns.promises, 'resolveTxt').callsFake(async () => { + sinon.stub(dns.promises, 'resolve').callsFake(async () => { throw { code: 'ENODATA' }; }); }); @@ -244,7 +244,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with one domain level causes a runtime error', async function () { - sinon.stub(dns.promises, 'resolveSrv').callsFake(async () => { + sinon.stub(dns.promises, 'resolve').callsFake(async () => { return [ { name: 'test_1.cluster_1localhost', @@ -263,7 +263,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with two domain levels causes a runtime error', async function () { - sinon.stub(dns.promises, 'resolveSrv').callsFake(async () => { + sinon.stub(dns.promises, 'resolve').callsFake(async () => { return [ { name: 'test_1.my_hostmongo.local', @@ -282,7 +282,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with three domain levels causes a runtime error', async function () { - sinon.stub(dns.promises, 'resolveSrv').callsFake(async () => { + sinon.stub(dns.promises, 'resolve').callsFake(async () => { return [ { name: 'cluster.testmongodb.com', diff --git a/test/unit/assorted/polling_srv_records_for_mongos_discovery.prose.test.ts b/test/unit/assorted/polling_srv_records_for_mongos_discovery.prose.test.ts index 4db98a5c7db..26997a23bf6 100644 --- a/test/unit/assorted/polling_srv_records_for_mongos_discovery.prose.test.ts +++ b/test/unit/assorted/polling_srv_records_for_mongos_discovery.prose.test.ts @@ -282,7 +282,7 @@ describe('Polling Srv Records for Mongos Discovery', () => { initialRecords ??= mockRecords; // first call is for the driver initial connection // second call will check the poller - resolveSrvStub = sinon.stub(dns.promises, 'resolveSrv').callsFake(async address => { + resolveSrvStub = sinon.stub(dns.promises, 'resolve').callsFake(async address => { expect(address).to.equal(`_${srvServiceName}._tcp.test.mock.test.build.10gen.cc`); if (initialDNSLookup) { initialDNSLookup = false; diff --git a/test/unit/connection_string.test.ts b/test/unit/connection_string.test.ts index 7b3cfdb9eda..4e0e27e9e18 100644 --- a/test/unit/connection_string.test.ts +++ b/test/unit/connection_string.test.ts @@ -631,13 +631,13 @@ describe('Connection String', function () { const mockRecord: string[][] = [[txtRecord]]; - // first call is for stubbing resolveSrv - // second call is for stubbing resolveTxt - sinon.stub(dns.promises, 'resolveSrv').callsFake(async () => { + // first call is for stubbing resolve + // second call is for stubbing resolve + sinon.stub(dns.promises, 'resolve').callsFake(async () => { return mockAddress; }); - sinon.stub(dns.promises, 'resolveTxt').callsFake(async () => { + sinon.stub(dns.promises, 'resolve').callsFake(async () => { return mockRecord; }); } diff --git a/test/unit/sdam/srv_polling.test.ts b/test/unit/sdam/srv_polling.test.ts index f38dfd646de..7d2e9915c45 100644 --- a/test/unit/sdam/srv_polling.test.ts +++ b/test/unit/sdam/srv_polling.test.ts @@ -28,9 +28,9 @@ describe('Mongos SRV Polling', function () { function stubDns(err: Error | null, records?: dns.SrvRecord[]) { if (err) { - sinon.stub(dns.promises, 'resolveSrv').rejects(err); + sinon.stub(dns.promises, 'resolve').rejects(err); } else { - sinon.stub(dns.promises, 'resolveSrv').resolves(records); + sinon.stub(dns.promises, 'resolve').resolves(records); } } @@ -93,13 +93,13 @@ describe('Mongos SRV Polling', function () { it('should poll dns srv records', async function () { const poller = new SrvPoller({ srvHost: SRV_HOST }); - sinon.stub(dns.promises, 'resolveSrv').resolves([srvRecord('iLoveJavascript.lots')]); + sinon.stub(dns.promises, 'resolve').resolves([srvRecord('iLoveJavascript.lots')]); await poller._poll(); clearTimeout(poller._timeout); - expect(dns.promises.resolveSrv).to.have.been.calledOnce.and.to.have.been.calledWith( + expect(dns.promises.resolve).to.have.been.calledOnce.and.to.have.been.calledWith( `_mongodb._tcp.${SRV_HOST}` ); }); From 3ade98f0d85f5a10c2e469533adb6ee25b58c75b Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Wed, 4 Feb 2026 14:20:42 +0700 Subject: [PATCH 03/12] test: fix gssapi tests --- test/unit/cmap/auth/gssapi.test.ts | 88 +++++++++++++++-------------- test/unit/connection_string.test.ts | 5 +- 2 files changed, 49 insertions(+), 44 deletions(-) diff --git a/test/unit/cmap/auth/gssapi.test.ts b/test/unit/cmap/auth/gssapi.test.ts index 9df2500b8e0..3a8c4db6715 100644 --- a/test/unit/cmap/auth/gssapi.test.ts +++ b/test/unit/cmap/auth/gssapi.test.ts @@ -10,13 +10,11 @@ import { describe('GSSAPI', () => { let lookupSpy; - let resolvePtrSpy; - let resolveCnameSpy; + let resolveSpy; beforeEach(() => { lookupSpy = sinon.spy(dns, 'lookup'); - resolvePtrSpy = sinon.spy(dns, 'resolvePtr'); - resolveCnameSpy = sinon.spy(dns, 'resolveCname'); + resolveSpy = sinon.spy(dns, 'resolve'); }); afterEach(() => { @@ -34,8 +32,8 @@ describe('GSSAPI', () => { }); expect(host).to.equal(hostName); expect(dns.lookup).to.not.be.called; - expect(dns.resolvePtr).to.not.be.called; - expect(dns.resolveCname).to.not.be.called; + expect(dns.resolve.withArgs(sinon.match.any, 'PTR')).to.not.be.called; + expect(dns.resolve.withArgs(sinon.match.any, 'CNAME')).to.not.be.called; }); }); } @@ -44,8 +42,8 @@ describe('GSSAPI', () => { const resolved = '10gen.cc'; beforeEach(() => { - resolveCnameSpy.restore(); - sinon.stub(dns, 'resolveCname').resolves([resolved]); + resolveSpy.restore(); + sinon.stub(dns, 'resolve').withArgs(sinon.match.any, 'CNAME').resolves([resolved]); }); it('performs a cname lookup', async () => { @@ -54,8 +52,8 @@ describe('GSSAPI', () => { }); expect(host).to.equal(resolved); expect(dns.lookup).to.not.be.called; - expect(dns.resolvePtr).to.not.be.called; - expect(dns.resolveCname).to.be.calledOnceWith(hostName); + expect(dns.resolve.withArgs(sinon.match.any, 'PTR')).to.not.be.called; + expect(dns.resolve).to.be.calledOnceWith(hostName, 'CNAME'); }); }); @@ -73,9 +71,9 @@ describe('GSSAPI', () => { beforeEach(() => { lookupSpy.restore(); - resolvePtrSpy.restore(); + resolveSpy.restore(); sinon.stub(dns, 'lookup').resolves(lookedUp); - sinon.stub(dns, 'resolvePtr').resolves([resolved]); + sinon.stub(dns, 'resolve').withArgs(sinon.match.any, 'PTR').resolves([resolved]); }); it('uses the reverse lookup host', async () => { @@ -84,8 +82,8 @@ describe('GSSAPI', () => { }); expect(host).to.equal(resolved); expect(dns.lookup).to.be.calledOnceWith(hostName); - expect(dns.resolvePtr).to.be.calledOnceWith(lookedUp.address); - expect(dns.resolveCname).to.not.be.called; + expect(dns.resolve).to.be.calledOnceWith(lookedUp.address, 'PTR'); + expect(dns.resolve).to.not.be.calledOnceWith(lookedUp.address, 'CNAME'); }); }); @@ -94,9 +92,12 @@ describe('GSSAPI', () => { beforeEach(() => { lookupSpy.restore(); - resolvePtrSpy.restore(); + resolveSpy.restore(); sinon.stub(dns, 'lookup').resolves(lookedUp); - sinon.stub(dns, 'resolvePtr').resolves([resolved, 'example.com']); + sinon + .stub(dns, 'resolve') + .withArgs(sinon.match.any, 'PTR') + .resolves([resolved, 'example.com']); }); it('uses the first found reverse lookup host', async () => { @@ -105,8 +106,8 @@ describe('GSSAPI', () => { }); expect(host).to.equal(resolved); expect(dns.lookup).to.be.calledOnceWith(hostName); - expect(dns.resolvePtr).to.be.calledOnceWith(lookedUp.address); - expect(dns.resolveCname).to.not.be.called; + expect(dns.resolve).to.be.calledOnceWith(lookedUp.address, 'PTR'); + expect(dns.resolve).to.not.be.calledOnceWith(sinon.match.any, 'CNAME'); }); }); }); @@ -116,11 +117,11 @@ describe('GSSAPI', () => { beforeEach(() => { lookupSpy.restore(); - resolvePtrSpy.restore(); - resolveCnameSpy.restore(); + resolveSpy.restore(); sinon.stub(dns, 'lookup').resolves(lookedUp); - sinon.stub(dns, 'resolvePtr').rejects(new Error('failed')); - sinon.stub(dns, 'resolveCname').resolves([cname]); + const stub = sinon.stub(dns, 'resolve'); + stub.withArgs(sinon.match.any, 'PTR').rejects(new Error('failed')); + stub.withArgs(sinon.match.any, 'CNAME').resolves([cname]); }); it('falls back to a cname lookup', async () => { @@ -130,17 +131,17 @@ describe('GSSAPI', () => { expect(host).to.equal(cname); expect(dns.lookup).to.be.calledOnceWith(hostName); - expect(dns.resolvePtr).to.be.calledOnceWith(lookedUp.address); - expect(dns.resolveCname).to.be.calledWith(hostName); + expect(dns.resolve).to.be.calledWith(lookedUp.address, 'PTR'); + expect(dns.resolve).to.be.calledWith(hostName, 'CNAME'); }); }); context('when the reverse lookup is empty', () => { beforeEach(() => { lookupSpy.restore(); - resolvePtrSpy.restore(); + resolveSpy.restore(); sinon.stub(dns, 'lookup').resolves(lookedUp); - sinon.stub(dns, 'resolvePtr').resolves([]); + sinon.stub(dns, 'resolve').withArgs(sinon.match.any, 'PTR').resolves([]); }); it('uses the provided host', async () => { @@ -149,8 +150,8 @@ describe('GSSAPI', () => { }); expect(host).to.equal(hostName); expect(dns.lookup).to.be.calledOnceWith(hostName); - expect(dns.resolvePtr).to.be.calledOnceWith(lookedUp.address); - expect(dns.resolveCname).to.not.be.called; + expect(dns.resolve).to.be.calledOnceWith(lookedUp.address, 'PTR'); + expect(dns.resolve).to.not.be.calledWith(sinon.match.any, 'CNAME'); }); }); }); @@ -168,8 +169,8 @@ describe('GSSAPI', () => { expect(error.message).to.equal('failed'); expect(dns.lookup).to.be.calledOnceWith(hostName); - expect(dns.resolvePtr).to.not.be.called; - expect(dns.resolveCname).to.not.be.called; + expect(dns.resolve).to.not.be.calledWith(sinon.match.any, 'PTR'); + expect(dns.resolve).to.not.be.calledWith(sinon.match.any, 'CNAME'); }); }); }); @@ -181,14 +182,14 @@ describe('GSSAPI', () => { const hostName = 'example.com'; beforeEach(() => { - resolveCnameSpy.restore(); - sinon.stub(dns, 'resolveCname').rejects(new Error('failed')); + resolveSpy.restore(); + sinon.stub(dns, 'resolve').withArgs(sinon.match.any, 'CNAME').rejects(new Error('failed')); }); it('falls back to the provided host name', async () => { const host = await resolveCname(hostName); expect(host).to.equal(hostName); - expect(dns.resolveCname).to.be.calledOnceWith(hostName); + expect(dns.resolve).to.be.calledOnceWith(hostName, 'CNAME'); }); }); @@ -198,14 +199,14 @@ describe('GSSAPI', () => { const resolved = '10gen.cc'; beforeEach(() => { - resolveCnameSpy.restore(); - sinon.stub(dns, 'resolveCname').resolves([resolved]); + resolveSpy.restore(); + sinon.stub(dns, 'resolve').withArgs(sinon.match.any, 'CNAME').resolves([resolved]); }); it('uses the result', async () => { const host = await resolveCname(hostName); expect(host).to.equal(resolved); - expect(dns.resolveCname).to.be.calledOnceWith(hostName); + expect(dns.resolve).to.be.calledOnceWith(hostName, 'CNAME'); }); }); @@ -214,14 +215,17 @@ describe('GSSAPI', () => { const resolved = '10gen.cc'; beforeEach(() => { - resolveCnameSpy.restore(); - sinon.stub(dns, 'resolveCname').resolves([resolved, hostName]); + resolveSpy.restore(); + sinon + .stub(dns, 'resolve') + .withArgs(sinon.match.any, 'CNAME') + .resolves([resolved, hostName]); }); it('uses the first result', async () => { const host = await resolveCname(hostName); expect(host).to.equal(resolved); - expect(dns.resolveCname).to.be.calledOnceWith(hostName); + expect(dns.resolve).to.be.calledOnceWith(hostName, 'CNAME'); }); }); }); @@ -230,14 +234,14 @@ describe('GSSAPI', () => { const hostName = 'example.com'; beforeEach(() => { - resolveCnameSpy.restore(); - sinon.stub(dns, 'resolveCname').resolves([]); + resolveSpy.restore(); + sinon.stub(dns, 'resolve').withArgs(sinon.match.any, 'CNAME').resolves([]); }); it('falls back to using the provided host', async () => { const host = await resolveCname(hostName); expect(host).to.equal(hostName); - expect(dns.resolveCname).to.be.calledOnceWith(hostName); + expect(dns.resolve).to.be.calledOnceWith(hostName, 'CNAME'); }); }); }); diff --git a/test/unit/connection_string.test.ts b/test/unit/connection_string.test.ts index 4e0e27e9e18..5ebe37a5bbb 100644 --- a/test/unit/connection_string.test.ts +++ b/test/unit/connection_string.test.ts @@ -633,11 +633,12 @@ describe('Connection String', function () { // first call is for stubbing resolve // second call is for stubbing resolve - sinon.stub(dns.promises, 'resolve').callsFake(async () => { + const stub = sinon.stub(dns.promises, 'resolve'); + stub.withArgs(sinon.match.any, 'SRV').callsFake(async () => { return mockAddress; }); - sinon.stub(dns.promises, 'resolve').callsFake(async () => { + stub.withArgs(sinon.match.any, 'TXT').callsFake(async () => { return mockRecord; }); } From 38b46c14f58d18d0d9fd4a19de1436ae9def7eef Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Wed, 4 Feb 2026 14:47:50 +0700 Subject: [PATCH 04/12] test: fix initial dns seedlist test --- ...itial_dns_seedlist_discovery.prose.test.ts | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/test/integration/initial-dns-seedlist-discovery/initial_dns_seedlist_discovery.prose.test.ts b/test/integration/initial-dns-seedlist-discovery/initial_dns_seedlist_discovery.prose.test.ts index fce47c89844..77ae55b0e2c 100644 --- a/test/integration/initial-dns-seedlist-discovery/initial_dns_seedlist_discovery.prose.test.ts +++ b/test/integration/initial-dns-seedlist-discovery/initial_dns_seedlist_discovery.prose.test.ts @@ -23,7 +23,8 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { beforeEach(async function () { // this fn stubs DNS resolution to always pass - so we are only checking pre-DNS validation - sinon.stub(dns.promises, 'resolve').callsFake(async () => { + const stub = sinon.stub(dns.promises, 'resolve'); + stub.withArgs(sinon.match.any, 'SRV').callsFake(async () => { return [ { name: 'resolved.mongo.localhost', @@ -34,7 +35,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { ]; }); - sinon.stub(dns.promises, 'resolve').callsFake(async () => { + stub.withArgs(sinon.match.any, 'TXT').callsFake(async () => { throw { code: 'ENODATA' }; }); @@ -84,9 +85,11 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { * - the SRV mongodb+srv://blogs.mongodb.com resolving to blogs.evil.com * Remember, the domain of an SRV with one or two . separated parts is the SRVs entire hostname. */ + let stub; beforeEach(async function () { - sinon.stub(dns.promises, 'resolve').callsFake(async () => { + stub = sinon.stub(dns.promises, 'resolve'); + stub.withArgs(sinon.match.any, 'TXT').callsFake(async () => { throw { code: 'ENODATA' }; }); }); @@ -96,7 +99,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with one domain level causes a runtime error', async function () { - sinon.stub(dns.promises, 'resolve').callsFake(async () => { + stub.withArgs(sinon.match.any, 'SRV').callsFake(async () => { return [ { name: 'localhost.mongodb', @@ -115,7 +118,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with two domain levels causes a runtime error', async function () { - sinon.stub(dns.promises, 'resolve').callsFake(async () => { + stub.withArgs(sinon.match.any, 'SRV').callsFake(async () => { return [ { name: 'test_1.evil.local', // this string only ends with part of the domain, not all of it! @@ -134,7 +137,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with three or more domain levels causes a runtime error', async function () { - sinon.stub(dns.promises, 'resolve').callsFake(async () => { + stub.withArgs(sinon.match.any, 'SRV').callsFake(async () => { return [ { name: 'blogs.evil.com', @@ -166,8 +169,11 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { context( 'when given a host from DNS resolution that is identical to the original SRVs hostname', function () { + let stub; + beforeEach(async function () { - sinon.stub(dns.promises, 'resolve').callsFake(async () => { + stub = sinon.stub(dns.promises, 'resolve'); + stub.withArgs(sinon.match.any, 'TXT').callsFake(async () => { throw { code: 'ENODATA' }; }); }); @@ -177,7 +183,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with one domain level causes a runtime error', async function () { - sinon.stub(dns.promises, 'resolve').callsFake(async () => { + stub.withArgs(sinon.match.any, 'SRV').callsFake(async () => { return [ { name: 'localhost', @@ -198,7 +204,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with two domain levels causes a runtime error', async function () { - sinon.stub(dns.promises, 'resolve').callsFake(async () => { + stub.withArgs(sinon.match.any, 'SRV').callsFake(async () => { return [ { name: 'mongo.local', @@ -233,8 +239,11 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { context( 'when given a returned address that does NOT share the domain name of the SRV record because its missing a `.`', function () { + let stub; + beforeEach(async function () { - sinon.stub(dns.promises, 'resolve').callsFake(async () => { + stub = sinon.stub(dns.promises, 'resolve'); + stub.withArgs(sinon.match.any, 'TXT').callsFake(async () => { throw { code: 'ENODATA' }; }); }); @@ -244,7 +253,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with one domain level causes a runtime error', async function () { - sinon.stub(dns.promises, 'resolve').callsFake(async () => { + stub.withArgs(sinon.match.any, 'SRV').callsFake(async () => { return [ { name: 'test_1.cluster_1localhost', @@ -263,7 +272,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with two domain levels causes a runtime error', async function () { - sinon.stub(dns.promises, 'resolve').callsFake(async () => { + stub.withArgs(sinon.match.any, 'SRV').callsFake(async () => { return [ { name: 'test_1.my_hostmongo.local', @@ -282,7 +291,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with three domain levels causes a runtime error', async function () { - sinon.stub(dns.promises, 'resolve').callsFake(async () => { + stub.withArgs(sinon.match.any, 'SRV').callsFake(async () => { return [ { name: 'cluster.testmongodb.com', From 9b5b0a84bd44dc1f2f27cf8b3ad61b52918a1cbc Mon Sep 17 00:00:00 2001 From: Sergey Zelenov Date: Fri, 6 Feb 2026 12:38:38 +0100 Subject: [PATCH 05/12] update sinon stubs --- src/connection_string.ts | 10 +-- .../dns_seedlist.test.ts | 66 ++++++++----------- 2 files changed, 32 insertions(+), 44 deletions(-) diff --git a/src/connection_string.ts b/src/connection_string.ts index ebe728f1b63..c73835ac21a 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -41,17 +41,17 @@ const LB_REPLICA_SET_ERROR = 'loadBalanced option not supported with a replicaSe const LB_DIRECT_CONNECTION_ERROR = 'loadBalanced option not supported when directConnection is provided'; -function retryDNSTimeoutFor(api: 'SRV'): (a: string) => Promise; -function retryDNSTimeoutFor(api: 'TXT'): (a: string) => Promise; +function retryDNSTimeoutFor(rrtype: 'SRV'): (a: string) => Promise; +function retryDNSTimeoutFor(rrtype: 'TXT'): (a: string) => Promise; function retryDNSTimeoutFor( - api: 'SRV' | 'TXT' + rrtype: 'SRV' | 'TXT' ): (a: string) => Promise { return async function dnsReqRetryTimeout(lookupAddress: string) { try { - return (await dns.promises.resolve(lookupAddress, api)) as dns.SrvRecord[] | string[][]; + return (await dns.promises.resolve(lookupAddress, rrtype)) as dns.SrvRecord[] | string[][]; } catch (firstDNSError) { if (firstDNSError.code === dns.TIMEOUT) { - return (await dns.promises.resolve(lookupAddress, api)) as dns.SrvRecord[] | string[][]; + return (await dns.promises.resolve(lookupAddress, rrtype)) as dns.SrvRecord[] | string[][]; } else { throw firstDNSError; } diff --git a/test/integration/initial-dns-seedlist-discovery/dns_seedlist.test.ts b/test/integration/initial-dns-seedlist-discovery/dns_seedlist.test.ts index 389333f6a37..a489319607c 100644 --- a/test/integration/initial-dns-seedlist-discovery/dns_seedlist.test.ts +++ b/test/integration/initial-dns-seedlist-discovery/dns_seedlist.test.ts @@ -31,25 +31,23 @@ describe('DNS timeout errors', () => { await client.close(); }); - const restoreDNS = api => async args => { - sinon.restore(); - return await dns.promises.resolve(args, api); + const restoreDNS = rrtype => async hostname => { + stub.restore(); + return await dns.promises.resolve(hostname, rrtype); }; describe('when SRV record look up times out', () => { beforeEach(() => { - stub = sinon - .stub(dns.promises, 'resolve') + stub = sinon.stub(dns.promises, 'resolve').callThrough(); + + stub + .withArgs(sinon.match.string, 'SRV') .onFirstCall() .rejects(new DNSTimeoutError()) .onSecondCall() .callsFake(restoreDNS('SRV')); }); - afterEach(async function () { - sinon.restore(); - }); - it('retries timeout error', metadata, async () => { await client.connect(); expect(stub).to.have.been.calledTwice; @@ -58,18 +56,16 @@ describe('DNS timeout errors', () => { describe('when TXT record look up times out', () => { beforeEach(() => { - stub = sinon - .stub(dns.promises, 'resolve') + stub = sinon.stub(dns.promises, 'resolve').callThrough(); + + stub + .withArgs(sinon.match.string, 'TXT') .onFirstCall() .rejects(new DNSTimeoutError()) .onSecondCall() .callsFake(restoreDNS('TXT')); }); - afterEach(async function () { - sinon.restore(); - }); - it('retries timeout error', metadata, async () => { await client.connect(); expect(stub).to.have.been.calledTwice; @@ -78,18 +74,16 @@ describe('DNS timeout errors', () => { describe('when SRV record look up times out twice', () => { beforeEach(() => { - stub = sinon - .stub(dns.promises, 'resolve') + stub = sinon.stub(dns.promises, 'resolve').callThrough(); + + stub + .withArgs(sinon.match.string, 'SRV') .onFirstCall() .rejects(new DNSTimeoutError()) .onSecondCall() .rejects(new DNSTimeoutError()); }); - afterEach(async function () { - sinon.restore(); - }); - it('throws timeout error', metadata, async () => { const error = await client.connect().catch(error => error); expect(error).to.be.instanceOf(DNSTimeoutError); @@ -99,18 +93,16 @@ describe('DNS timeout errors', () => { describe('when TXT record look up times out twice', () => { beforeEach(() => { - stub = sinon - .stub(dns.promises, 'resolve') + stub = sinon.stub(dns.promises, 'resolve').callThrough(); + + stub + .withArgs(sinon.match.string, 'TXT') .onFirstCall() .rejects(new DNSTimeoutError()) .onSecondCall() .rejects(new DNSTimeoutError()); }); - afterEach(async function () { - sinon.restore(); - }); - it('throws timeout error', metadata, async () => { const error = await client.connect().catch(error => error); expect(error).to.be.instanceOf(DNSTimeoutError); @@ -120,18 +112,16 @@ describe('DNS timeout errors', () => { describe('when SRV record look up throws a non-timeout error', () => { beforeEach(() => { - stub = sinon - .stub(dns.promises, 'resolve') + stub = sinon.stub(dns.promises, 'resolve').callThrough(); + + stub + .withArgs(sinon.match.string, 'SRV') .onFirstCall() .rejects(new DNSSomethingError()) .onSecondCall() .callsFake(restoreDNS('SRV')); }); - afterEach(async function () { - sinon.restore(); - }); - it('throws that error', metadata, async () => { const error = await client.connect().catch(error => error); expect(error).to.be.instanceOf(DNSSomethingError); @@ -141,18 +131,16 @@ describe('DNS timeout errors', () => { describe('when TXT record look up throws a non-timeout error', () => { beforeEach(() => { - stub = sinon - .stub(dns.promises, 'resolve') + stub = sinon.stub(dns.promises, 'resolve').callThrough(); + + stub + .withArgs(sinon.match.string, 'TXT') .onFirstCall() .rejects(new DNSSomethingError()) .onSecondCall() .callsFake(restoreDNS('TXT')); }); - afterEach(async function () { - sinon.restore(); - }); - it('throws that error', metadata, async () => { const error = await client.connect().catch(error => error); expect(error).to.be.instanceOf(DNSSomethingError); From e7e43d35dc5de396845f3238b70d1bedab1913b5 Mon Sep 17 00:00:00 2001 From: Sergey Zelenov Date: Fri, 6 Feb 2026 15:56:06 +0100 Subject: [PATCH 06/12] make sure to check stubs with arguments --- .../dns_seedlist.test.ts | 25 ++++--------- ...itial_dns_seedlist_discovery.prose.test.ts | 26 ++++++------- test/unit/cmap/auth/gssapi.test.ts | 37 ++++++++++--------- 3 files changed, 40 insertions(+), 48 deletions(-) diff --git a/test/integration/initial-dns-seedlist-discovery/dns_seedlist.test.ts b/test/integration/initial-dns-seedlist-discovery/dns_seedlist.test.ts index a489319607c..c9cfc5d0f2c 100644 --- a/test/integration/initial-dns-seedlist-discovery/dns_seedlist.test.ts +++ b/test/integration/initial-dns-seedlist-discovery/dns_seedlist.test.ts @@ -23,6 +23,7 @@ describe('DNS timeout errors', () => { beforeEach(async function () { client = new MongoClient(CONNECTION_STRING, { serverSelectionTimeoutMS: 2000, tls: false }); + stub = sinon.stub(dns.promises, 'resolve').callThrough(); }); afterEach(async function () { @@ -38,8 +39,6 @@ describe('DNS timeout errors', () => { describe('when SRV record look up times out', () => { beforeEach(() => { - stub = sinon.stub(dns.promises, 'resolve').callThrough(); - stub .withArgs(sinon.match.string, 'SRV') .onFirstCall() @@ -50,14 +49,12 @@ describe('DNS timeout errors', () => { it('retries timeout error', metadata, async () => { await client.connect(); - expect(stub).to.have.been.calledTwice; + expect(stub.withArgs(sinon.match.string, 'SRV')).to.have.been.calledTwice; }); }); describe('when TXT record look up times out', () => { beforeEach(() => { - stub = sinon.stub(dns.promises, 'resolve').callThrough(); - stub .withArgs(sinon.match.string, 'TXT') .onFirstCall() @@ -68,14 +65,12 @@ describe('DNS timeout errors', () => { it('retries timeout error', metadata, async () => { await client.connect(); - expect(stub).to.have.been.calledTwice; + expect(stub.withArgs(sinon.match.string, 'TXT')).to.have.been.calledTwice; }); }); describe('when SRV record look up times out twice', () => { beforeEach(() => { - stub = sinon.stub(dns.promises, 'resolve').callThrough(); - stub .withArgs(sinon.match.string, 'SRV') .onFirstCall() @@ -87,14 +82,12 @@ describe('DNS timeout errors', () => { it('throws timeout error', metadata, async () => { const error = await client.connect().catch(error => error); expect(error).to.be.instanceOf(DNSTimeoutError); - expect(stub).to.have.been.calledTwice; + expect(stub.withArgs(sinon.match.string, 'SRV')).to.have.been.calledTwice; }); }); describe('when TXT record look up times out twice', () => { beforeEach(() => { - stub = sinon.stub(dns.promises, 'resolve').callThrough(); - stub .withArgs(sinon.match.string, 'TXT') .onFirstCall() @@ -106,14 +99,12 @@ describe('DNS timeout errors', () => { it('throws timeout error', metadata, async () => { const error = await client.connect().catch(error => error); expect(error).to.be.instanceOf(DNSTimeoutError); - expect(stub).to.have.been.calledTwice; + expect(stub.withArgs(sinon.match.string, 'TXT')).to.have.been.calledTwice; }); }); describe('when SRV record look up throws a non-timeout error', () => { beforeEach(() => { - stub = sinon.stub(dns.promises, 'resolve').callThrough(); - stub .withArgs(sinon.match.string, 'SRV') .onFirstCall() @@ -125,14 +116,12 @@ describe('DNS timeout errors', () => { it('throws that error', metadata, async () => { const error = await client.connect().catch(error => error); expect(error).to.be.instanceOf(DNSSomethingError); - expect(stub).to.have.been.calledOnce; + expect(stub.withArgs(sinon.match.string, 'TXT')).to.have.been.calledOnce; }); }); describe('when TXT record look up throws a non-timeout error', () => { beforeEach(() => { - stub = sinon.stub(dns.promises, 'resolve').callThrough(); - stub .withArgs(sinon.match.string, 'TXT') .onFirstCall() @@ -144,7 +133,7 @@ describe('DNS timeout errors', () => { it('throws that error', metadata, async () => { const error = await client.connect().catch(error => error); expect(error).to.be.instanceOf(DNSSomethingError); - expect(stub).to.have.been.calledOnce; + expect(stub.withArgs(sinon.match.string, 'TXT')).to.have.been.calledOnce; }); }); }); diff --git a/test/integration/initial-dns-seedlist-discovery/initial_dns_seedlist_discovery.prose.test.ts b/test/integration/initial-dns-seedlist-discovery/initial_dns_seedlist_discovery.prose.test.ts index 77ae55b0e2c..dc879ecb085 100644 --- a/test/integration/initial-dns-seedlist-discovery/initial_dns_seedlist_discovery.prose.test.ts +++ b/test/integration/initial-dns-seedlist-discovery/initial_dns_seedlist_discovery.prose.test.ts @@ -24,7 +24,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { // this fn stubs DNS resolution to always pass - so we are only checking pre-DNS validation const stub = sinon.stub(dns.promises, 'resolve'); - stub.withArgs(sinon.match.any, 'SRV').callsFake(async () => { + stub.withArgs(sinon.match.string, 'SRV').callsFake(async () => { return [ { name: 'resolved.mongo.localhost', @@ -35,7 +35,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { ]; }); - stub.withArgs(sinon.match.any, 'TXT').callsFake(async () => { + stub.withArgs(sinon.match.string, 'TXT').callsFake(async () => { throw { code: 'ENODATA' }; }); @@ -89,7 +89,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { beforeEach(async function () { stub = sinon.stub(dns.promises, 'resolve'); - stub.withArgs(sinon.match.any, 'TXT').callsFake(async () => { + stub.withArgs(sinon.match.string, 'TXT').callsFake(async () => { throw { code: 'ENODATA' }; }); }); @@ -99,7 +99,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with one domain level causes a runtime error', async function () { - stub.withArgs(sinon.match.any, 'SRV').callsFake(async () => { + stub.withArgs(sinon.match.string, 'SRV').callsFake(async () => { return [ { name: 'localhost.mongodb', @@ -118,7 +118,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with two domain levels causes a runtime error', async function () { - stub.withArgs(sinon.match.any, 'SRV').callsFake(async () => { + stub.withArgs(sinon.match.string, 'SRV').callsFake(async () => { return [ { name: 'test_1.evil.local', // this string only ends with part of the domain, not all of it! @@ -137,7 +137,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with three or more domain levels causes a runtime error', async function () { - stub.withArgs(sinon.match.any, 'SRV').callsFake(async () => { + stub.withArgs(sinon.match.string, 'SRV').callsFake(async () => { return [ { name: 'blogs.evil.com', @@ -173,7 +173,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { beforeEach(async function () { stub = sinon.stub(dns.promises, 'resolve'); - stub.withArgs(sinon.match.any, 'TXT').callsFake(async () => { + stub.withArgs(sinon.match.string, 'TXT').callsFake(async () => { throw { code: 'ENODATA' }; }); }); @@ -183,7 +183,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with one domain level causes a runtime error', async function () { - stub.withArgs(sinon.match.any, 'SRV').callsFake(async () => { + stub.withArgs(sinon.match.string, 'SRV').callsFake(async () => { return [ { name: 'localhost', @@ -204,7 +204,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with two domain levels causes a runtime error', async function () { - stub.withArgs(sinon.match.any, 'SRV').callsFake(async () => { + stub.withArgs(sinon.match.string, 'SRV').callsFake(async () => { return [ { name: 'mongo.local', @@ -243,7 +243,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { beforeEach(async function () { stub = sinon.stub(dns.promises, 'resolve'); - stub.withArgs(sinon.match.any, 'TXT').callsFake(async () => { + stub.withArgs(sinon.match.string, 'TXT').callsFake(async () => { throw { code: 'ENODATA' }; }); }); @@ -253,7 +253,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with one domain level causes a runtime error', async function () { - stub.withArgs(sinon.match.any, 'SRV').callsFake(async () => { + stub.withArgs(sinon.match.string, 'SRV').callsFake(async () => { return [ { name: 'test_1.cluster_1localhost', @@ -272,7 +272,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with two domain levels causes a runtime error', async function () { - stub.withArgs(sinon.match.any, 'SRV').callsFake(async () => { + stub.withArgs(sinon.match.string, 'SRV').callsFake(async () => { return [ { name: 'test_1.my_hostmongo.local', @@ -291,7 +291,7 @@ describe('Initial DNS Seedlist Discovery (Prose Tests)', () => { }); it('an SRV with three domain levels causes a runtime error', async function () { - stub.withArgs(sinon.match.any, 'SRV').callsFake(async () => { + stub.withArgs(sinon.match.string, 'SRV').callsFake(async () => { return [ { name: 'cluster.testmongodb.com', diff --git a/test/unit/cmap/auth/gssapi.test.ts b/test/unit/cmap/auth/gssapi.test.ts index 3a8c4db6715..b0a6fe4eafb 100644 --- a/test/unit/cmap/auth/gssapi.test.ts +++ b/test/unit/cmap/auth/gssapi.test.ts @@ -32,8 +32,8 @@ describe('GSSAPI', () => { }); expect(host).to.equal(hostName); expect(dns.lookup).to.not.be.called; - expect(dns.resolve.withArgs(sinon.match.any, 'PTR')).to.not.be.called; - expect(dns.resolve.withArgs(sinon.match.any, 'CNAME')).to.not.be.called; + expect(dns.resolve.withArgs(sinon.match.string, 'PTR')).to.not.be.called; + expect(dns.resolve.withArgs(sinon.match.string, 'CNAME')).to.not.be.called; }); }); } @@ -43,7 +43,7 @@ describe('GSSAPI', () => { beforeEach(() => { resolveSpy.restore(); - sinon.stub(dns, 'resolve').withArgs(sinon.match.any, 'CNAME').resolves([resolved]); + sinon.stub(dns, 'resolve').withArgs(sinon.match.string, 'CNAME').resolves([resolved]); }); it('performs a cname lookup', async () => { @@ -52,7 +52,7 @@ describe('GSSAPI', () => { }); expect(host).to.equal(resolved); expect(dns.lookup).to.not.be.called; - expect(dns.resolve.withArgs(sinon.match.any, 'PTR')).to.not.be.called; + expect(dns.resolve.withArgs(sinon.match.string, 'PTR')).to.not.be.called; expect(dns.resolve).to.be.calledOnceWith(hostName, 'CNAME'); }); }); @@ -73,7 +73,7 @@ describe('GSSAPI', () => { lookupSpy.restore(); resolveSpy.restore(); sinon.stub(dns, 'lookup').resolves(lookedUp); - sinon.stub(dns, 'resolve').withArgs(sinon.match.any, 'PTR').resolves([resolved]); + sinon.stub(dns, 'resolve').withArgs(sinon.match.string, 'PTR').resolves([resolved]); }); it('uses the reverse lookup host', async () => { @@ -96,7 +96,7 @@ describe('GSSAPI', () => { sinon.stub(dns, 'lookup').resolves(lookedUp); sinon .stub(dns, 'resolve') - .withArgs(sinon.match.any, 'PTR') + .withArgs(sinon.match.string, 'PTR') .resolves([resolved, 'example.com']); }); @@ -107,7 +107,7 @@ describe('GSSAPI', () => { expect(host).to.equal(resolved); expect(dns.lookup).to.be.calledOnceWith(hostName); expect(dns.resolve).to.be.calledOnceWith(lookedUp.address, 'PTR'); - expect(dns.resolve).to.not.be.calledOnceWith(sinon.match.any, 'CNAME'); + expect(dns.resolve).to.not.be.calledOnceWith(sinon.match.string, 'CNAME'); }); }); }); @@ -120,8 +120,8 @@ describe('GSSAPI', () => { resolveSpy.restore(); sinon.stub(dns, 'lookup').resolves(lookedUp); const stub = sinon.stub(dns, 'resolve'); - stub.withArgs(sinon.match.any, 'PTR').rejects(new Error('failed')); - stub.withArgs(sinon.match.any, 'CNAME').resolves([cname]); + stub.withArgs(sinon.match.string, 'PTR').rejects(new Error('failed')); + stub.withArgs(sinon.match.string, 'CNAME').resolves([cname]); }); it('falls back to a cname lookup', async () => { @@ -141,7 +141,7 @@ describe('GSSAPI', () => { lookupSpy.restore(); resolveSpy.restore(); sinon.stub(dns, 'lookup').resolves(lookedUp); - sinon.stub(dns, 'resolve').withArgs(sinon.match.any, 'PTR').resolves([]); + sinon.stub(dns, 'resolve').withArgs(sinon.match.string, 'PTR').resolves([]); }); it('uses the provided host', async () => { @@ -151,7 +151,7 @@ describe('GSSAPI', () => { expect(host).to.equal(hostName); expect(dns.lookup).to.be.calledOnceWith(hostName); expect(dns.resolve).to.be.calledOnceWith(lookedUp.address, 'PTR'); - expect(dns.resolve).to.not.be.calledWith(sinon.match.any, 'CNAME'); + expect(dns.resolve).to.not.be.calledWith(sinon.match.string, 'CNAME'); }); }); }); @@ -169,8 +169,8 @@ describe('GSSAPI', () => { expect(error.message).to.equal('failed'); expect(dns.lookup).to.be.calledOnceWith(hostName); - expect(dns.resolve).to.not.be.calledWith(sinon.match.any, 'PTR'); - expect(dns.resolve).to.not.be.calledWith(sinon.match.any, 'CNAME'); + expect(dns.resolve).to.not.be.calledWith(sinon.match.string, 'PTR'); + expect(dns.resolve).to.not.be.calledWith(sinon.match.string, 'CNAME'); }); }); }); @@ -183,7 +183,10 @@ describe('GSSAPI', () => { beforeEach(() => { resolveSpy.restore(); - sinon.stub(dns, 'resolve').withArgs(sinon.match.any, 'CNAME').rejects(new Error('failed')); + sinon + .stub(dns, 'resolve') + .withArgs(sinon.match.string, 'CNAME') + .rejects(new Error('failed')); }); it('falls back to the provided host name', async () => { @@ -200,7 +203,7 @@ describe('GSSAPI', () => { beforeEach(() => { resolveSpy.restore(); - sinon.stub(dns, 'resolve').withArgs(sinon.match.any, 'CNAME').resolves([resolved]); + sinon.stub(dns, 'resolve').withArgs(sinon.match.string, 'CNAME').resolves([resolved]); }); it('uses the result', async () => { @@ -218,7 +221,7 @@ describe('GSSAPI', () => { resolveSpy.restore(); sinon .stub(dns, 'resolve') - .withArgs(sinon.match.any, 'CNAME') + .withArgs(sinon.match.string, 'CNAME') .resolves([resolved, hostName]); }); @@ -235,7 +238,7 @@ describe('GSSAPI', () => { beforeEach(() => { resolveSpy.restore(); - sinon.stub(dns, 'resolve').withArgs(sinon.match.any, 'CNAME').resolves([]); + sinon.stub(dns, 'resolve').withArgs(sinon.match.string, 'CNAME').resolves([]); }); it('falls back to using the provided host', async () => { From 52aa2e929b0a20c94f3c8763e9a890e2cdc74839 Mon Sep 17 00:00:00 2001 From: Sergey Zelenov Date: Fri, 6 Feb 2026 16:26:58 +0100 Subject: [PATCH 07/12] run kerberos tests only --- test/manual/kerberos.test.ts | 48 +++++++++++++++++------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/test/manual/kerberos.test.ts b/test/manual/kerberos.test.ts index 3260f455eb3..2ed578a24d9 100644 --- a/test/manual/kerberos.test.ts +++ b/test/manual/kerberos.test.ts @@ -16,15 +16,13 @@ async function verifyKerberosAuthentication(client) { expect(docs).to.have.nested.property('[0].kerberos', true); } -describe('Kerberos', function () { - let resolvePtrSpy; - let resolveCnameSpy; +describe.only('Kerberos', function () { + let resolveSpy; let client; beforeEach(() => { sinon.spy(dns, 'lookup'); - resolvePtrSpy = sinon.spy(dns, 'resolvePtr'); - resolveCnameSpy = sinon.spy(dns, 'resolveCname'); + resolveSpy = sinon.spy(dns, 'resolve'); }); afterEach(function () { @@ -65,7 +63,7 @@ describe('Kerberos', function () { `${krb5Uri}&authMechanismProperties=SERVICE_NAME:mongodb,CANONICALIZE_HOST_NAME:forward&maxPoolSize=1` ); await client.connect(); - expect(dns.resolveCname).to.be.calledOnceWith(host); + expect(resolveSpy.withArgs(sinon.match.string, 'SRV')).to.be.calledOnceWith(host); await verifyKerberosAuthentication(client); }); }); @@ -77,7 +75,7 @@ describe('Kerberos', function () { `${krb5Uri}&authMechanismProperties=SERVICE_NAME:mongodb,CANONICALIZE_HOST_NAME:${option}&maxPoolSize=1` ); await client.connect(); - expect(dns.resolveCname).to.not.be.called; + expect(resolveSpy.withArgs('CNAME')).to.not.be.called; // There are 2 calls to establish connection, however they use the callback form of dns.lookup expect(dns.lookup).to.not.be.called; await verifyKerberosAuthentication(client); @@ -89,8 +87,8 @@ describe('Kerberos', function () { context(`when the value is ${option}`, function () { context('when the reverse lookup succeeds', function () { beforeEach(function () { - resolvePtrSpy.restore(); - sinon.stub(dns, 'resolvePtr').resolves([host]); + resolveSpy.restore(); + resolveSpy.withArgs(sinon.match.string, 'PTR').resolves([host]); }); it('authenticates with a forward dns lookup and a reverse ptr lookup', async function () { @@ -101,15 +99,15 @@ describe('Kerberos', function () { // There are 2 calls to establish connection, however they use the callback form of dns.lookup // 1 dns.promises.lookup call in canonicalization. expect(dns.lookup).to.be.calledOnce; - expect(dns.resolvePtr).to.be.calledOnce; + expect(resolveSpy.withArgs(sinon.match.string, 'PTR')).to.be.calledOnce; await verifyKerberosAuthentication(client); }); }); context('when the reverse lookup is empty', function () { beforeEach(function () { - resolvePtrSpy.restore(); - sinon.stub(dns, 'resolvePtr').resolves([]); + resolveSpy.restore(); + resolveSpy.withArgs(sinon.match.string, 'PTR').resolves([]); }); it('authenticates with a fallback cname lookup', async function () { @@ -122,17 +120,17 @@ describe('Kerberos', function () { // 1 dns.promises.lookup call in canonicalization. expect(dns.lookup).to.be.calledOnce; // This fails. - expect(dns.resolvePtr).to.be.calledOnce; + expect(resolveSpy.withArgs(sinon.match.string, 'PTR')).to.be.calledOnce; // Expect the fallback to the host name. - expect(dns.resolveCname).to.not.be.called; + expect(resolveSpy.withArgs(sinon.match.string, 'CNAME')).to.not.be.called; await verifyKerberosAuthentication(client); }); }); context('when the reverse lookup fails', function () { beforeEach(function () { - resolvePtrSpy.restore(); - sinon.stub(dns, 'resolvePtr').rejects(new Error('not found')); + resolveSpy.restore(); + resolveSpy.withArgs(sinon.match.string, 'PTR').rejects(new Error('not found')); }); it('authenticates with a fallback cname lookup', async function () { @@ -145,17 +143,17 @@ describe('Kerberos', function () { // 1 dns.promises.lookup call in canonicalization. expect(dns.lookup).to.be.calledOnce; // This fails. - expect(dns.resolvePtr).to.be.calledOnce; + expect(resolveSpy.withArgs(sinon.match.string, 'PTR')).to.be.calledOnce; // Expect the fallback to be called. - expect(dns.resolveCname).to.be.calledOnceWith(host); + expect(resolveSpy.withArgs(sinon.match.string, 'CNAME')).to.be.calledOnceWith(host); await verifyKerberosAuthentication(client); }); }); context('when the cname lookup fails', function () { beforeEach(function () { - resolveCnameSpy.restore(); - sinon.stub(dns, 'resolveCname').rejects(new Error('not found')); + resolveSpy.restore(); + resolveSpy.withArgs(sinon.match.string, 'CNAME').rejects(new Error('not found')); }); it('authenticates with a fallback host name', async function () { @@ -167,16 +165,16 @@ describe('Kerberos', function () { // 1 dns.promises.lookup call in canonicalization. expect(dns.lookup).to.be.calledOnce; // This fails. - expect(dns.resolvePtr).to.be.calledOnce; + expect(resolveSpy.withArgs(sinon.match.string, 'PTR')).to.be.calledOnce; // Expect the fallback to be called. - expect(dns.resolveCname).to.be.calledOnceWith(host); + expect(resolveSpy.withArgs(sinon.match.string, 'CNAME')).to.be.calledOnceWith(host); await verifyKerberosAuthentication(client); }); }); context('when the cname lookup is empty', function () { beforeEach(function () { - resolveCnameSpy.restore(); + resolveSpy.restore(); sinon.stub(dns, 'resolveCname').resolves([]); }); @@ -189,9 +187,9 @@ describe('Kerberos', function () { // 1 dns.promises.lookup call in canonicalization. expect(dns.lookup).to.be.calledOnce; // This fails. - expect(dns.resolvePtr).to.be.calledOnce; + expect(resolveSpy.withArgs(sinon.match.string, 'PTR')).to.be.calledOnce; // Expect the fallback to be called. - expect(dns.resolveCname).to.be.calledOnceWith(host); + expect(resolveSpy.withArgs(sinon.match.string, 'CNAME')).to.be.calledOnceWith(host); await verifyKerberosAuthentication(client); }); }); From 68008babb6140815d66f228aef821cac5f86085a Mon Sep 17 00:00:00 2001 From: Sergey Zelenov Date: Fri, 6 Feb 2026 19:03:52 +0100 Subject: [PATCH 08/12] fix kerberos tests --- test/manual/kerberos.test.ts | 60 ++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/test/manual/kerberos.test.ts b/test/manual/kerberos.test.ts index 2ed578a24d9..4afca48924b 100644 --- a/test/manual/kerberos.test.ts +++ b/test/manual/kerberos.test.ts @@ -17,12 +17,13 @@ async function verifyKerberosAuthentication(client) { } describe.only('Kerberos', function () { - let resolveSpy; + let resolveStub; + let lookupStub; let client; beforeEach(() => { - sinon.spy(dns, 'lookup'); - resolveSpy = sinon.spy(dns, 'resolve'); + lookupStub = sinon.stub(dns, 'lookup').callThrough(); + resolveStub = sinon.stub(dns, 'resolve').callThrough(); }); afterEach(function () { @@ -63,7 +64,7 @@ describe.only('Kerberos', function () { `${krb5Uri}&authMechanismProperties=SERVICE_NAME:mongodb,CANONICALIZE_HOST_NAME:forward&maxPoolSize=1` ); await client.connect(); - expect(resolveSpy.withArgs(sinon.match.string, 'SRV')).to.be.calledOnceWith(host); + expect(resolveStub.withArgs(sinon.match.any, 'CNAME')).to.be.calledOnceWith(host); await verifyKerberosAuthentication(client); }); }); @@ -75,9 +76,10 @@ describe.only('Kerberos', function () { `${krb5Uri}&authMechanismProperties=SERVICE_NAME:mongodb,CANONICALIZE_HOST_NAME:${option}&maxPoolSize=1` ); await client.connect(); - expect(resolveSpy.withArgs('CNAME')).to.not.be.called; + + expect(resolveStub.withArgs(sinon.match.any, 'CNAME')).to.not.be.called; // There are 2 calls to establish connection, however they use the callback form of dns.lookup - expect(dns.lookup).to.not.be.called; + expect(lookupStub).to.not.be.called; await verifyKerberosAuthentication(client); }); }); @@ -86,28 +88,23 @@ describe.only('Kerberos', function () { for (const option of [true, 'forwardAndReverse']) { context(`when the value is ${option}`, function () { context('when the reverse lookup succeeds', function () { - beforeEach(function () { - resolveSpy.restore(); - resolveSpy.withArgs(sinon.match.string, 'PTR').resolves([host]); - }); - it('authenticates with a forward dns lookup and a reverse ptr lookup', async function () { client = new MongoClient( `${krb5Uri}&authMechanismProperties=SERVICE_NAME:mongodb,CANONICALIZE_HOST_NAME:${option}&maxPoolSize=1` ); await client.connect(); + // There are 2 calls to establish connection, however they use the callback form of dns.lookup // 1 dns.promises.lookup call in canonicalization. - expect(dns.lookup).to.be.calledOnce; - expect(resolveSpy.withArgs(sinon.match.string, 'PTR')).to.be.calledOnce; + expect(lookupStub).to.be.calledOnce; + expect(resolveStub.withArgs(sinon.match.any, 'PTR')).to.be.calledOnce; await verifyKerberosAuthentication(client); }); }); context('when the reverse lookup is empty', function () { beforeEach(function () { - resolveSpy.restore(); - resolveSpy.withArgs(sinon.match.string, 'PTR').resolves([]); + resolveStub.withArgs(sinon.match.string, 'PTR').resolves([]); }); it('authenticates with a fallback cname lookup', async function () { @@ -118,19 +115,18 @@ describe.only('Kerberos', function () { await client.connect(); // There are 2 calls to establish connection, however they use the callback form of dns.lookup // 1 dns.promises.lookup call in canonicalization. - expect(dns.lookup).to.be.calledOnce; + expect(lookupStub).to.be.calledOnce; // This fails. - expect(resolveSpy.withArgs(sinon.match.string, 'PTR')).to.be.calledOnce; + expect(resolveStub.withArgs(sinon.match.string, 'PTR')).to.be.calledOnce; // Expect the fallback to the host name. - expect(resolveSpy.withArgs(sinon.match.string, 'CNAME')).to.not.be.called; + expect(resolveStub.withArgs(sinon.match.string, 'CNAME')).to.not.be.called; await verifyKerberosAuthentication(client); }); }); context('when the reverse lookup fails', function () { beforeEach(function () { - resolveSpy.restore(); - resolveSpy.withArgs(sinon.match.string, 'PTR').rejects(new Error('not found')); + resolveStub.withArgs(sinon.match.string, 'PTR').rejects(new Error('not found')); }); it('authenticates with a fallback cname lookup', async function () { @@ -141,19 +137,18 @@ describe.only('Kerberos', function () { await client.connect(); // There are 2 calls to establish connection, however they use the callback form of dns.lookup // 1 dns.promises.lookup call in canonicalization. - expect(dns.lookup).to.be.calledOnce; + expect(lookupStub).to.be.calledOnce; // This fails. - expect(resolveSpy.withArgs(sinon.match.string, 'PTR')).to.be.calledOnce; + expect(resolveStub.withArgs(sinon.match.string, 'PTR')).to.be.calledOnce; // Expect the fallback to be called. - expect(resolveSpy.withArgs(sinon.match.string, 'CNAME')).to.be.calledOnceWith(host); + expect(resolveStub.withArgs(sinon.match.string, 'CNAME')).to.be.calledOnceWith(host); await verifyKerberosAuthentication(client); }); }); context('when the cname lookup fails', function () { beforeEach(function () { - resolveSpy.restore(); - resolveSpy.withArgs(sinon.match.string, 'CNAME').rejects(new Error('not found')); + resolveStub.withArgs(sinon.match.string, 'CNAME').rejects(new Error('not found')); }); it('authenticates with a fallback host name', async function () { @@ -163,19 +158,18 @@ describe.only('Kerberos', function () { await client.connect(); // There are 2 calls to establish connection, however they use the callback form of dns.lookup // 1 dns.promises.lookup call in canonicalization. - expect(dns.lookup).to.be.calledOnce; + expect(lookupStub).to.be.calledOnce; // This fails. - expect(resolveSpy.withArgs(sinon.match.string, 'PTR')).to.be.calledOnce; + expect(resolveStub.withArgs(sinon.match.string, 'PTR')).to.be.calledOnce; // Expect the fallback to be called. - expect(resolveSpy.withArgs(sinon.match.string, 'CNAME')).to.be.calledOnceWith(host); + expect(resolveStub.withArgs(sinon.match.string, 'CNAME')).to.be.calledOnceWith(host); await verifyKerberosAuthentication(client); }); }); context('when the cname lookup is empty', function () { beforeEach(function () { - resolveSpy.restore(); - sinon.stub(dns, 'resolveCname').resolves([]); + resolveStub.withArgs(sinon.match.string, 'CNAME').rejects([]); }); it('authenticates with a fallback host name', async function () { @@ -185,11 +179,11 @@ describe.only('Kerberos', function () { await client.connect(); // There are 2 calls to establish connection, however they use the callback form of dns.lookup // 1 dns.promises.lookup call in canonicalization. - expect(dns.lookup).to.be.calledOnce; + expect(lookupStub).to.be.calledOnce; // This fails. - expect(resolveSpy.withArgs(sinon.match.string, 'PTR')).to.be.calledOnce; + expect(resolveStub.withArgs(sinon.match.string, 'PTR')).to.be.calledOnce; // Expect the fallback to be called. - expect(resolveSpy.withArgs(sinon.match.string, 'CNAME')).to.be.calledOnceWith(host); + expect(resolveStub.withArgs(sinon.match.string, 'CNAME')).to.be.calledOnceWith(host); await verifyKerberosAuthentication(client); }); }); From bb166696e394a1d072780037f77edb877ee768cd Mon Sep 17 00:00:00 2001 From: Sergey Zelenov Date: Mon, 9 Feb 2026 10:54:24 +0100 Subject: [PATCH 09/12] run all tests --- test/manual/kerberos.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/manual/kerberos.test.ts b/test/manual/kerberos.test.ts index 4afca48924b..9ed66a04ddc 100644 --- a/test/manual/kerberos.test.ts +++ b/test/manual/kerberos.test.ts @@ -16,7 +16,7 @@ async function verifyKerberosAuthentication(client) { expect(docs).to.have.nested.property('[0].kerberos', true); } -describe.only('Kerberos', function () { +describe('Kerberos', function () { let resolveStub; let lookupStub; let client; From 481b7b1415b21f2b71fe9f340162ffb73354bcd7 Mon Sep 17 00:00:00 2001 From: Sergey Zelenov Date: Tue, 17 Feb 2026 12:56:58 +0100 Subject: [PATCH 10/12] address typescript type comment --- src/connection_string.ts | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/connection_string.ts b/src/connection_string.ts index c73835ac21a..c3fda78a8ee 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -41,20 +41,24 @@ const LB_REPLICA_SET_ERROR = 'loadBalanced option not supported with a replicaSe const LB_DIRECT_CONNECTION_ERROR = 'loadBalanced option not supported when directConnection is provided'; -function retryDNSTimeoutFor(rrtype: 'SRV'): (a: string) => Promise; -function retryDNSTimeoutFor(rrtype: 'TXT'): (a: string) => Promise; -function retryDNSTimeoutFor( - rrtype: 'SRV' | 'TXT' -): (a: string) => Promise { +// connect the rrtype to the expected result +interface DNSLookupMap { + SRV: dns.SrvRecord[]; + TXT: string[][]; +} +function retryDNSTimeoutFor( + rrtype: T +): (lookupAddress: string) => Promise { return async function dnsReqRetryTimeout(lookupAddress: string) { + const resolve = () => dns.promises.resolve(lookupAddress, rrtype) as Promise; + try { - return (await dns.promises.resolve(lookupAddress, rrtype)) as dns.SrvRecord[] | string[][]; + return await resolve(); } catch (firstDNSError) { - if (firstDNSError.code === dns.TIMEOUT) { - return (await dns.promises.resolve(lookupAddress, rrtype)) as dns.SrvRecord[] | string[][]; - } else { - throw firstDNSError; + if (firstDNSError.code === 'ETIMEOUT') { + return await resolve(); } + throw firstDNSError; } }; } From 9a631da0e3cc39ace538aeefe116cfd199bd9910 Mon Sep 17 00:00:00 2001 From: Sergey Zelenov Date: Wed, 18 Feb 2026 16:09:20 +0100 Subject: [PATCH 11/12] fix typo in test --- .../initial-dns-seedlist-discovery/dns_seedlist.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/initial-dns-seedlist-discovery/dns_seedlist.test.ts b/test/integration/initial-dns-seedlist-discovery/dns_seedlist.test.ts index c9cfc5d0f2c..cc0981fa492 100644 --- a/test/integration/initial-dns-seedlist-discovery/dns_seedlist.test.ts +++ b/test/integration/initial-dns-seedlist-discovery/dns_seedlist.test.ts @@ -116,7 +116,7 @@ describe('DNS timeout errors', () => { it('throws that error', metadata, async () => { const error = await client.connect().catch(error => error); expect(error).to.be.instanceOf(DNSSomethingError); - expect(stub.withArgs(sinon.match.string, 'TXT')).to.have.been.calledOnce; + expect(stub.withArgs(sinon.match.string, 'SRV')).to.have.been.calledOnce; }); }); From c342496587076c4ddd6a27befc5362e0e13ee342 Mon Sep 17 00:00:00 2001 From: Sergey Zelenov Date: Wed, 18 Feb 2026 17:25:39 +0100 Subject: [PATCH 12/12] avoid ts casting --- src/connection_string.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/connection_string.ts b/src/connection_string.ts index c3fda78a8ee..0fb7394e77b 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -41,24 +41,24 @@ const LB_REPLICA_SET_ERROR = 'loadBalanced option not supported with a replicaSe const LB_DIRECT_CONNECTION_ERROR = 'loadBalanced option not supported when directConnection is provided'; -// connect the rrtype to the expected result -interface DNSLookupMap { - SRV: dns.SrvRecord[]; - TXT: string[][]; -} -function retryDNSTimeoutFor( - rrtype: T -): (lookupAddress: string) => Promise { +function retryDNSTimeoutFor(rrtype: 'SRV'): (lookupAddress: string) => Promise; +function retryDNSTimeoutFor(rrtype: 'TXT'): (lookupAddress: string) => Promise; +function retryDNSTimeoutFor( + rrtype: 'SRV' | 'TXT' +): (lookupAddress: string) => Promise { + const resolve = + rrtype === 'SRV' + ? (address: string) => dns.promises.resolve(address, 'SRV') + : (address: string) => dns.promises.resolve(address, 'TXT'); return async function dnsReqRetryTimeout(lookupAddress: string) { - const resolve = () => dns.promises.resolve(lookupAddress, rrtype) as Promise; - try { - return await resolve(); + return await resolve(lookupAddress); } catch (firstDNSError) { - if (firstDNSError.code === 'ETIMEOUT') { - return await resolve(); + if (firstDNSError.code === dns.TIMEOUT) { + return await resolve(lookupAddress); + } else { + throw firstDNSError; } - throw firstDNSError; } }; }