/**
 * This tests that NotPrimary errors include a TopologyVersion field. (Note that shutdown errors
 * include a TopologyVersion field when the server is in quiesce mode. This is tested in
 * quiesce_mode.js)
 */

import {ReplSetTest} from "jstests/libs/replsettest.js";

const rst = new ReplSetTest({nodes: 1});
rst.startSet();
rst.initiate();

const dbName = "test";
const collName = "notprimary_errors_return_topology_version";
const primary = rst.getPrimary();
const primaryDB = primary.getDB(dbName);

const notPrimaryErrorCodes = [
    ErrorCodes.InterruptedDueToReplStateChange,
    ErrorCodes.NotWritablePrimary,
    ErrorCodes.NotPrimaryNoSecondaryOk,
    ErrorCodes.NotPrimaryOrSecondary,
    ErrorCodes.PrimarySteppedDown
];

const otherErrorCodes =
    [ErrorCodes.InterruptedAtShutdown, ErrorCodes.ShutdownInProgress, ErrorCodes.InternalError];

function runFailInCommandDispatch(errorCode, isWCError) {
    jsTestLog(`Testing with errorCode: ${
        errorCode} with failPoint: failWithErrorCodeInCommandDispatch, isWCError: ${isWCError}.`);

    if (isWCError) {
        assert.commandWorked(primary.adminCommand({
            configureFailPoint: "failCommand",
            mode: "alwaysOn",
            data: {
                writeConcernError: {code: NumberInt(errorCode), errmsg: "dummy"},
                failCommands: ["insert"],
            }
        }));
    } else {
        assert.commandWorked(primary.adminCommand({
            configureFailPoint: "failCommand",
            mode: "alwaysOn",
            data: {
                errorCode: NumberInt(errorCode),
                failCommands: ["insert"],
            }
        }));
    }

    const res = primaryDB.runCommand({insert: collName, documents: [{x: 1}]});
    assert.commandFailedWithCode(res, errorCode);
    // Only NotPrimary errors should return TopologyVersion in the response.
    if (notPrimaryErrorCodes.includes(errorCode)) {
        assert(res.hasOwnProperty("topologyVersion"), tojson(res));
    } else {
        assert(!res.hasOwnProperty("topologyVersion"), tojson(res));
    }
    assert.commandWorked(primary.adminCommand({configureFailPoint: "failCommand", mode: "off"}));
}

function runFailInRunCommand(errorCode) {
    jsTestLog(
        `Testing with errorCode: ${errorCode} with failPoint: failWithErrorCodeInRunCommand.`);

    assert.commandWorked(primary.adminCommand({
        configureFailPoint: "failWithErrorCodeInRunCommand",
        mode: "alwaysOn",
        data: {
            errorCode: NumberInt(errorCode),
        }
    }));

    const res = primaryDB.runCommand({insert: collName, documents: [{x: 1}]});
    assert.commandFailedWithCode(res, errorCode);
    // Only NotPrimary errors should return TopologyVersion in the response.
    if (notPrimaryErrorCodes.includes(errorCode)) {
        assert(res.hasOwnProperty("topologyVersion"), tojson(res));
    } else {
        assert(!res.hasOwnProperty("topologyVersion"), tojson(res));
    }
    // We expect 'configureFailPoint' to succeed here since the 'failWithErrorCodeInRunCommand' fail
    // point only affects commands that support writeConcern.
    assert.commandWorked(
        primary.adminCommand({configureFailPoint: "failWithErrorCodeInRunCommand", mode: "off"}));
}

notPrimaryErrorCodes.forEach(function(code) {
    runFailInCommandDispatch(code, true /* isWCError */);
    runFailInCommandDispatch(code, false /* isWCError */);
});

// Test that errors that are not NotPrimary errors will not return a TopologyVersion.
otherErrorCodes.forEach(function(code) {
    runFailInCommandDispatch(code, true /* isWCError */);
    runFailInCommandDispatch(code, false /* isWCError */);
});

notPrimaryErrorCodes.forEach(function(code) {
    runFailInRunCommand(code);
});
otherErrorCodes.forEach(function(code) {
    runFailInRunCommand(code);
});
rst.stopSet();