/**
 * Test that transaction operations wait for write concern (or don't) correctly on noop writes.
 *
 * We run most commands on a different connection. If the commands were run on the same
 * connection, then the client last op for the noop writes would be set by the previous operation.
 * By using a fresh connection the client last op begins as null.  This test explicitly tests that
 * write concern for noop writes works when the client last op has not already been set by a
 * duplicate operation.
 * @tags: [uses_transactions, uses_prepare_transaction]
 */
(function() {
"use strict";

load("jstests/libs/write_concern_util.js");
load("jstests/core/txns/libs/prepare_helpers.js");

const dbName = "test";
const collNameBase = "coll";

const rst = new ReplSetTest({
    nodes: [{}, {rsConfig: {priority: 0}}],
});
rst.startSet();
rst.initiate();

const primary = rst.getPrimary();
const primaryDB = primary.getDB(dbName);

const failTimeoutMS = 1000;
const successTimeoutMS = ReplSetTest.kDefaultTimeoutMS;

function runTest(readConcernLevel) {
    jsTestLog("Testing " + readConcernLevel);

    const collName = `${collNameBase}_${readConcernLevel}`;
    assert.commandWorked(primaryDB[collName].insert(
        [{x: 1}, {x: 2}, {x: 3}, {x: 4}, {x: 5}, {x: 6}], {writeConcern: {w: "majority"}}));

    jsTestLog("Unprepared Abort Setup");
    const mongo1 = new Mongo(primary.host);
    const session1 = mongo1.startSession();
    const sessionDB1 = session1.getDatabase(dbName);
    session1.startTransaction({
        writeConcern: {w: "majority", wtimeout: successTimeoutMS},
        readConcern: {level: readConcernLevel}
    });
    const fruitlessUpdate1 = {update: collName, updates: [{q: {x: 1}, u: {$set: {x: 1}}}]};
    printjson(assert.commandWorked(sessionDB1.runCommand(fruitlessUpdate1)));

    jsTestLog("Prepared Abort Setup");
    const mongo2 = new Mongo(primary.host);
    const session2 = mongo2.startSession();
    const sessionDB2 = session2.getDatabase(dbName);
    session2.startTransaction({
        writeConcern: {w: "majority", wtimeout: failTimeoutMS},
        readConcern: {level: readConcernLevel}
    });
    const fruitlessUpdate2 = {update: collName, updates: [{q: {x: 2}, u: {$set: {x: 2}}}]};
    printjson(assert.commandWorked(sessionDB2.runCommand(fruitlessUpdate2)));
    PrepareHelpers.prepareTransaction(session2);

    jsTestLog("Prepare Setup");
    const mongo3 = new Mongo(primary.host);
    const session3 = mongo3.startSession();
    const sessionDB3 = session3.getDatabase(dbName);
    session3.startTransaction({
        writeConcern: {w: "majority", wtimeout: failTimeoutMS},
        readConcern: {level: readConcernLevel}
    });
    const fruitlessUpdate3 = {update: collName, updates: [{q: {x: 3}, u: {$set: {x: 3}}}]};
    printjson(assert.commandWorked(sessionDB3.runCommand(fruitlessUpdate3)));

    jsTestLog("Unprepared Commit Setup");
    const mongo4 = new Mongo(primary.host);
    const session4 = mongo4.startSession();
    const sessionDB4 = session4.getDatabase(dbName);
    session4.startTransaction({
        writeConcern: {w: "majority", wtimeout: failTimeoutMS},
        readConcern: {level: readConcernLevel}
    });
    const fruitlessUpdate4 = {update: collName, updates: [{q: {x: 4}, u: {$set: {x: 4}}}]};
    printjson(assert.commandWorked(sessionDB4.runCommand(fruitlessUpdate4)));

    jsTestLog("Prepared Commit Setup");
    const mongo5 = new Mongo(primary.host);
    const session5 = mongo5.startSession();
    const sessionDB5 = session5.getDatabase(dbName);
    session5.startTransaction({
        writeConcern: {w: "majority", wtimeout: failTimeoutMS},
        readConcern: {level: readConcernLevel}
    });
    const fruitlessUpdate5 = {update: collName, updates: [{q: {x: 5}, u: {$set: {x: 5}}}]};
    printjson(assert.commandWorked(sessionDB5.runCommand(fruitlessUpdate5)));
    let prepareTS5 = PrepareHelpers.prepareTransaction(session5);

    jsTestLog("Stop replication");
    stopReplicationOnSecondaries(rst);

    jsTestLog("Advance OpTime on primary, with replication stopped");

    printjson(assert.commandWorked(primaryDB.runCommand({insert: collName, documents: [{}]})));

    jsTestLog("Run test commands, with replication stopped");

    jsTestLog("Unprepared Abort Test");
    assert.commandWorked(session1.abortTransaction_forTesting());

    jsTestLog("Prepared Abort Test");
    assert.commandFailedWithCode(session2.abortTransaction_forTesting(),
                                 ErrorCodes.WriteConcernFailed);

    jsTestLog("Prepare Test");
    assert.commandFailedWithCode(
        session3.getDatabase('admin').adminCommand(
            {prepareTransaction: 1, writeConcern: {w: "majority", wtimeout: failTimeoutMS}}),
        ErrorCodes.WriteConcernFailed);
    assert.commandFailedWithCode(session3.abortTransaction_forTesting(),
                                 ErrorCodes.WriteConcernFailed);

    jsTestLog("Unprepared Commit Test");
    assert.commandFailedWithCode(session4.commitTransaction_forTesting(),
                                 ErrorCodes.WriteConcernFailed);

    jsTestLog("Prepared Commit Test");
    assert.commandFailedWithCode(session5.getDatabase('admin').adminCommand({
        commitTransaction: 1,
        commitTimestamp: prepareTS5,
        writeConcern: {w: "majority", wtimeout: failTimeoutMS}
    }),
                                 ErrorCodes.WriteConcernFailed);
    // Send commit with the shell helper to reset the shell's state.
    assert.commandFailedWithCode(session5.commitTransaction_forTesting(),
                                 ErrorCodes.WriteConcernFailed);

    jsTestLog("Restart replication");
    restartReplicationOnSecondaries(rst);

    jsTestLog("Try transaction with replication enabled");

    // Unprepared Abort.
    session1.startTransaction({
        writeConcern: {w: "majority", wtimeout: successTimeoutMS},
        readConcern: {level: readConcernLevel}
    });
    assert.commandWorked(sessionDB1.runCommand(fruitlessUpdate1));
    assert.commandWorked(session1.abortTransaction_forTesting());

    // Prepared Abort.
    session2.startTransaction({
        writeConcern: {w: "majority", wtimeout: successTimeoutMS},
        readConcern: {level: readConcernLevel}
    });
    assert.commandWorked(sessionDB2.runCommand(fruitlessUpdate2));
    PrepareHelpers.prepareTransaction(session2);
    assert.commandWorked(session2.abortTransaction_forTesting());

    // Testing prepare is no different then prepared abort or prepared commit.

    // Unprepared Commit.
    session4.startTransaction({
        writeConcern: {w: "majority", wtimeout: successTimeoutMS},
        readConcern: {level: readConcernLevel}
    });
    assert.commandWorked(sessionDB4.runCommand(fruitlessUpdate4));
    assert.commandWorked(session4.commitTransaction_forTesting());

    // Prepared Commit.
    session5.startTransaction({
        writeConcern: {w: "majority", wtimeout: successTimeoutMS},
        readConcern: {level: readConcernLevel}
    });
    assert.commandWorked(sessionDB5.runCommand(fruitlessUpdate5));
    prepareTS5 = PrepareHelpers.prepareTransaction(session5);
    assert.commandWorked(session5.getDatabase('admin').adminCommand({
        commitTransaction: 1,
        commitTimestamp: prepareTS5,
        writeConcern: {w: "majority", wtimeout: successTimeoutMS}
    }));
    // Send commit with the shell helper to reset the shell's state.
    assert.commandWorked(session5.commitTransaction_forTesting());

    // Unprepared abort already is using a "used connection" for this success test.
}

runTest("local");
runTest("majority");
runTest("snapshot");

rst.stopSet();
}());
