summaryrefslogtreecommitdiff
path: root/jstests/noPassthrough/change_stream_error_label.js
blob: 4902521463b69dbb8075b21b614372e8be9b64e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/**
 * Test that a change stream pipeline which encounters a retryable exception responds to the client
 * with an error object that includes the "ResumableChangeStreamError" label.
 * @tags: [
 *   requires_replication,
 *   uses_change_streams,
 * ]
 */
(function() {
"use strict";

// Create a two-node replica set so that we can issue a request to the Secondary.
const rst = new ReplSetTest({nodes: 2});
rst.startSet();
rst.initiate();
rst.awaitSecondaryNodes();

// Disable "secondaryOk" on the connection so that we are not allowed to run on the Secondary.
const testDB = rst.getSecondary().getDB(jsTestName());
testDB.getMongo().setSecondaryOk(false);
const coll = testDB.test;

// Issue a change stream. We should fail with a NotPrimaryNoSecondaryOk error.
const err = assert.throws(() => coll.watch([]));
assert.commandFailedWithCode(err, ErrorCodes.NotPrimaryNoSecondaryOk);

// Confirm that the response includes the "ResumableChangeStreamError" error label.
assert("errorLabels" in err, err);
assert.contains("ResumableChangeStreamError", err.errorLabels, err);

// Now verify that the 'failGetMoreAfterCursorCheckout' failpoint can effectively exercise the
// error label generation logic for change stream getMores.
function testFailGetMoreAfterCursorCheckoutFailpoint({errorCode, expectedLabel}) {
    // Re-enable "secondaryOk" on the test connection.
    testDB.getMongo().setSecondaryOk();

    // Activate the failpoint and set the exception that it will throw.
    assert.commandWorked(testDB.adminCommand({
        configureFailPoint: "failGetMoreAfterCursorCheckout",
        mode: "alwaysOn",
        data: {"errorCode": errorCode}
    }));

    // Now open a valid $changeStream cursor...
    const aggCmdRes = assert.commandWorked(
        coll.runCommand("aggregate", {pipeline: [{$changeStream: {}}], cursor: {}}));

    // ... run a getMore using the cursorID from the original command response, and confirm that the
    // expected error was thrown...
    const getMoreRes = assert.commandFailedWithCode(
        testDB.runCommand({getMore: aggCmdRes.cursor.id, collection: coll.getName()}), errorCode);

    /// ... and confirm that the label is present or absent depending on the "expectedLabel" value.
    const errorLabels = (getMoreRes.errorLabels || []);
    assert.eq("errorLabels" in getMoreRes, expectedLabel, getMoreRes);
    assert.eq(errorLabels.includes("ResumableChangeStreamError"), expectedLabel, getMoreRes);

    // Finally, disable the failpoint.
    assert.commandWorked(
        testDB.adminCommand({configureFailPoint: "failGetMoreAfterCursorCheckout", mode: "off"}));
}
// Test the expected output for both resumable and non-resumable error codes.
testFailGetMoreAfterCursorCheckoutFailpoint(
    {errorCode: ErrorCodes.ShutdownInProgress, expectedLabel: true});
testFailGetMoreAfterCursorCheckoutFailpoint(
    {errorCode: ErrorCodes.ReadConcernMajorityNotAvailableYet, expectedLabel: true});
testFailGetMoreAfterCursorCheckoutFailpoint(
    {errorCode: ErrorCodes.FailedToParse, expectedLabel: false});

rst.stopSet();
}());