Skip to content

Commit 88b2ad2

Browse files
authored
test(NODE-7366): add case 1 and 2 to retryable writes prose test 6 (#4887)
1 parent 4fb0a0a commit 88b2ad2

File tree

1 file changed

+131
-28
lines changed

1 file changed

+131
-28
lines changed

test/integration/retryable-writes/retryable_writes.spec.prose.test.ts

Lines changed: 131 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -346,21 +346,21 @@ describe('Retryable Writes Spec Prose', () => {
346346
);
347347
});
348348

349-
describe('6. Test that drivers return the original error after encountering multiple WriteConcernErrors with a RetryableWriteError label', () => {
349+
describe('6. Test error propagation after encountering multiple errors', () => {
350+
// These tests MUST:
351+
// - be implemented by any driver that implements the Command Monitoring specification.
352+
// - only run against replica sets as mongos does not propagate the NoWritesPerformed label to the drivers.
353+
// - be run against server versions 6.0 and above.
354+
// - be implemented by any driver that has implemented the Client Backpressure specification.
355+
356+
// Additionally, this test requires drivers to set a fail point after an `insertOne` operation but before the subsequent
357+
// retry. Drivers that are unable to set a failCommand after the CommandFailedEvent SHOULD use mocking or write a unit test
358+
// to cover the same sequence of events.
359+
350360
let client: MongoClient;
351361
let collection: Collection<{ _id: 1 }>;
352362

353363
beforeEach(async function () {
354-
// This test MUST:
355-
// - be implemented by any driver that implements the Command Monitoring specification,
356-
// - only run against replica sets as mongos does not propagate the NoWritesPerformed label to the drivers.
357-
// - be run against server versions 6.0 and above.
358-
// - be implemented by any driver that has implemented the Client Backpressure specification.
359-
360-
// Additionally, this test requires drivers to set a fail point after an `insertOne` operation but before the subsequent
361-
// retry. Drivers that are unable to set a failCommand after the CommandFailedEvent SHOULD use mocking or write a unit test
362-
// to cover the same sequence of events.
363-
364364
// 1. Create a client with `retryWrites=true`.
365365
client = this.configuration.newClient({ monitorCommands: true, retryWrites: true });
366366
await client
@@ -372,26 +372,68 @@ describe('Retryable Writes Spec Prose', () => {
372372
});
373373

374374
afterEach(async function () {
375-
// 5. Disable the fail point:
376-
// ```javascript
377-
// {
378-
// configureFailPoint: "failCommand",
379-
// mode: "off"
380-
// }
381-
// ```
382-
383-
// (we don't use a failPoint, so we use sinon.restore instead)
375+
// 5. Disable the fail point (we don't use a failPoint, so we use sinon.restore instead)
384376
sinon.restore();
385377
await client.close();
386378
});
387379

388380
it(
389-
'when a retry attempt fails with an error labeled NoWritesPerformed, drivers MUST return the original error',
390-
{ requires: { topology: 'replicaset', mongodb: '>=4.2.9' } },
381+
'Case 1: Test that drivers return the correct error when receiving only errors without NoWritesPerformed',
382+
{ requires: { topology: 'replicaset', mongodb: '>=6.0' } },
391383
async () => {
392384
// 2. Configure a fail point with error code `91` (ShutdownInProgress) with the `RetryableError` and
393385
// `SystemOverloadedError` error labels:
386+
// ```javascript
387+
// {
388+
// configureFailPoint: "failCommand",
389+
// mode: {times: 1},
390+
// data: {
391+
// failCommands: ["insert"],
392+
// errorLabels: ["RetryableError", "SystemOverloadedError"],
393+
// errorCode: 91
394+
// }
395+
// }
396+
// ```
397+
398+
// 3. Via the command monitoring CommandFailedEvent, configure a fail point with error code `10107` (NotWritablePrimary):
399+
// ```javascript
400+
// {
401+
// configureFailPoint: "failCommand",
402+
// mode: "alwaysOn",
403+
// data: {
404+
// failCommands: ["insert"],
405+
// errorCode: 10107,
406+
// errorLabels: ["RetryableError", "SystemOverloadedError"]
407+
// }
408+
// }
409+
// ```
410+
// Configure the `10107` fail point command only if the the failed event is for the `91` error configured in step 2.
411+
const serverCommandStub = sinon
412+
.stub(Server.prototype, 'command')
413+
.callsFake(async function () {
414+
throw new MongoServerError({
415+
message: 'Server Error',
416+
errorLabels: [MongoErrorLabel.RetryableError, MongoErrorLabel.SystemOverloadedError],
417+
code: serverCommandStub.callCount === 1 ? 91 : 10107,
418+
ok: 0
419+
});
420+
});
421+
422+
// 4. Attempt an `insertOne` operation on any record for any database and collection. Expect the `insertOne` to fail with a
423+
// server error. Assert that the error code of the server error is `10107`.
424+
const insertResult = await collection.insertOne({ _id: 1 }).catch(error => error);
394425

426+
expect(insertResult).to.be.instanceOf(MongoServerError);
427+
expect(insertResult).to.have.property('code', 10107);
428+
}
429+
);
430+
431+
it(
432+
'Case 2: Test that drivers return the correct error when receiving only errors with NoWritesPerformed',
433+
{ requires: { topology: 'replicaset', mongodb: '>=6.0' } },
434+
async () => {
435+
// 2. Configure a fail point with error code `91` (ShutdownInProgress) with the `RetryableError`,
436+
// `SystemOverloadedError`, and `NoWritesPerformed` error labels:
395437
// ```javascript
396438
// {
397439
// configureFailPoint: "failCommand",
@@ -406,21 +448,18 @@ describe('Retryable Writes Spec Prose', () => {
406448

407449
// 3. Via the command monitoring CommandFailedEvent, configure a fail point with error code `10107` (NotWritablePrimary)
408450
// and a NoWritesPerformed label:
409-
410451
// ```javascript
411452
// {
412453
// configureFailPoint: "failCommand",
413454
// mode: "alwaysOn",
414455
// data: {
415456
// failCommands: ["insert"],
416457
// errorCode: 10107,
417-
// errorLabels: ["RetryableError", "SystemOverloadedError", , "NoWritesPerformed"]
458+
// errorLabels: ["RetryableError", "SystemOverloadedError", "NoWritesPerformed"]
418459
// }
419460
// }
420461
// ```
421-
422-
// Drivers SHOULD only configure the `10107` fail point command if the the failed event is for the `91` error configured
423-
// in step 2.
462+
// Configure the `10107` fail point command only if the the failed event is for the `91` error configured in step 2.
424463
const serverCommandStub = sinon
425464
.stub(Server.prototype, 'command')
426465
.callsFake(async function () {
@@ -436,14 +475,78 @@ describe('Retryable Writes Spec Prose', () => {
436475
});
437476
});
438477

478+
// 4. Attempt an `insertOne` operation on any record for any database and collection. Expect the `insertOne` to fail with a
479+
// server error. Assert that the error code of the server error is 91.
439480
const insertResult = await collection.insertOne({ _id: 1 }).catch(error => error);
440481

441482
expect(serverCommandStub.callCount).to.equal(6);
442483

484+
expect(insertResult).to.be.instanceOf(MongoServerError);
485+
expect(insertResult).to.have.property('code', 91);
486+
}
487+
);
488+
489+
it(
490+
'Case 3: Test that drivers return the correct error when receiving some errors with NoWritesPerformed and some without NoWritesPerformed',
491+
{ requires: { topology: 'replicaset', mongodb: '>=6.0' } },
492+
async () => {
493+
// 2. Configure the client to listen to CommandFailedEvents. In the attached listener, configure a fail point with error
494+
// code `91` (NotWritablePrimary) and the `NoWritesPerformed`, `RetryableError` and `SystemOverloadedError` labels:
495+
// ```javascript
496+
// {
497+
// configureFailPoint: "failCommand",
498+
// mode: "alwaysOn",
499+
// data: {
500+
// failCommands: ["insert"],
501+
// errorLabels: ["RetryableError", "SystemOverloadedError", "NoWritesPerformed"],
502+
// errorCode: 91
503+
// }
504+
// }
505+
// ```
506+
507+
// 3. Configure a fail point with error code `91` (ShutdownInProgress) with the `RetryableError` and
508+
// `SystemOverloadedError` error labels but without the `NoWritesPerformed` error label:
509+
// ```javascript
510+
// {
511+
// configureFailPoint: "failCommand",
512+
// mode: {times: 1},
513+
// data: {
514+
// failCommands: ["insert"],
515+
// errorLabels: ["RetryableError", "SystemOverloadedError"],
516+
// errorCode: 91
517+
// }
518+
// }
519+
// ```
520+
const serverCommandStub = sinon
521+
.stub(Server.prototype, 'command')
522+
.callsFake(async function () {
523+
// First call: error WITHOUT NoWritesPerformed
524+
// Subsequent calls: error WITH NoWritesPerformed
525+
const errorLabels =
526+
serverCommandStub.callCount === 1
527+
? [MongoErrorLabel.RetryableError, MongoErrorLabel.SystemOverloadedError]
528+
: [
529+
MongoErrorLabel.RetryableError,
530+
MongoErrorLabel.SystemOverloadedError,
531+
MongoErrorLabel.NoWritesPerformed
532+
];
533+
534+
throw new MongoServerError({
535+
message: 'Server Error',
536+
errorLabels,
537+
code: 91,
538+
ok: 0
539+
});
540+
});
541+
443542
// 4. Attempt an `insertOne` operation on any record for any database and collection. Expect the `insertOne` to fail with a
444-
// server error. Assert that the error code of the server error is 91.
543+
// server error. Assert that the error code of the server error is 91. Assert that the error does not contain the
544+
// error label `NoWritesPerformed`.
545+
const insertResult = await collection.insertOne({ _id: 1 }).catch(error => error);
546+
445547
expect(insertResult).to.be.instanceOf(MongoServerError);
446548
expect(insertResult).to.have.property('code', 91);
549+
expect(insertResult.errorLabels).to.not.include(MongoErrorLabel.NoWritesPerformed);
447550
}
448551
);
449552
});

0 commit comments

Comments
 (0)