diff --git a/src/operations/find.ts b/src/operations/find.ts index 9795f583ca8..2b719963706 100644 --- a/src/operations/find.ts +++ b/src/operations/find.ts @@ -2,18 +2,16 @@ import type { Document } from '../bson'; import { CursorResponse, ExplainedCursorResponse } from '../cmap/wire_protocol/responses'; import { type AbstractCursorOptions, type CursorTimeoutMode } from '../cursor/abstract_cursor'; import { MongoInvalidArgumentError } from '../error'; -import { - decorateWithExplain, - type ExplainOptions, - validateExplainTimeoutOptions -} from '../explain'; -import { ReadConcern } from '../read_concern'; -import type { Server } from '../sdam/server'; -import type { ClientSession } from '../sessions'; +import { type ExplainOptions } from '../explain'; +import type { ServerCommandOptions } from '../sdam/server'; import { formatSort, type Sort } from '../sort'; import { type TimeoutContext } from '../timeout'; import { type MongoDBNamespace, normalizeHintField } from '../utils'; -import { type CollationOptions, CommandOperation, type CommandOperationOptions } from './command'; +import { + type CollationOptions, + type CommandOperationOptions, + ModernizedCommandOperation +} from './command'; import { Aspect, defineAspects, type Hint } from './operation'; /** @@ -92,7 +90,9 @@ export interface FindOneOptions extends FindOptions { } /** @internal */ -export class FindOperation extends CommandOperation { +export class FindOperation extends ModernizedCommandOperation { + override SERVER_COMMAND_RESPONSE_TYPE = CursorResponse; + /** * @remarks WriteConcern can still be present on the options because * we inherit options from the client/db/collection. The @@ -116,39 +116,32 @@ export class FindOperation extends CommandOperation { // special case passing in an ObjectId as a filter this.filter = filter != null && filter._bsontype === 'ObjectId' ? { _id: filter } : filter; + + this.SERVER_COMMAND_RESPONSE_TYPE = this.explain ? ExplainedCursorResponse : CursorResponse; } override get commandName() { return 'find' as const; } - override async execute( - server: Server, - session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { - this.server = server; - - const options = this.options; + override buildOptions(timeoutContext: TimeoutContext): ServerCommandOptions { + return { + ...this.options, + ...this.bsonOptions, + documentsReturnedIn: 'firstBatch', + session: this.session, + timeoutContext + }; + } - let findCommand = makeFindCommand(this.ns, this.filter, options); - if (this.explain) { - validateExplainTimeoutOptions(this.options, this.explain); - findCommand = decorateWithExplain(findCommand, this.explain); - } + override handleOk( + response: InstanceType + ): CursorResponse { + return response; + } - return await server.command( - this.ns, - findCommand, - { - ...this.options, - ...this.bsonOptions, - documentsReturnedIn: 'firstBatch', - session, - timeoutContext - }, - this.explain ? ExplainedCursorResponse : CursorResponse - ); + override buildCommandDocument(): Document { + return makeFindCommand(this.ns, this.filter, this.options); } } @@ -217,15 +210,6 @@ function makeFindCommand(ns: MongoDBNamespace, filter: Document, options: FindOp findCommand.comment = options.comment; } - if (typeof options.maxTimeMS === 'number') { - findCommand.maxTimeMS = options.maxTimeMS; - } - - const readConcern = ReadConcern.fromOptions(options); - if (readConcern) { - findCommand.readConcern = readConcern.toJSON(); - } - if (options.max) { findCommand.max = options.max; } @@ -263,11 +247,6 @@ function makeFindCommand(ns: MongoDBNamespace, filter: Document, options: FindOp if (typeof options.allowPartialResults === 'boolean') { findCommand.allowPartialResults = options.allowPartialResults; } - - if (options.collation) { - findCommand.collation = options.collation; - } - if (typeof options.allowDiskUse === 'boolean') { findCommand.allowDiskUse = options.allowDiskUse; } diff --git a/src/operations/get_more.ts b/src/operations/get_more.ts index 34317d533b5..f9397f9a11e 100644 --- a/src/operations/get_more.ts +++ b/src/operations/get_more.ts @@ -1,11 +1,11 @@ -import type { Long } from '../bson'; +import type { Document, Long } from '../bson'; +import { type Connection } from '../cmap/connection'; import { CursorResponse } from '../cmap/wire_protocol/responses'; import { MongoRuntimeError } from '../error'; -import type { Server } from '../sdam/server'; -import type { ClientSession } from '../sessions'; +import type { Server, ServerCommandOptions } from '../sdam/server'; import { type TimeoutContext } from '../timeout'; import { maxWireVersion, type MongoDBNamespace } from '../utils'; -import { AbstractOperation, Aspect, defineAspects, type OperationOptions } from './operation'; +import { Aspect, defineAspects, ModernizedOperation, type OperationOptions } from './operation'; /** @internal */ export interface GetMoreOptions extends OperationOptions { @@ -37,7 +37,8 @@ export interface GetMoreCommand { } /** @internal */ -export class GetMoreOperation extends AbstractOperation { +export class GetMoreOperation extends ModernizedOperation { + override SERVER_COMMAND_RESPONSE_TYPE = CursorResponse; cursorId: Long; override options: GetMoreOptions; @@ -53,19 +54,8 @@ export class GetMoreOperation extends AbstractOperation { override get commandName() { return 'getMore' as const; } - /** - * Although there is a server already associated with the get more operation, the signature - * for execute passes a server so we will just use that one. - */ - override async execute( - server: Server, - _session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { - if (server !== this.server) { - throw new MongoRuntimeError('Getmore must run on the same server operation began on'); - } + override buildCommand(connection: Connection): Document { if (this.cursorId == null || this.cursorId.isZero()) { throw new MongoRuntimeError('Unable to iterate cursor with no id'); } @@ -92,18 +82,26 @@ export class GetMoreOperation extends AbstractOperation { // we check for undefined specifically here to allow falsy values // eslint-disable-next-line no-restricted-syntax - if (this.options.comment !== undefined && maxWireVersion(server) >= 9) { + if (this.options.comment !== undefined && maxWireVersion(connection) >= 9) { getMoreCmd.comment = this.options.comment; } - const commandOptions = { + return getMoreCmd; + } + + override buildOptions(timeoutContext: TimeoutContext): ServerCommandOptions { + return { returnFieldSelector: null, documentsReturnedIn: 'nextBatch', timeoutContext, ...this.options }; + } - return await server.command(this.ns, getMoreCmd, commandOptions, CursorResponse); + override handleOk( + response: InstanceType + ): CursorResponse { + return response; } } diff --git a/src/operations/indexes.ts b/src/operations/indexes.ts index afd05f5be36..3dc1db8ba26 100644 --- a/src/operations/indexes.ts +++ b/src/operations/indexes.ts @@ -1,4 +1,5 @@ import type { Document } from '../bson'; +import { type Connection } from '../cmap/connection'; import { CursorResponse } from '../cmap/wire_protocol/responses'; import type { Collection } from '../collection'; import { type AbstractCursorOptions } from '../cursor/abstract_cursor'; @@ -12,6 +13,7 @@ import { type CollationOptions, CommandOperation, type CommandOperationOptions, + ModernizedCommandOperation, type OperationParent } from './command'; import { Aspect, defineAspects } from './operation'; @@ -366,7 +368,8 @@ export type ListIndexesOptions = AbstractCursorOptions & { }; /** @internal */ -export class ListIndexesOperation extends CommandOperation { +export class ListIndexesOperation extends ModernizedCommandOperation { + override SERVER_COMMAND_RESPONSE_TYPE = CursorResponse; /** * @remarks WriteConcern can still be present on the options because * we inherit options from the client/db/collection. The @@ -389,12 +392,8 @@ export class ListIndexesOperation extends CommandOperation { return 'listIndexes' as const; } - override async execute( - server: Server, - session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { - const serverWireVersion = maxWireVersion(server); + override buildCommandDocument(connection: Connection): Document { + const serverWireVersion = maxWireVersion(connection); const cursor = this.options.batchSize ? { batchSize: this.options.batchSize } : {}; @@ -406,7 +405,13 @@ export class ListIndexesOperation extends CommandOperation { command.comment = this.options.comment; } - return await super.executeCommand(server, session, command, timeoutContext, CursorResponse); + return command; + } + + override handleOk( + response: InstanceType + ): CursorResponse { + return response; } } diff --git a/src/operations/kill_cursors.ts b/src/operations/kill_cursors.ts index 72c6a04b276..f5089dfbaa9 100644 --- a/src/operations/kill_cursors.ts +++ b/src/operations/kill_cursors.ts @@ -1,10 +1,12 @@ import type { Long } from '../bson'; -import { MongoRuntimeError } from '../error'; -import type { Server } from '../sdam/server'; +import { type Connection } from '../cmap/connection'; +import { MongoDBResponse } from '../cmap/wire_protocol/responses'; +import { type MongoError, MongoRuntimeError } from '../error'; +import type { Server, ServerCommandOptions } from '../sdam/server'; import type { ClientSession } from '../sessions'; import { type TimeoutContext } from '../timeout'; -import { type MongoDBNamespace, squashError } from '../utils'; -import { AbstractOperation, Aspect, defineAspects, type OperationOptions } from './operation'; +import { type MongoDBNamespace } from '../utils'; +import { Aspect, defineAspects, ModernizedOperation, type OperationOptions } from './operation'; /** * https://www.mongodb.com/docs/manual/reference/command/killCursors/ @@ -16,7 +18,8 @@ interface KillCursorsCommand { comment?: unknown; } -export class KillCursorsOperation extends AbstractOperation { +export class KillCursorsOperation extends ModernizedOperation { + override SERVER_COMMAND_RESPONSE_TYPE = MongoDBResponse; cursorId: Long; constructor(cursorId: Long, ns: MongoDBNamespace, server: Server, options: OperationOptions) { @@ -30,15 +33,7 @@ export class KillCursorsOperation extends AbstractOperation { return 'killCursors' as const; } - override async execute( - server: Server, - session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { - if (server !== this.server) { - throw new MongoRuntimeError('Killcursor must run on the same server operation began on'); - } - + override buildCommand(_connection: Connection, _session?: ClientSession): KillCursorsCommand { const killCursors = this.ns.collection; if (killCursors == null) { // Cursors should have adopted the namespace returned by MongoDB @@ -50,15 +45,19 @@ export class KillCursorsOperation extends AbstractOperation { killCursors, cursors: [this.cursorId] }; - try { - await server.command(this.ns, killCursorsCommand, { - session, - timeoutContext - }); - } catch (error) { - // The driver should never emit errors from killCursors, this is spec-ed behavior - squashError(error); - } + + return killCursorsCommand; + } + + override buildOptions(timeoutContext: TimeoutContext): ServerCommandOptions { + return { + session: this.session, + timeoutContext + }; + } + + override handleError(_error: MongoError): void { + // The driver should never emit errors from killCursors, this is spec-ed behavior } } diff --git a/src/operations/list_collections.ts b/src/operations/list_collections.ts index 57f8aff45e6..869c15383c3 100644 --- a/src/operations/list_collections.ts +++ b/src/operations/list_collections.ts @@ -1,13 +1,11 @@ +import { type Connection } from '..'; import type { Binary, Document } from '../bson'; -import { CursorResponse } from '../cmap/wire_protocol/responses'; +import { CursorResponse, ExplainedCursorResponse } from '../cmap/wire_protocol/responses'; import { type CursorTimeoutContext, type CursorTimeoutMode } from '../cursor/abstract_cursor'; import type { Db } from '../db'; import { type Abortable } from '../mongo_types'; -import type { Server } from '../sdam/server'; -import type { ClientSession } from '../sessions'; -import { type TimeoutContext } from '../timeout'; import { maxWireVersion } from '../utils'; -import { CommandOperation, type CommandOperationOptions } from './command'; +import { type CommandOperationOptions, ModernizedCommandOperation } from './command'; import { Aspect, defineAspects } from './operation'; /** @public */ @@ -28,7 +26,8 @@ export interface ListCollectionsOptions } /** @internal */ -export class ListCollectionsOperation extends CommandOperation { +export class ListCollectionsOperation extends ModernizedCommandOperation { + override SERVER_COMMAND_RESPONSE_TYPE = CursorResponse; /** * @remarks WriteConcern can still be present on the options because * we inherit options from the client/db/collection. The @@ -56,28 +55,15 @@ export class ListCollectionsOperation extends CommandOperation { if (typeof this.options.batchSize === 'number') { this.batchSize = this.options.batchSize; } + + this.SERVER_COMMAND_RESPONSE_TYPE = this.explain ? ExplainedCursorResponse : CursorResponse; } override get commandName() { return 'listCollections' as const; } - override async execute( - server: Server, - session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { - return await super.executeCommand( - server, - session, - this.generateCommand(maxWireVersion(server)), - timeoutContext, - CursorResponse - ); - } - - /* This is here for the purpose of unit testing the final command that gets sent. */ - generateCommand(wireVersion: number): Document { + override buildCommandDocument(connection: Connection): Document { const command: Document = { listCollections: 1, filter: this.filter, @@ -88,12 +74,18 @@ export class ListCollectionsOperation extends CommandOperation { // we check for undefined specifically here to allow falsy values // eslint-disable-next-line no-restricted-syntax - if (wireVersion >= 9 && this.options.comment !== undefined) { + if (maxWireVersion(connection) >= 9 && this.options.comment !== undefined) { command.comment = this.options.comment; } return command; } + + override handleOk( + response: InstanceType + ): CursorResponse { + return response; + } } /** @public */ diff --git a/src/sdam/server.ts b/src/sdam/server.ts index bfaa9ac93d3..27b600b2362 100644 --- a/src/sdam/server.ts +++ b/src/sdam/server.ts @@ -111,6 +111,7 @@ export type ServerEvents = { /** @internal */ export type ServerCommandOptions = Omit & { timeoutContext: TimeoutContext; + returnFieldSelector?: Document | null; } & Abortable; /** @internal */ diff --git a/test/integration/server-selection/operation_count.test.ts b/test/integration/server-selection/operation_count.test.ts index cf2ec4e5d76..451515e35d5 100644 --- a/test/integration/server-selection/operation_count.test.ts +++ b/test/integration/server-selection/operation_count.test.ts @@ -70,7 +70,7 @@ describe('Server Operation Count Tests', function () { it('is zero after a successful command', loadBalancedTestMetadata, async function () { const server = Array.from(client.topology.s.servers.values())[0]; expect(server.s.operationCount).to.equal(0); - const commandSpy = sinon.spy(server, 'command'); + const commandSpy = sinon.spy(server, 'modernCommand'); await collection.findOne({ count: 1 }); @@ -84,7 +84,7 @@ describe('Server Operation Count Tests', function () { const server = Array.from(client.topology.s.servers.values())[0]; expect(server.s.operationCount).to.equal(0); - const commandSpy = sinon.spy(server, 'command'); + const commandSpy = sinon.spy(server, 'modernCommand'); const error = await collection.findOne({ count: 1 }).catch(e => e); @@ -104,7 +104,7 @@ describe('Server Operation Count Tests', function () { sinon .stub(ConnectionPool.prototype, 'checkOut') .rejects(new Error('unable to checkout connection')); - const commandSpy = sinon.spy(server, 'command'); + const commandSpy = sinon.spy(server, 'modernCommand'); const error = await collection.findOne({ count: 1 }).catch(e => e); diff --git a/test/unit/operations/find.test.ts b/test/unit/operations/find.test.ts index f208636d238..81ab4f16718 100644 --- a/test/unit/operations/find.test.ts +++ b/test/unit/operations/find.test.ts @@ -1,8 +1,7 @@ import { expect } from 'chai'; import * as sinon from 'sinon'; -import { FindOperation, ns, Server, ServerDescription } from '../../mongodb'; -import { topologyWithPlaceholderClient } from '../../tools/utils'; +import { FindOperation, ns } from '../../mongodb'; describe('FindOperation', function () { const namespace = ns('db.coll'); @@ -33,34 +32,16 @@ describe('FindOperation', function () { }); }); - describe('#execute', function () { - context('command construction', () => { - const namespace = ns('db.collection'); - const topology = topologyWithPlaceholderClient([], {} as any); - const server = new Server(topology, new ServerDescription('a:1'), {} as any); + context('command construction', () => { + const namespace = ns('db.collection'); - it('should build basic find command with filter', async () => { - const findOperation = new FindOperation(namespace, filter); - const stub = sinon.stub(server, 'command').resolves({}); - await findOperation.execute(server, undefined); - expect(stub).to.have.been.calledOnceWith(namespace, { - find: namespace.collection, - filter - }); - }); - - it('should build find command with oplogReplay', async () => { - const options = { - oplogReplay: true - }; - const findOperation = new FindOperation(namespace, {}, options); - const stub = sinon.stub(server, 'command').resolves({}); - await findOperation.execute(server, undefined); - expect(stub).to.have.been.calledOnceWith( - namespace, - sinon.match.has('oplogReplay', options.oplogReplay) - ); - }); + it('should build find command with oplogReplay', () => { + const options = { + oplogReplay: true + }; + const findOperation = new FindOperation(namespace, {}, options); + const command = findOperation.buildCommandDocument(); + expect(command.oplogReplay).to.be.true; }); }); }); diff --git a/test/unit/operations/get_more.test.ts b/test/unit/operations/get_more.test.ts index 76ebf16555d..9f2245f77fb 100644 --- a/test/unit/operations/get_more.test.ts +++ b/test/unit/operations/get_more.test.ts @@ -1,12 +1,9 @@ import { expect } from 'chai'; -import * as sinon from 'sinon'; import { Aspect, - ClientSession, GetMoreOperation, Long, - MongoRuntimeError, ns, ReadPreference, Server, @@ -23,10 +20,6 @@ describe('GetMoreOperation', function () { readPreference: ReadPreference.primary }; - afterEach(function () { - sinon.restore(); - }); - describe('#constructor', function () { const topology = topologyWithPlaceholderClient([], {} as any); const server = new Server(topology, new ServerDescription('a:1'), {} as any); @@ -45,161 +38,24 @@ describe('GetMoreOperation', function () { }); }); - describe('#execute', function () { - context('when the server is the same as the instance', function () { - it('executes a getMore on the provided server', async function () { - const server = new Server( - topologyWithPlaceholderClient([], {} as any), - new ServerDescription('a:1'), - {} as any - ); - const opts = { - ...options, - documentsReturnedIn: 'nextBatch', - returnFieldSelector: null, - timeoutContext: undefined - }; - const operation = new GetMoreOperation(namespace, cursorId, server, opts); - const stub = sinon.stub(server, 'command').resolves({}); - - const expectedGetMoreCommand = { - getMore: cursorId, - collection: namespace.collection, - batchSize: 100, - maxTimeMS: 500 - }; - - await operation.execute(server, undefined); - expect(stub.calledOnce).to.be.true; - const call = stub.getCall(0); - expect(call.args[0]).to.equal(namespace); - expect(call.args[1]).to.deep.equal(expectedGetMoreCommand); - expect(call.args[2]).to.deep.equal(opts); - }); - }); - - context('when the server is not the same as the instance', function () { - it('errors in the callback', async function () { - const server1 = new Server( - topologyWithPlaceholderClient([], {} as any), - new ServerDescription('a:1'), - {} as any - ); - const server2 = new Server( - topologyWithPlaceholderClient([], {} as any), - new ServerDescription('a:1'), - {} as any - ); - const session = sinon.createStubInstance(ClientSession); - const opts = { ...options, session }; - const operation = new GetMoreOperation(namespace, cursorId, server1, opts); - const error = await operation.execute(server2, session).catch(error => error); - expect(error).to.be.instanceOf(MongoRuntimeError); - expect(error.message).to.equal('Getmore must run on the same server operation began on'); - }); - }); - - context('command construction', () => { - const cursorId = Long.fromBigInt(0xffff_ffffn); - const namespace = ns('db.collection'); - const server = new Server( - topologyWithPlaceholderClient([], {} as any), - new ServerDescription('a:1'), - {} as any - ); - - it('should build basic getMore command with cursorId and collection', async () => { - const getMoreOperation = new GetMoreOperation(namespace, cursorId, server, {}); - const stub = sinon.stub(server, 'command').resolves({}); - await getMoreOperation.execute(server, undefined); - expect(stub).to.have.been.calledOnceWith(namespace, { - getMore: cursorId, - collection: namespace.collection - }); - }); - - it('should build getMore command with batchSize', async () => { - const options = { - batchSize: 234 - }; - const getMoreOperation = new GetMoreOperation(namespace, cursorId, server, options); - const stub = sinon.stub(server, 'command').resolves({}); - await getMoreOperation.execute(server, undefined); - expect(stub).to.have.been.calledOnceWith( - namespace, - sinon.match.has('batchSize', options.batchSize) - ); - }); - - it('should build getMore command with maxTimeMS if maxAwaitTimeMS specified', async () => { - const options = { - maxAwaitTimeMS: 234 - }; - const getMoreOperation = new GetMoreOperation(namespace, cursorId, server, options); - const stub = sinon.stub(server, 'command').resolves({}); - await getMoreOperation.execute(server, undefined); - expect(stub).to.have.been.calledOnceWith( - namespace, - sinon.match.has('maxTimeMS', options.maxAwaitTimeMS) - ); - }); - - context('comment', function () { - const optionsWithComment = { - ...options, - comment: 'test' - }; + context('command construction', () => { + const cursorId = Long.fromBigInt(0xffff_ffffn); + const namespace = ns('db.collection'); + const server = new Server( + topologyWithPlaceholderClient([], {} as any), + new ServerDescription('a:1'), + {} as any + ); - const serverVersions = [ - { - serverVersion: 8, - getMore: { - getMore: cursorId, - collection: namespace.collection, - batchSize: 100, - maxTimeMS: 500 - } - }, - { - serverVersion: 9, - getMore: { - getMore: cursorId, - collection: namespace.collection, - batchSize: 100, - maxTimeMS: 500, - comment: 'test' - } - }, - { - serverVersion: 10, - getMore: { - getMore: cursorId, - collection: namespace.collection, - batchSize: 100, - maxTimeMS: 500, - comment: 'test' - } - } - ]; - for (const { serverVersion, getMore } of serverVersions) { - const verb = serverVersion < 9 ? 'does not' : 'does'; - const state = serverVersion < 9 ? 'less than 9' : 'greater than or equal to 9'; - it(`${verb} set the comment on the command if the server wire version is ${state}`, async () => { - const server = new Server( - topologyWithPlaceholderClient([], {} as any), - new ServerDescription('a:1'), - {} as any - ); - server.hello = { - maxWireVersion: serverVersion - }; - const operation = new GetMoreOperation(namespace, cursorId, server, optionsWithComment); - const stub = sinon.stub(server, 'command').resolves({}); - await operation.execute(server, undefined); - expect(stub).to.have.been.calledOnceWith(namespace, getMore); - }); - } - }); + it('should build getMore command with maxTimeMS if maxAwaitTimeMS specified', async () => { + const options = { + maxAwaitTimeMS: 234 + }; + const getMoreOperation = new GetMoreOperation(namespace, cursorId, server, options); + const { maxTimeMS } = getMoreOperation.buildCommand({ + description: {} + } as any); + expect(maxTimeMS).to.equal(234); }); context('error cases', () => { @@ -208,18 +64,20 @@ describe('GetMoreOperation', function () { new ServerDescription('a:1'), {} as any ); - sinon.stub(server, 'command').resolves({}); it('should throw if the cursorId is undefined', async () => { const getMoreOperation = new GetMoreOperation( ns('db.collection'), - // @ts-expect-error: Testing undefined cursorId undefined, server, options ); - const error = await getMoreOperation.execute(server, undefined).catch(error => error); - expect(error).to.be.instanceOf(MongoRuntimeError); + const connection = { + description: {} + } as any; + expect(() => { + getMoreOperation.buildCommand(connection); + }).to.throw(/Unable to iterate cursor with no id/); }); it('should throw if the collection is undefined', async () => { @@ -229,8 +87,12 @@ describe('GetMoreOperation', function () { server, options ); - const error = await getMoreOperation.execute(server, undefined).catch(error => error); - expect(error).to.be.instanceOf(MongoRuntimeError); + const connection = { + description: {} + } as any; + expect(() => { + getMoreOperation.buildCommand(connection); + }).to.throw(/A collection name must be determined before getMore/); }); it('should throw if the cursorId is zero', async () => { @@ -240,8 +102,12 @@ describe('GetMoreOperation', function () { server, options ); - const error = await getMoreOperation.execute(server, undefined).catch(error => error); - expect(error).to.be.instanceOf(MongoRuntimeError); + const connection = { + description: {} + } as any; + expect(() => { + getMoreOperation.buildCommand(connection); + }).to.throw(/Unable to iterate cursor with no id/); }); }); }); diff --git a/test/unit/operations/kill_cursors.test.ts b/test/unit/operations/kill_cursors.test.ts index d88a7053b4e..f732ab04270 100644 --- a/test/unit/operations/kill_cursors.test.ts +++ b/test/unit/operations/kill_cursors.test.ts @@ -1,11 +1,9 @@ import { expect } from 'chai'; -import * as sinon from 'sinon'; import { KillCursorsOperation, Long, MongoDBNamespace, - MongoRuntimeError, ns, Server, ServerDescription @@ -13,10 +11,6 @@ import { import { topologyWithPlaceholderClient } from '../../tools/utils'; describe('class KillCursorsOperation', () => { - afterEach(function () { - sinon.restore(); - }); - describe('constructor()', () => { const cursorId = Long.fromBigInt(0xffff_ffffn); const namespace = ns('db.collection'); @@ -43,60 +37,22 @@ describe('class KillCursorsOperation', () => { describe('execute()', () => { const cursorId = Long.fromBigInt(0xffff_ffffn); - const namespace = ns('db.collection'); const server = new Server( topologyWithPlaceholderClient([], {} as any), new ServerDescription('a:1'), {} as any ); - const differentServer = new Server( - topologyWithPlaceholderClient([], {} as any), - new ServerDescription('a:1'), - {} as any - ); const options = {}; - it('should throw if the server defined from the constructor changes', async () => { - const killCursorsOperation = new KillCursorsOperation( - cursorId, - namespace, - server, - options - ) as any; - - const error = await killCursorsOperation - .execute(differentServer, undefined) - .catch(error => error); - - expect(error).to.be.instanceOf(MongoRuntimeError); - }); - it('should throw if the namespace does not define a collection', async () => { - const killCursorsOperation = new KillCursorsOperation( - cursorId, - ns('db'), - server, - options - ) as any; - - const error = await killCursorsOperation.execute(server, undefined).catch(error => error); - - expect(error).to.be.instanceOf(MongoRuntimeError); - }); - - it('should construct a killCursors command', async () => { - const killCursorsOperation = new KillCursorsOperation( - cursorId, - namespace, - server, - options - ) as any; - const stub = sinon.stub(server, 'command').resolves({}); - await killCursorsOperation.execute(server, undefined); - expect(stub).to.have.been.calledOnceWith(namespace, { - killCursors: namespace.collection, - cursors: [cursorId] - }); + const killCursorsOperation = new KillCursorsOperation(cursorId, ns('db'), server, options); + + const connection = { + description: {} + } as any; + expect(() => { + killCursorsOperation.buildCommand(connection); + }).to.throw(/A collection name must be determined before killCursors/); }); }); }); diff --git a/test/unit/operations/list_collections.test.js b/test/unit/operations/list_collections.test.js index 3e67474623a..3e5d12991d0 100644 --- a/test/unit/operations/list_collections.test.js +++ b/test/unit/operations/list_collections.test.js @@ -1,7 +1,7 @@ 'use strict'; const { expect } = require('chai'); -const { ListCollectionsOperation } = require('../../mongodb'); +const { ListCollectionsOperation, StreamDescription } = require('../../mongodb'); describe('ListCollectionsOperation', function () { const db = 'test'; @@ -65,6 +65,8 @@ describe('ListCollectionsOperation', function () { }); describe('#generateCommand', function () { + const description = new StreamDescription(); + context('when comment is provided', function () { context('when the wireVersion < 9', function () { it('does not set a comment on the command', function () { @@ -73,7 +75,10 @@ describe('ListCollectionsOperation', function () { {}, { dbName: db, comment: 'test comment' } ); - const command = operation.generateCommand(8); + description.maxWireVersion = 8; + const command = operation.buildCommandDocument({ + description + }); expect(command).not.to.haveOwnProperty('comment'); }); }); @@ -85,7 +90,10 @@ describe('ListCollectionsOperation', function () { {}, { dbName: db, comment: 'test comment' } ); - const command = operation.generateCommand(9); + description.maxWireVersion = 9; + const command = operation.buildCommandDocument({ + description + }); expect(command).to.have.property('comment').that.equals('test comment'); }); }); @@ -95,7 +103,11 @@ describe('ListCollectionsOperation', function () { const operation = new ListCollectionsOperation(db, {}, { nameOnly: true, dbName: db }); it('sets nameOnly to true', function () { - expect(operation.generateCommand(8)).to.deep.equal({ + description.maxWireVersion = 8; + const command = operation.buildCommandDocument({ + description + }); + expect(command).to.deep.equal({ listCollections: 1, cursor: {}, filter: {}, @@ -109,7 +121,11 @@ describe('ListCollectionsOperation', function () { const operation = new ListCollectionsOperation(db, {}, { nameOnly: false, dbName: db }); it('sets nameOnly to false', function () { - expect(operation.generateCommand(8)).to.deep.equal({ + description.maxWireVersion = 8; + const command = operation.buildCommandDocument({ + description + }); + expect(command).to.deep.equal({ listCollections: 1, cursor: {}, filter: {}, @@ -129,7 +145,11 @@ describe('ListCollectionsOperation', function () { ); it('sets authorizedCollections to true', function () { - expect(operation.generateCommand(8)).to.deep.equal({ + description.maxWireVersion = 8; + const command = operation.buildCommandDocument({ + description + }); + expect(command).to.deep.equal({ listCollections: 1, cursor: {}, filter: {}, @@ -147,7 +167,11 @@ describe('ListCollectionsOperation', function () { ); it('sets authorizedCollections to false', function () { - expect(operation.generateCommand(8)).to.deep.equal({ + description.maxWireVersion = 8; + const command = operation.buildCommandDocument({ + description + }); + expect(command).to.deep.equal({ listCollections: 1, cursor: {}, filter: {}, @@ -162,7 +186,11 @@ describe('ListCollectionsOperation', function () { const operation = new ListCollectionsOperation(db, {}, { dbName: db }); it('sets nameOnly and authorizedCollections properties to false', function () { - expect(operation.generateCommand(8)).to.deep.equal({ + description.maxWireVersion = 8; + const command = operation.buildCommandDocument({ + description + }); + expect(command).to.deep.equal({ listCollections: 1, cursor: {}, filter: {},