From 7b2a5652674f740584c0ae33dadf4436a4fe5311 Mon Sep 17 00:00:00 2001 From: Warren James Date: Mon, 9 Sep 2024 16:17:23 -0400 Subject: [PATCH 01/14] WIP --- src/cursor/abstract_cursor.ts | 4 -- .../crud/find_cursor_methods.test.js | 51 +++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index 4525ae19fe4..a63571ab079 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -314,10 +314,6 @@ export abstract class AbstractCursor< } async *[Symbol.asyncIterator](): AsyncGenerator { - if (this.isClosed) { - return; - } - try { while (true) { if (this.isKilled) { diff --git a/test/integration/crud/find_cursor_methods.test.js b/test/integration/crud/find_cursor_methods.test.js index 6e7066d5faa..21ac1a4151b 100644 --- a/test/integration/crud/find_cursor_methods.test.js +++ b/test/integration/crud/find_cursor_methods.test.js @@ -361,4 +361,55 @@ describe('Find Cursor', function () { } }); }); + + describe.only('next + Symbol.asyncIterator()', function () { + let client; + let collection; + + before(async function () { + client = this.configuration.newClient(); + await client.connect(); + collection = client.db('next-symbolasynciterator').collection('bar'); + await collection.deleteMany({}, { writeConcern: { w: 'majority' } }); + await collection.insertMany([{ a: 1 }, { a: 2 }], { writeConcern: { w: 'majority' } }); + }); + + after(async function () { + await client.close(); + }); + + // fails + it('allows combining iteration modes', async function () { + let count = 0; + const cursor = collection.find().map(doc => { + count++; + return doc; + }); + + await cursor.next(); + for await (const _ of cursor) { + /* empty */ + } + + expect(count).to.equal(2); + }); + + // passes + it('works with next + next() loop', async function () { + let count = 0; + const cursor = collection.find().map(doc => { + count++; + return doc; + }); + + await cursor.next(); + + let doc; + while ((doc = (await cursor.next()) && doc != null)) { + /** empty */ + } + + expect(count).to.equal(2); + }); + }); }); From bfae9d8dd2abfa7de982b49146c764d5476264c9 Mon Sep 17 00:00:00 2001 From: Warren James Date: Tue, 10 Sep 2024 13:47:16 -0400 Subject: [PATCH 02/14] restore old behaviour for investigation --- src/cursor/abstract_cursor.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index a63571ab079..4525ae19fe4 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -314,6 +314,10 @@ export abstract class AbstractCursor< } async *[Symbol.asyncIterator](): AsyncGenerator { + if (this.isClosed) { + return; + } + try { while (true) { if (this.isKilled) { From 9a5539af840a0d75ba60665d64e46eed632af35e Mon Sep 17 00:00:00 2001 From: Warren James Date: Tue, 10 Sep 2024 14:09:11 -0400 Subject: [PATCH 03/14] WIP: comment out early return --- src/cursor/abstract_cursor.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index 4525ae19fe4..ff95c00e84d 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -314,9 +314,11 @@ export abstract class AbstractCursor< } async *[Symbol.asyncIterator](): AsyncGenerator { + /* if (this.isClosed) { return; } + */ try { while (true) { From 8af54ebcfd6fd7ebead9656859d4a04e70ef3f59 Mon Sep 17 00:00:00 2001 From: Warren James Date: Tue, 10 Sep 2024 15:55:34 -0400 Subject: [PATCH 04/14] fix for mixed API use --- src/cursor/abstract_cursor.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index ff95c00e84d..fc8b1794da2 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -314,11 +314,9 @@ export abstract class AbstractCursor< } async *[Symbol.asyncIterator](): AsyncGenerator { - /* if (this.isClosed) { return; } - */ try { while (true) { @@ -468,6 +466,7 @@ export abstract class AbstractCursor< * Frees any client-side resources used by the cursor. */ async close(): Promise { + this.isClosed = true; await this.cleanup(); } @@ -783,7 +782,6 @@ export abstract class AbstractCursor< /** @internal */ private async cleanup(error?: Error) { - this.isClosed = true; const session = this.cursorSession; try { if ( From 472c390199f1c62a7910617c549e280fd05db364 Mon Sep 17 00:00:00 2001 From: Warren James Date: Tue, 10 Sep 2024 21:57:11 -0400 Subject: [PATCH 05/14] change early exit condition --- src/cursor/abstract_cursor.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index fc8b1794da2..e330e4f1e4f 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -314,7 +314,7 @@ export abstract class AbstractCursor< } async *[Symbol.asyncIterator](): AsyncGenerator { - if (this.isClosed) { + if (this.closed) { return; } @@ -466,7 +466,6 @@ export abstract class AbstractCursor< * Frees any client-side resources used by the cursor. */ async close(): Promise { - this.isClosed = true; await this.cleanup(); } @@ -782,6 +781,7 @@ export abstract class AbstractCursor< /** @internal */ private async cleanup(error?: Error) { + this.isClosed = true; const session = this.cursorSession; try { if ( From 63887bad507140eb158174ccf05f1b39e1a852dd Mon Sep 17 00:00:00 2001 From: Warren James Date: Tue, 10 Sep 2024 21:58:02 -0400 Subject: [PATCH 06/14] add tests --- .../crud/find_cursor_methods.test.js | 88 +++++++++++++------ 1 file changed, 62 insertions(+), 26 deletions(-) diff --git a/test/integration/crud/find_cursor_methods.test.js b/test/integration/crud/find_cursor_methods.test.js index 21ac1a4151b..85932a5f517 100644 --- a/test/integration/crud/find_cursor_methods.test.js +++ b/test/integration/crud/find_cursor_methods.test.js @@ -362,11 +362,12 @@ describe('Find Cursor', function () { }); }); - describe.only('next + Symbol.asyncIterator()', function () { + describe('next + Symbol.asyncIterator()', function () { let client; let collection; + let cursor; - before(async function () { + beforeEach(async function () { client = this.configuration.newClient(); await client.connect(); collection = client.db('next-symbolasynciterator').collection('bar'); @@ -374,42 +375,77 @@ describe('Find Cursor', function () { await collection.insertMany([{ a: 1 }, { a: 2 }], { writeConcern: { w: 'majority' } }); }); - after(async function () { + afterEach(async function () { + await cursor.close(); await client.close(); }); - // fails - it('allows combining iteration modes', async function () { - let count = 0; - const cursor = collection.find().map(doc => { - count++; - return doc; + context('when all documents are retrieved in the first batch', function () { + it('allows combining iteration modes', async function () { + let count = 0; + cursor = collection.find().map(doc => { + count++; + return doc; + }); + + await cursor.next(); + for await (const _ of cursor) { + /* empty */ + } + + expect(count).to.equal(2); }); - await cursor.next(); - for await (const _ of cursor) { - /* empty */ - } + it('works with next + next() loop', async function () { + let count = 0; + cursor = collection.find().map(doc => { + count++; + return doc; + }); + + await cursor.next(); + + let doc; + while ((doc = (await cursor.next()) && doc != null)) { + /** empty */ + } - expect(count).to.equal(2); + expect(count).to.equal(2); + }); }); - // passes - it('works with next + next() loop', async function () { - let count = 0; - const cursor = collection.find().map(doc => { - count++; - return doc; + context('when there are documents are not retrieved in the first batch', function () { + it('allows combining iteration modes', async function () { + let count = 0; + cursor = collection.find({}, { batchSize: 1 }).map(doc => { + count++; + return doc; + }); + + await cursor.next(); + for await (const _ of cursor) { + /* empty */ + } + + expect(count).to.equal(2); }); - await cursor.next(); + it('works with next + next() loop', async function () { + let count = 0; + cursor = collection.find({}, { batchSize: 1 }).map(doc => { + count++; + return doc; + }); - let doc; - while ((doc = (await cursor.next()) && doc != null)) { - /** empty */ - } + await cursor.next(); - expect(count).to.equal(2); + let doc; + while ((doc = (await cursor.next()) && doc != null)) { + /** empty */ + } + + expect(count).to.equal(2); + }); }); }); }); From 9579af6a9254c8d51995ed3276847c9d7a843d12 Mon Sep 17 00:00:00 2001 From: Warren James Date: Tue, 10 Sep 2024 22:41:26 -0400 Subject: [PATCH 07/14] lint --- test/integration/crud/find_cursor_methods.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/integration/crud/find_cursor_methods.test.js b/test/integration/crud/find_cursor_methods.test.js index 85932a5f517..5001c9c6749 100644 --- a/test/integration/crud/find_cursor_methods.test.js +++ b/test/integration/crud/find_cursor_methods.test.js @@ -389,6 +389,7 @@ describe('Find Cursor', function () { }); await cursor.next(); + // eslint-disable-next-line no-unused-vars for await (const _ of cursor) { /* empty */ } @@ -423,6 +424,7 @@ describe('Find Cursor', function () { }); await cursor.next(); + // eslint-disable-next-line no-unused-vars for await (const _ of cursor) { /* empty */ } From c676991b426cd7799ac252d1a9b3a1c0f34b37fb Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 11 Sep 2024 13:23:42 -0400 Subject: [PATCH 08/14] support mixed iteration --- src/cursor/abstract_cursor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index 51206b51a27..c1be6587d85 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -412,7 +412,7 @@ export abstract class AbstractCursor< /** Get the next available document from the cursor, returns null if no more documents are available. */ async next(): Promise { - if (this.cursorId === Long.ZERO) { + if (this.cursorId === Long.ZERO && (this.documents?.length ?? 0) === 0) { throw new MongoCursorExhaustedError(); } From bfd5385bf0dbd57192a1d10ff2900d21357b0828 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 11 Sep 2024 13:24:06 -0400 Subject: [PATCH 09/14] add new tests --- .../crud/find_cursor_methods.test.js | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/test/integration/crud/find_cursor_methods.test.js b/test/integration/crud/find_cursor_methods.test.js index 5001c9c6749..06787ce9124 100644 --- a/test/integration/crud/find_cursor_methods.test.js +++ b/test/integration/crud/find_cursor_methods.test.js @@ -416,7 +416,7 @@ describe('Find Cursor', function () { }); context('when there are documents are not retrieved in the first batch', function () { - it('allows combining iteration modes', async function () { + it('allows combining next() and for await syntax', async function () { let count = 0; cursor = collection.find({}, { batchSize: 1 }).map(doc => { count++; @@ -432,6 +432,24 @@ describe('Find Cursor', function () { expect(count).to.equal(2); }); + it.only('allows partial iteration with for await syntax and then calling .next()', async function () { + let count = 0; + cursor = collection.find({}, { batchSize: 2 }).map(doc => { + count++; + return doc; + }); + + for await (const doc of cursor) { + console.log(doc); + /* empty */ + break; + } + + await cursor.next(); + + expect(count).to.equal(2); + }); + it('works with next + next() loop', async function () { let count = 0; cursor = collection.find({}, { batchSize: 1 }).map(doc => { From 8d4ba51a698cdf2eb169266b155513fb67d777b3 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 11 Sep 2024 14:51:22 -0400 Subject: [PATCH 10/14] remove behaviour change --- src/cursor/abstract_cursor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index c1be6587d85..51206b51a27 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -412,7 +412,7 @@ export abstract class AbstractCursor< /** Get the next available document from the cursor, returns null if no more documents are available. */ async next(): Promise { - if (this.cursorId === Long.ZERO && (this.documents?.length ?? 0) === 0) { + if (this.cursorId === Long.ZERO) { throw new MongoCursorExhaustedError(); } From c5cae7b213afeac7c510795c8a3a00b407eebeb2 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 11 Sep 2024 14:52:26 -0400 Subject: [PATCH 11/14] remove only --- test/integration/crud/find_cursor_methods.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/crud/find_cursor_methods.test.js b/test/integration/crud/find_cursor_methods.test.js index 06787ce9124..0aa87cfd6b1 100644 --- a/test/integration/crud/find_cursor_methods.test.js +++ b/test/integration/crud/find_cursor_methods.test.js @@ -432,7 +432,7 @@ describe('Find Cursor', function () { expect(count).to.equal(2); }); - it.only('allows partial iteration with for await syntax and then calling .next()', async function () { + it('allows partial iteration with for await syntax and then calling .next()', async function () { let count = 0; cursor = collection.find({}, { batchSize: 2 }).map(doc => { count++; From 1a3d2b19113b86689c3e50a18247a40b4223caa4 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 11 Sep 2024 16:20:32 -0400 Subject: [PATCH 12/14] add tests characterizing cursor iteration API mixing --- .../crud/find_cursor_methods.test.js | 392 ++++++++++++++++-- 1 file changed, 367 insertions(+), 25 deletions(-) diff --git a/test/integration/crud/find_cursor_methods.test.js b/test/integration/crud/find_cursor_methods.test.js index 0aa87cfd6b1..c6ebe574d00 100644 --- a/test/integration/crud/find_cursor_methods.test.js +++ b/test/integration/crud/find_cursor_methods.test.js @@ -1,6 +1,11 @@ 'use strict'; const { expect } = require('chai'); const { filterForCommands } = require('../shared'); +const { + promiseWithResolvers, + MongoExpiredSessionError, + MongoCursorExhaustedError +} = require('../../mongodb'); describe('Find Cursor', function () { let client; @@ -362,7 +367,7 @@ describe('Find Cursor', function () { }); }); - describe('next + Symbol.asyncIterator()', function () { + describe('mixing iteration APIs', function () { let client; let collection; let cursor; @@ -413,9 +418,182 @@ describe('Find Cursor', function () { expect(count).to.equal(2); }); + + context('when next() is called in a loop after a single invocation', function () { + it('iterates over all documents', async function () { + let count = 0; + cursor = collection.find({}).map(doc => { + count++; + return doc; + }); + + await cursor.next(); + + let doc; + while ((doc = (await cursor.next()) && doc != null)) { + /** empty */ + } + + expect(count).to.equal(2); + }); + }); + + context( + 'when cursor.next() is called after cursor.stream() is partially iterated', + function () { + it('returns null', async function () { + cursor = collection.find({}); + + const stream = cursor.stream(); + const { promise, resolve, reject } = promiseWithResolvers(); + + stream.once('data', v => { + resolve(v); + }); + + stream.once('end', v => { + resolve(v); + }); + + stream.once('error', v => { + reject(v); + }); + await promise; + + expect(await cursor.next()).to.be.null; + }); + } + ); + + context('when cursor.tryNext() is called after cursor.stream()', function () { + it('returns null', async function () { + cursor = collection.find({}); + + const stream = cursor.stream(); + const { promise, resolve, reject } = promiseWithResolvers(); + + stream.once('data', v => { + resolve(v); + }); + + stream.once('end', v => { + resolve(v); + }); + + stream.once('error', v => { + reject(v); + }); + await promise; + + expect(await cursor.tryNext()).to.be.null; + }); + }); + + context( + 'when cursor.[Symbol.asyncIterator] is called after cursor.stream() is partly iterated', + function () { + it('returns an empty iterator', async function () { + cursor = collection.find({}); + + const stream = cursor.stream(); + const { promise, resolve, reject } = promiseWithResolvers(); + + stream.once('data', v => { + resolve(v); + }); + + stream.once('end', v => { + resolve(v); + }); + + stream.once('error', v => { + reject(v); + }); + await promise; + + let count = 0; + // eslint-disable-next-line no-unused-vars + for await (const _ of cursor) { + count++; + } + + expect(count).to.equal(0); + }); + } + ); + + context('when cursor.readBufferedDocuments() is called after cursor.next()', function () { + it('returns an array with remaining buffered documents', async function () { + cursor = collection.find({}); + + await cursor.next(); + const docs = cursor.readBufferedDocuments(); + + expect(docs).to.have.lengthOf(1); + }); + }); + + context('when cursor.next() is called after cursor.toArray()', function () { + it('returns null', async function () { + cursor = collection.find({}); + + await cursor.toArray(); + expect(await cursor.next()).to.be.null; + }); + }); + + context('when cursor.tryNext is called after cursor.toArray()', function () { + it('returns null', async function () { + cursor = collection.find({}); + + await cursor.toArray(); + expect(await cursor.tryNext()).to.be.null; + }); + }); + + context('when cursor.[Symbol.asyncIterator] is called after cursor.toArray()', function () { + it('should not iterate', async function () { + cursor = collection.find({}); + + await cursor.toArray(); + // eslint-disable-next-line no-unused-vars + for await (const _ of cursor) { + expect.fail('should not iterate'); + } + }); + }); + + context('when cursor.readBufferedDocuments() is called after cursor.toArray()', function () { + it('return and empty array', async function () { + cursor = collection.find({}); + + await cursor.toArray(); + expect(cursor.readBufferedDocuments()).to.have.lengthOf(0); + }); + }); + + context('when cursor.stream() is called after cursor.toArray()', function () { + it('returns an empty stream', async function () { + cursor = collection.find({}); + await cursor.toArray(); + + const s = cursor.stream(); + const { promise, resolve } = promiseWithResolvers(); + + s.once('data', d => { + resolve(d); + }); + + s.once('end', d => { + resolve(d); + }); + + expect(await promise).to.be.undefined; + }); + }); }); - context('when there are documents are not retrieved in the first batch', function () { + context('when there are documents that are not retrieved in the first batch', function () { it('allows combining next() and for await syntax', async function () { let count = 0; cursor = collection.find({}, { batchSize: 1 }).map(doc => { @@ -432,39 +610,203 @@ describe('Find Cursor', function () { expect(count).to.equal(2); }); - it('allows partial iteration with for await syntax and then calling .next()', async function () { - let count = 0; - cursor = collection.find({}, { batchSize: 2 }).map(doc => { - count++; - return doc; + context( + 'when a cursor is partially iterated with for await and then .next() is called', + function () { + it('throws a MongoCursorExhaustedError', async function () { + cursor = collection.find({}, { batchSize: 1 }); + + // eslint-disable-next-line no-unused-vars + for await (const _ of cursor) { + /* empty */ + break; + } + + const maybeError = await cursor.next().then( + () => null, + e => e + ); + expect(maybeError).to.be.instanceof(MongoCursorExhaustedError); + }); + } + ); + + context('when next() is called in a loop after a single invocation', function () { + it('iterates over all documents', async function () { + let count = 0; + cursor = collection.find({}, { batchSize: 1 }).map(doc => { + count++; + return doc; + }); + + await cursor.next(); + + let doc; + while ((doc = (await cursor.next()) && doc != null)) { + /** empty */ + } + + expect(count).to.equal(2); }); + }); - for await (const doc of cursor) { - console.log(doc); - /* empty */ - break; - } + context('when cursor.next() is called after cursor.stream()', function () { + it('throws a MongoExpiredSessionError', async function () { + cursor = collection.find({}, { batchSize: 1 }); - await cursor.next(); + const stream = cursor.stream(); + const { promise, resolve, reject } = promiseWithResolvers(); - expect(count).to.equal(2); + stream.once('data', v => { + resolve(v); + }); + + stream.once('end', v => { + resolve(v); + }); + + stream.once('error', v => { + reject(v); + }); + await promise; + + const maybeError = await cursor.next().then( + () => null, + e => e + ); + + expect(maybeError).to.be.instanceof(MongoExpiredSessionError); + }); }); - it('works with next + next() loop', async function () { - let count = 0; - cursor = collection.find({}, { batchSize: 1 }).map(doc => { - count++; - return doc; + context('when cursor.tryNext() is called after cursor.stream()', function () { + it('throws a MongoExpiredSessionError', async function () { + cursor = collection.find({}, { batchSize: 1 }); + + const stream = cursor.stream(); + const { promise, resolve, reject } = promiseWithResolvers(); + + stream.once('data', v => { + resolve(v); + }); + + stream.once('end', v => { + resolve(v); + }); + + stream.once('error', v => { + reject(v); + }); + await promise; + + const maybeError = await cursor.tryNext().then( + () => null, + e => e + ); + + expect(maybeError).to.be.instanceof(MongoExpiredSessionError); }); + }); - await cursor.next(); + context('when cursor.[Symbol.asyncIterator] is called after cursor.stream()', function () { + it('throws a MongoExpiredSessionError', async function () { + cursor = collection.find({}, { batchSize: 1 }); - let doc; - while ((doc = (await cursor.next()) && doc != null)) { - /** empty */ - } + const stream = cursor.stream(); + const { promise, resolve, reject } = promiseWithResolvers(); - expect(count).to.equal(2); + stream.once('data', v => { + resolve(v); + }); + + stream.once('end', v => { + resolve(v); + }); + + stream.once('error', v => { + reject(v); + }); + await promise; + + try { + // eslint-disable-next-line no-unused-vars, no-empty + for await (const _ of cursor) { + } + expect.fail('expected to throw'); + } catch (err) { + expect(err).to.be.instanceof(MongoExpiredSessionError); + } + }); + }); + + context('when cursor.readBufferedDocuments() is called after cursor.next()', function () { + it('returns an empty array', async function () { + cursor = collection.find({}, { batchSize: 1 }); + + await cursor.next(); + const docs = cursor.readBufferedDocuments(); + + expect(docs).to.have.lengthOf(0); + }); + }); + + context('when cursor.next() is called after cursor.toArray()', function () { + it('returns null', async function () { + cursor = collection.find({}, { batchSize: 1 }); + + await cursor.toArray(); + expect(await cursor.next()).to.be.null; + }); + }); + + context('when cursor.tryNext is called after cursor.toArray()', function () { + it('returns null', async function () { + cursor = collection.find({}, { batchSize: 1 }); + + await cursor.toArray(); + expect(await cursor.tryNext()).to.be.null; + }); + }); + + context('when cursor.[Symbol.asyncIterator] is called after cursor.toArray()', function () { + it('should not iterate', async function () { + cursor = collection.find({}, { batchSize: 1 }); + + await cursor.toArray(); + // eslint-disable-next-line no-unused-vars + for await (const _ of cursor) { + expect.fail('should not iterate'); + } + }); + }); + + context('when cursor.readBufferedDocuments() is called after cursor.toArray()', function () { + it('return and empty array', async function () { + cursor = collection.find({}, { batchSize: 1 }); + + await cursor.toArray(); + expect(cursor.readBufferedDocuments()).to.have.lengthOf(0); + }); + }); + + context('when cursor.stream() is called after cursor.toArray()', function () { + it('returns an empty stream', async function () { + cursor = collection.find({}, { batchSize: 1 }); + await cursor.toArray(); + + const s = cursor.stream(); + const { promise, resolve } = promiseWithResolvers(); + + s.once('data', d => { + resolve(d); + }); + + s.once('end', d => { + resolve(d); + }); + + expect(await promise).to.be.undefined; + }); }); }); }); From ea7b1737751a0b0c86c3bd7460fedf3691b7efec Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 11 Sep 2024 17:29:38 -0400 Subject: [PATCH 13/14] test fixes --- .../crud/find_cursor_methods.test.js | 109 +----------------- 1 file changed, 4 insertions(+), 105 deletions(-) diff --git a/test/integration/crud/find_cursor_methods.test.js b/test/integration/crud/find_cursor_methods.test.js index c6ebe574d00..812096999a7 100644 --- a/test/integration/crud/find_cursor_methods.test.js +++ b/test/integration/crud/find_cursor_methods.test.js @@ -451,10 +451,6 @@ describe('Find Cursor', function () { resolve(v); }); - stream.once('end', v => { - resolve(v); - }); - stream.once('error', v => { reject(v); }); @@ -476,10 +472,6 @@ describe('Find Cursor', function () { resolve(v); }); - stream.once('end', v => { - resolve(v); - }); - stream.once('error', v => { reject(v); }); @@ -502,10 +494,6 @@ describe('Find Cursor', function () { resolve(v); }); - stream.once('end', v => { - resolve(v); - }); - stream.once('error', v => { reject(v); }); @@ -578,10 +566,10 @@ describe('Find Cursor', function () { await cursor.toArray(); const s = cursor.stream(); - const { promise, resolve } = promiseWithResolvers(); + const { promise, resolve, reject } = promiseWithResolvers(); s.once('data', d => { - resolve(d); + reject(d); }); s.once('end', d => { @@ -650,95 +638,6 @@ describe('Find Cursor', function () { }); }); - context('when cursor.next() is called after cursor.stream()', function () { - it('throws a MongoExpiredSessionError', async function () { - cursor = collection.find({}, { batchSize: 1 }); - - const stream = cursor.stream(); - const { promise, resolve, reject } = promiseWithResolvers(); - - stream.once('data', v => { - resolve(v); - }); - - stream.once('end', v => { - resolve(v); - }); - - stream.once('error', v => { - reject(v); - }); - await promise; - - const maybeError = await cursor.next().then( - () => null, - e => e - ); - - expect(maybeError).to.be.instanceof(MongoExpiredSessionError); - }); - }); - - context('when cursor.tryNext() is called after cursor.stream()', function () { - it('throws a MongoExpiredSessionError', async function () { - cursor = collection.find({}, { batchSize: 1 }); - - const stream = cursor.stream(); - const { promise, resolve, reject } = promiseWithResolvers(); - - stream.once('data', v => { - resolve(v); - }); - - stream.once('end', v => { - resolve(v); - }); - - stream.once('error', v => { - reject(v); - }); - await promise; - - const maybeError = await cursor.tryNext().then( - () => null, - e => e - ); - - expect(maybeError).to.be.instanceof(MongoExpiredSessionError); - }); - }); - - context('when cursor.[Symbol.asyncIterator] is called after cursor.stream()', function () { - it('throws a MongoExpiredSessionError', async function () { - cursor = collection.find({}, { batchSize: 1 }); - - const stream = cursor.stream(); - const { promise, resolve, reject } = promiseWithResolvers(); - - stream.once('data', v => { - resolve(v); - }); - - stream.once('end', v => { - resolve(v); - }); - - stream.once('error', v => { - reject(v); - }); - await promise; - - try { - // eslint-disable-next-line no-unused-vars, no-empty - for await (const _ of cursor) { - } - expect.fail('expected to throw'); - } catch (err) { - expect(err).to.be.instanceof(MongoExpiredSessionError); - } - }); - }); - context('when cursor.readBufferedDocuments() is called after cursor.next()', function () { it('returns an empty array', async function () { cursor = collection.find({}, { batchSize: 1 }); @@ -795,10 +694,10 @@ describe('Find Cursor', function () { await cursor.toArray(); const s = cursor.stream(); - const { promise, resolve } = promiseWithResolvers(); + const { promise, resolve, reject } = promiseWithResolvers(); s.once('data', d => { - resolve(d); + reject(d); }); s.once('end', d => { From 6f33e40e1682374e5aaeb4e502fdeed7a8c793b2 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 11 Sep 2024 17:50:37 -0400 Subject: [PATCH 14/14] lint... again --- test/integration/crud/find_cursor_methods.test.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/integration/crud/find_cursor_methods.test.js b/test/integration/crud/find_cursor_methods.test.js index 812096999a7..42eeda3e816 100644 --- a/test/integration/crud/find_cursor_methods.test.js +++ b/test/integration/crud/find_cursor_methods.test.js @@ -1,11 +1,7 @@ 'use strict'; const { expect } = require('chai'); const { filterForCommands } = require('../shared'); -const { - promiseWithResolvers, - MongoExpiredSessionError, - MongoCursorExhaustedError -} = require('../../mongodb'); +const { promiseWithResolvers, MongoCursorExhaustedError } = require('../../mongodb'); describe('Find Cursor', function () { let client;