diff --git a/src/error.ts b/src/error.ts index b488d0d5d7..6f91f24634 100644 --- a/src/error.ts +++ b/src/error.ts @@ -200,6 +200,8 @@ export class MongoError extends Error { * @category Error */ export class MongoServerError extends MongoError { + /** Raw error result document returned by server. */ + errorResponse: ErrorDescription; codeName?: string; writeConcernError?: Document; errInfo?: Document; @@ -223,9 +225,17 @@ export class MongoServerError extends MongoError { this[kErrorLabels] = new Set(message.errorLabels); } + this.errorResponse = message; + for (const name in message) { - if (name !== 'errorLabels' && name !== 'errmsg' && name !== 'message') + if ( + name !== 'errorLabels' && + name !== 'errmsg' && + name !== 'message' && + name !== 'errorResponse' + ) { this[name] = message[name]; + } } } diff --git a/src/sdam/server.ts b/src/sdam/server.ts index 13cadd9f7f..f1a0bf1d98 100644 --- a/src/sdam/server.ts +++ b/src/sdam/server.ts @@ -171,7 +171,7 @@ export class Server extends TypedEventEmitter { this.monitor.on(event, (e: any) => this.emit(event, e)); } - this.monitor.on('resetServer', (error: MongoError) => markServerUnknown(this, error)); + this.monitor.on('resetServer', (error: MongoServerError) => markServerUnknown(this, error)); this.monitor.on(Server.SERVER_HEARTBEAT_SUCCEEDED, (event: ServerHeartbeatSucceededEvent) => { this.emit( Server.DESCRIPTION_RECEIVED, @@ -369,7 +369,7 @@ export class Server extends TypedEventEmitter { // clear for the specific service id. if (!this.loadBalanced) { error.addErrorLabel(MongoErrorLabel.ResetPool); - markServerUnknown(this, error); + markServerUnknown(this, error as MongoServerError); } else if (connection) { this.pool.clear({ serviceId: connection.serviceId }); } @@ -385,7 +385,7 @@ export class Server extends TypedEventEmitter { if (shouldClearPool) { error.addErrorLabel(MongoErrorLabel.ResetPool); } - markServerUnknown(this, error); + markServerUnknown(this, error as MongoServerError); process.nextTick(() => this.requestCheck()); } } diff --git a/src/sdam/topology_description.ts b/src/sdam/topology_description.ts index 380e6c8f0c..f2fafaf87b 100644 --- a/src/sdam/topology_description.ts +++ b/src/sdam/topology_description.ts @@ -313,7 +313,7 @@ export class TopologyDescription { ); if (descriptionsWithError.length > 0) { - return descriptionsWithError[0].error; + return descriptionsWithError[0].error as MongoServerError; } return null; diff --git a/test/integration/collection-management/collection_management.spec.test.js b/test/integration/collection-management/collection_management.spec.test.js index 1c78969d67..9242b24133 100644 --- a/test/integration/collection-management/collection_management.spec.test.js +++ b/test/integration/collection-management/collection_management.spec.test.js @@ -4,7 +4,10 @@ const { loadSpecTests } = require('../../spec/index'); const { runUnifiedSuite } = require('../../tools/unified-spec-runner/runner'); // The Node driver does not have a Collection.modifyCollection helper. -const SKIPPED_TESTS = ['modifyCollection to changeStreamPreAndPostImages enabled']; +const SKIPPED_TESTS = [ + 'modifyCollection to changeStreamPreAndPostImages enabled', + 'modifyCollection prepareUnique violations are accessible' +]; describe('Collection management unified spec tests', function () { runUnifiedSuite(loadSpecTests('collection-management'), ({ description }) => diff --git a/test/integration/crud/crud.spec.test.js b/test/integration/crud/crud.spec.test.js index ce145c038c..a7fd404c8b 100644 --- a/test/integration/crud/crud.spec.test.js +++ b/test/integration/crud/crud.spec.test.js @@ -425,6 +425,10 @@ describe('CRUD spec v1', function () { } }); +// TODO(NODE-5998) - The Node driver UTR does not have a Collection.modifyCollection helper. +const SKIPPED_TESTS = ['findOneAndUpdate document validation errInfo is accessible']; describe('CRUD unified', function () { - runUnifiedSuite(loadSpecTests(path.join('crud', 'unified'))); + runUnifiedSuite(loadSpecTests(path.join('crud', 'unified')), ({ description }) => + SKIPPED_TESTS.includes(description) ? `the Node driver does not have a collMod helper.` : false + ); }); diff --git a/test/spec/collection-management/modifyCollection-errorResponse.json b/test/spec/collection-management/modifyCollection-errorResponse.json new file mode 100644 index 0000000000..aea71eb08f --- /dev/null +++ b/test/spec/collection-management/modifyCollection-errorResponse.json @@ -0,0 +1,118 @@ +{ + "description": "modifyCollection-errorResponse", + "schemaVersion": "1.12", + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "collMod-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "collMod-tests", + "documents": [ + { + "_id": 1, + "x": 1 + }, + { + "_id": 2, + "x": 1 + } + ] + } + ], + "tests": [ + { + "description": "modifyCollection prepareUnique violations are accessible", + "runOnRequirements": [ + { + "minServerVersion": "5.2" + } + ], + "operations": [ + { + "name": "createIndex", + "object": "collection0", + "arguments": { + "keys": { + "x": 1 + } + } + }, + { + "name": "modifyCollection", + "object": "database0", + "arguments": { + "collection": "test", + "index": { + "keyPattern": { + "x": 1 + }, + "prepareUnique": true + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 3, + "x": 1 + } + }, + "expectError": { + "errorCode": 11000 + } + }, + { + "name": "modifyCollection", + "object": "database0", + "arguments": { + "collection": "test", + "index": { + "keyPattern": { + "x": 1 + }, + "unique": true + } + }, + "expectError": { + "isClientError": false, + "errorCode": 359, + "errorResponse": { + "violations": [ + { + "ids": [ + 1, + 2 + ] + } + ] + } + } + } + ] + } + ] +} diff --git a/test/spec/collection-management/modifyCollection-errorResponse.yml b/test/spec/collection-management/modifyCollection-errorResponse.yml new file mode 100644 index 0000000000..e61a01211c --- /dev/null +++ b/test/spec/collection-management/modifyCollection-errorResponse.yml @@ -0,0 +1,59 @@ +description: "modifyCollection-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name collMod-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +initialData: &initialData + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 1 } + - { _id: 2, x: 1 } + +tests: + - description: "modifyCollection prepareUnique violations are accessible" + runOnRequirements: + - minServerVersion: "5.2" # SERVER-61158 + operations: + - name: createIndex + object: *collection0 + arguments: + keys: { x: 1 } + - name: modifyCollection + object: *database0 + arguments: + collection: *collection0Name + index: + keyPattern: { x: 1 } + prepareUnique: true + - name: insertOne + object: *collection0 + arguments: + document: { _id: 3, x: 1 } + expectError: + errorCode: 11000 # DuplicateKey + - name: modifyCollection + object: *database0 + arguments: + collection: *collection0Name + index: + keyPattern: { x: 1 } + unique: true + expectError: + isClientError: false + errorCode: 359 # CannotConvertIndexToUnique + errorResponse: + violations: + - { ids: [ 1, 2 ] } diff --git a/test/spec/crud/unified/aggregate-merge-errorResponse.json b/test/spec/crud/unified/aggregate-merge-errorResponse.json new file mode 100644 index 0000000000..6c7305fd91 --- /dev/null +++ b/test/spec/crud/unified/aggregate-merge-errorResponse.json @@ -0,0 +1,90 @@ +{ + "description": "aggregate-merge-errorResponse", + "schemaVersion": "1.12", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 1 + }, + { + "_id": 2, + "x": 1 + } + ] + } + ], + "tests": [ + { + "description": "aggregate $merge DuplicateKey error is accessible", + "runOnRequirements": [ + { + "minServerVersion": "5.1", + "topologies": [ + "single", + "replicaset" + ] + } + ], + "operations": [ + { + "name": "aggregate", + "object": "database0", + "arguments": { + "pipeline": [ + { + "$documents": [ + { + "_id": 2, + "x": 1 + } + ] + }, + { + "$merge": { + "into": "test", + "whenMatched": "fail" + } + } + ] + }, + "expectError": { + "errorCode": 11000, + "errorResponse": { + "keyPattern": { + "_id": 1 + }, + "keyValue": { + "_id": 2 + } + } + } + } + ] + } + ] +} diff --git a/test/spec/crud/unified/aggregate-merge-errorResponse.yml b/test/spec/crud/unified/aggregate-merge-errorResponse.yml new file mode 100644 index 0000000000..5fd679bffb --- /dev/null +++ b/test/spec/crud/unified/aggregate-merge-errorResponse.yml @@ -0,0 +1,42 @@ +description: "aggregate-merge-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +initialData: &initialData + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 1 } + - { _id: 2, x: 1 } + +tests: + - description: "aggregate $merge DuplicateKey error is accessible" + runOnRequirements: + - minServerVersion: "5.1" # SERVER-59097 + # Exclude sharded topologies since the aggregate command fails with + # IllegalOperation(20) instead of DuplicateKey(11000) + topologies: [ single, replicaset ] + operations: + - name: aggregate + object: *database0 + arguments: + pipeline: + - { $documents: [ { _id: 2, x: 1 } ] } + - { $merge: { into: *collection0Name, whenMatched: "fail" } } + expectError: + errorCode: 11000 # DuplicateKey + errorResponse: + keyPattern: { _id: 1 } + keyValue: { _id: 2 } diff --git a/test/spec/crud/unified/bulkWrite-errorResponse.json b/test/spec/crud/unified/bulkWrite-errorResponse.json new file mode 100644 index 0000000000..157637c713 --- /dev/null +++ b/test/spec/crud/unified/bulkWrite-errorResponse.json @@ -0,0 +1,88 @@ +{ + "description": "bulkWrite-errorResponse", + "schemaVersion": "1.12", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "tests": [ + { + "description": "bulkWrite operations support errorResponse assertions", + "runOnRequirements": [ + { + "minServerVersion": "4.0.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.2.0", + "topologies": [ + "sharded" + ] + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 8 + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 1 + } + } + } + ] + }, + "expectError": { + "errorCode": 8, + "errorResponse": { + "code": 8 + } + } + } + ] + } + ] +} diff --git a/test/spec/crud/unified/bulkWrite-errorResponse.yml b/test/spec/crud/unified/bulkWrite-errorResponse.yml new file mode 100644 index 0000000000..d4f335dfd3 --- /dev/null +++ b/test/spec/crud/unified/bulkWrite-errorResponse.yml @@ -0,0 +1,50 @@ +description: "bulkWrite-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +tests: + # This test intentionally executes only a single insert operation in the bulk + # write to make the error code and response assertions less ambiguous. That + # said, some drivers may still need to skip this test because the CRUD spec + # does not prescribe how drivers should formulate a BulkWriteException beyond + # collecting write and write concern errors. + - description: "bulkWrite operations support errorResponse assertions" + runOnRequirements: + - minServerVersion: "4.0.0" + topologies: [ single, replicaset ] + - minServerVersion: "4.2.0" + topologies: [ sharded ] + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: &errorCode 8 # UnknownError + - name: bulkWrite + object: *collection0 + arguments: + requests: + - insertOne: + document: { _id: 1 } + expectError: + errorCode: *errorCode + errorResponse: + code: *errorCode diff --git a/test/spec/crud/unified/deleteOne-errorResponse.json b/test/spec/crud/unified/deleteOne-errorResponse.json new file mode 100644 index 0000000000..1f3a266f1e --- /dev/null +++ b/test/spec/crud/unified/deleteOne-errorResponse.json @@ -0,0 +1,82 @@ +{ + "description": "deleteOne-errorResponse", + "schemaVersion": "1.12", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "tests": [ + { + "description": "delete operations support errorResponse assertions", + "runOnRequirements": [ + { + "minServerVersion": "4.0.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.2.0", + "topologies": [ + "sharded" + ] + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorCode": 8 + } + } + } + }, + { + "name": "deleteOne", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorCode": 8, + "errorResponse": { + "code": 8 + } + } + } + ] + } + ] +} diff --git a/test/spec/crud/unified/deleteOne-errorResponse.yml b/test/spec/crud/unified/deleteOne-errorResponse.yml new file mode 100644 index 0000000000..dcf013060e --- /dev/null +++ b/test/spec/crud/unified/deleteOne-errorResponse.yml @@ -0,0 +1,46 @@ +description: "deleteOne-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +tests: + # Some drivers may still need to skip this test because the CRUD spec does not + # prescribe how drivers should formulate a WriteException beyond collecting a + # write or write concern error. + - description: "delete operations support errorResponse assertions" + runOnRequirements: + - minServerVersion: "4.0.0" + topologies: [ single, replicaset ] + - minServerVersion: "4.2.0" + topologies: [ sharded ] + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ delete ] + errorCode: &errorCode 8 # UnknownError + - name: deleteOne + object: *collection0 + arguments: + filter: { _id: 1 } + expectError: + errorCode: *errorCode + errorResponse: + code: *errorCode diff --git a/test/spec/crud/unified/findOneAndUpdate-errorResponse.json b/test/spec/crud/unified/findOneAndUpdate-errorResponse.json new file mode 100644 index 0000000000..5023a450f3 --- /dev/null +++ b/test/spec/crud/unified/findOneAndUpdate-errorResponse.json @@ -0,0 +1,132 @@ +{ + "description": "findOneAndUpdate-errorResponse", + "schemaVersion": "1.12", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": "foo" + } + ] + } + ], + "tests": [ + { + "description": "findOneAndUpdate DuplicateKey error is accessible", + "runOnRequirements": [ + { + "minServerVersion": "4.2" + } + ], + "operations": [ + { + "name": "createIndex", + "object": "collection0", + "arguments": { + "keys": { + "x": 1 + }, + "unique": true + } + }, + { + "name": "findOneAndUpdate", + "object": "collection0", + "arguments": { + "filter": { + "_id": 2 + }, + "update": { + "$set": { + "x": "foo" + } + }, + "upsert": true + }, + "expectError": { + "errorCode": 11000, + "errorResponse": { + "keyPattern": { + "x": 1 + }, + "keyValue": { + "x": "foo" + } + } + } + } + ] + }, + { + "description": "findOneAndUpdate document validation errInfo is accessible", + "runOnRequirements": [ + { + "minServerVersion": "5.0" + } + ], + "operations": [ + { + "name": "modifyCollection", + "object": "database0", + "arguments": { + "collection": "test", + "validator": { + "x": { + "$type": "string" + } + } + } + }, + { + "name": "findOneAndUpdate", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "x": 1 + } + } + }, + "expectError": { + "errorCode": 121, + "errorResponse": { + "errInfo": { + "failingDocumentId": 1, + "details": { + "$$type": "object" + } + } + } + } + } + ] + } + ] +} diff --git a/test/spec/crud/unified/findOneAndUpdate-errorResponse.yml b/test/spec/crud/unified/findOneAndUpdate-errorResponse.yml new file mode 100644 index 0000000000..8faed76809 --- /dev/null +++ b/test/spec/crud/unified/findOneAndUpdate-errorResponse.yml @@ -0,0 +1,69 @@ +description: "findOneAndUpdate-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +initialData: &initialData + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: "foo" } + +tests: + - description: "findOneAndUpdate DuplicateKey error is accessible" + runOnRequirements: + - minServerVersion: "4.2" # SERVER-37124 + operations: + - name: createIndex + object: *collection0 + arguments: + keys: { x: 1 } + unique: true + - name: findOneAndUpdate + object: *collection0 + arguments: + filter: { _id: 2 } + update: { $set: { x: "foo" } } + upsert: true + expectError: + errorCode: 11000 # DuplicateKey + errorResponse: + keyPattern: { x: 1 } + keyValue: { x: "foo" } + + - description: "findOneAndUpdate document validation errInfo is accessible" + runOnRequirements: + - minServerVersion: "5.0" + operations: + - name: modifyCollection + object: *database0 + arguments: + collection: *collection0Name + validator: + x: { $type: "string" } + - name: findOneAndUpdate + object: *collection0 + arguments: + filter: { _id: 1 } + update: { $set: { x: 1 } } + expectError: + errorCode: 121 # DocumentValidationFailure + errorResponse: + # Avoid asserting the exact contents of errInfo as it may vary by + # server version. Likewise, this is why drivers do not model the + # document. The following is sufficient to test that validation + # details are accessible. See SERVER-20547 for more context. + errInfo: + failingDocumentId: 1 + details: { $$type: "object" } diff --git a/test/spec/crud/unified/insertOne-errorResponse.json b/test/spec/crud/unified/insertOne-errorResponse.json new file mode 100644 index 0000000000..04ea6a7451 --- /dev/null +++ b/test/spec/crud/unified/insertOne-errorResponse.json @@ -0,0 +1,82 @@ +{ + "description": "insertOne-errorResponse", + "schemaVersion": "1.12", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "tests": [ + { + "description": "insert operations support errorResponse assertions", + "runOnRequirements": [ + { + "minServerVersion": "4.0.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.2.0", + "topologies": [ + "sharded" + ] + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 8 + } + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 1 + } + }, + "expectError": { + "errorCode": 8, + "errorResponse": { + "code": 8 + } + } + } + ] + } + ] +} diff --git a/test/spec/crud/unified/insertOne-errorResponse.yml b/test/spec/crud/unified/insertOne-errorResponse.yml new file mode 100644 index 0000000000..b14caa1737 --- /dev/null +++ b/test/spec/crud/unified/insertOne-errorResponse.yml @@ -0,0 +1,46 @@ +description: "insertOne-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +tests: + # Some drivers may still need to skip this test because the CRUD spec does not + # prescribe how drivers should formulate a WriteException beyond collecting a + # write or write concern error. + - description: "insert operations support errorResponse assertions" + runOnRequirements: + - minServerVersion: "4.0.0" + topologies: [ single, replicaset ] + - minServerVersion: "4.2.0" + topologies: [ sharded ] + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: &errorCode 8 # UnknownError + - name: insertOne + object: *collection0 + arguments: + document: { _id: 1 } + expectError: + errorCode: *errorCode + errorResponse: + code: *errorCode diff --git a/test/spec/crud/unified/updateOne-errorResponse.json b/test/spec/crud/unified/updateOne-errorResponse.json new file mode 100644 index 0000000000..0ceddbc4fc --- /dev/null +++ b/test/spec/crud/unified/updateOne-errorResponse.json @@ -0,0 +1,87 @@ +{ + "description": "updateOne-errorResponse", + "schemaVersion": "1.12", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "tests": [ + { + "description": "update operations support errorResponse assertions", + "runOnRequirements": [ + { + "minServerVersion": "4.0.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.2.0", + "topologies": [ + "sharded" + ] + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 8 + } + } + } + }, + { + "name": "updateOne", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "x": 1 + } + } + }, + "expectError": { + "errorCode": 8, + "errorResponse": { + "code": 8 + } + } + } + ] + } + ] +} diff --git a/test/spec/crud/unified/updateOne-errorResponse.yml b/test/spec/crud/unified/updateOne-errorResponse.yml new file mode 100644 index 0000000000..6d42195b0b --- /dev/null +++ b/test/spec/crud/unified/updateOne-errorResponse.yml @@ -0,0 +1,47 @@ +description: "updateOne-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +tests: + # Some drivers may still need to skip this test because the CRUD spec does not + # prescribe how drivers should formulate a WriteException beyond collecting a + # write or write concern error. + - description: "update operations support errorResponse assertions" + runOnRequirements: + - minServerVersion: "4.0.0" + topologies: [ single, replicaset ] + - minServerVersion: "4.2.0" + topologies: [ sharded ] + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ update ] + errorCode: &errorCode 8 # UnknownError + - name: updateOne + object: *collection0 + arguments: + filter: { _id: 1 } + update: { $set: { x: 1 } } + expectError: + errorCode: *errorCode + errorResponse: + code: *errorCode diff --git a/test/spec/unified-test-format/invalid/expectedError-errorResponse-type.json b/test/spec/unified-test-format/invalid/expectedError-errorResponse-type.json new file mode 100644 index 0000000000..6eb66d9b0b --- /dev/null +++ b/test/spec/unified-test-format/invalid/expectedError-errorResponse-type.json @@ -0,0 +1,25 @@ +{ + "description": "expectedError-errorResponse-type", + "schemaVersion": "1.12", + "createEntities": [ + { + "client": { + "id": "client0" + } + } + ], + "tests": [ + { + "description": "foo", + "operations": [ + { + "name": "foo", + "object": "client0", + "expectError": { + "errorResponse": 0 + } + } + ] + } + ] +} diff --git a/test/spec/unified-test-format/invalid/expectedError-errorResponse-type.yml b/test/spec/unified-test-format/invalid/expectedError-errorResponse-type.yml new file mode 100644 index 0000000000..e63f6ce892 --- /dev/null +++ b/test/spec/unified-test-format/invalid/expectedError-errorResponse-type.yml @@ -0,0 +1,15 @@ +description: "expectedError-errorResponse-type" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 "client0" + +tests: + - description: "foo" + operations: + - name: "foo" + object: *client0 + expectError: + errorResponse: 0 diff --git a/test/spec/unified-test-format/valid-pass/expectedError-errorResponse.json b/test/spec/unified-test-format/valid-pass/expectedError-errorResponse.json new file mode 100644 index 0000000000..177b1baf56 --- /dev/null +++ b/test/spec/unified-test-format/valid-pass/expectedError-errorResponse.json @@ -0,0 +1,70 @@ +{ + "description": "expectedError-errorResponse", + "schemaVersion": "1.12", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "test" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "tests": [ + { + "description": "Unsupported command", + "operations": [ + { + "name": "runCommand", + "object": "database0", + "arguments": { + "commandName": "unsupportedCommand", + "command": { + "unsupportedCommand": 1 + } + }, + "expectError": { + "errorResponse": { + "errmsg": { + "$$type": "string" + } + } + } + } + ] + }, + { + "description": "Unsupported query operator", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "$unsupportedQueryOperator": 1 + } + }, + "expectError": { + "errorResponse": { + "errmsg": { + "$$type": "string" + } + } + } + } + ] + } + ] +} diff --git a/test/spec/unified-test-format/valid-pass/expectedError-errorResponse.yml b/test/spec/unified-test-format/valid-pass/expectedError-errorResponse.yml new file mode 100644 index 0000000000..e10c25a1ed --- /dev/null +++ b/test/spec/unified-test-format/valid-pass/expectedError-errorResponse.yml @@ -0,0 +1,39 @@ +description: "expectedError-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name test + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +tests: + - description: "Unsupported command" + operations: + - name: runCommand + object: *database0 + arguments: + commandName: unsupportedCommand + command: { unsupportedCommand: 1 } + expectError: + # Avoid asserting the exact error since it may vary by server version + errorResponse: + errmsg: { $$type: "string" } + + - description: "Unsupported query operator" + operations: + - name: find + object: *collection0 + arguments: + filter: { $unsupportedQueryOperator: 1 } + expectError: + # Avoid asserting the exact error since it may vary by server version + errorResponse: + errmsg: { $$type: "string" } diff --git a/test/tools/unified-spec-runner/match.ts b/test/tools/unified-spec-runner/match.ts index 0e9ea113c8..e12062bf85 100644 --- a/test/tools/unified-spec-runner/match.ts +++ b/test/tools/unified-spec-runner/match.ts @@ -771,4 +771,8 @@ export function expectErrorCheck( if (expected.expectResult != null) { resultCheck(error, expected.expectResult as any, entities); } + + if (expected.errorResponse != null) { + resultCheck(error, expected.errorResponse, entities); + } } diff --git a/test/tools/unified-spec-runner/schema.ts b/test/tools/unified-spec-runner/schema.ts index 3b3daef804..353231cb99 100644 --- a/test/tools/unified-spec-runner/schema.ts +++ b/test/tools/unified-spec-runner/schema.ts @@ -368,6 +368,7 @@ export interface ExpectedError { errorLabelsContain?: string[]; errorLabelsOmit?: string[]; expectResult?: unknown; + errorResponse?: Document; } export interface ExpectedLogMessage { diff --git a/test/unit/error.test.ts b/test/unit/error.test.ts index 678398d27e..4a9d1423a5 100644 --- a/test/unit/error.test.ts +++ b/test/unit/error.test.ts @@ -113,6 +113,24 @@ describe('MongoErrors', () => { expect(err.message).to.equal(errorMessage); expect(err.someData).to.equal(12345); }); + context('errorResponse property', function () { + it(`should set errorResponse to raw results document passed in`, function () { + const errorDoc = { message: 'A test error', someData: 12345 }; + const err = new MongoServerError(errorDoc); + expect(err).to.be.an.instanceof(Error); + expect(err.errorResponse).to.deep.equal(errorDoc); + }); + it(`should not construct enumerated key 'errorResponse' if present`, function () { + const errorDoc = { + message: 'A test error', + errorResponse: 'I will not be an enumerated key' + }; + const err = new MongoServerError(errorDoc); + expect(err).to.be.an.instanceof(Error); + expect(err.errorResponse).to.deep.equal(errorDoc); + expect(err.errorResponse?.errorResponse).to.deep.equal('I will not be an enumerated key'); + }); + }); }); describe('MongoNetworkError#constructor', () => {