/**
 * Test that non-transaction snapshot reads only see committed data.
 *
 * Non-transaction snapshot reads with atClusterTime (or afterClusterTime) will wait for
 * the majority commit point to move past the atClusterTime (or afterClusterTime) before they can
 * read.
 *
 * @tags: [
 *   requires_fcv_47,
 *   requires_majority_read_concern,
 *   requires_persistence,
 * ]
 */
(function() {
"use strict";

load("jstests/libs/write_concern_util.js");  // For stopReplicationOnSecondaries.

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

const dbName = "test";
const collName = "coll";

const primary = replSet.getPrimary();
const secondary = replSet.getSecondary();

const primaryDB = primary.getDB(dbName);
const secondaryDB = secondary.getDB(dbName);

const committedTimestamp =
    assert.commandWorked(primaryDB.runCommand({insert: collName, documents: [{_id: 0}]}))
        .operationTime;
replSet.awaitLastOpCommitted();

stopReplicationOnSecondaries(replSet);

// This document will not replicate.
const nonCommittedTimestamp =
    assert.commandWorked(primaryDB.runCommand({insert: collName, documents: [{_id: 1}]}))
        .operationTime;

for (let db of [primaryDB, secondaryDB]) {
    jsTestLog(`Reading from ${db.getMongo()}`);
    for (let readConcern of [{level: "snapshot"},
                             {level: "snapshot", atClusterTime: committedTimestamp},
                             {level: "snapshot", afterClusterTime: committedTimestamp}]) {
        jsTestLog("Testing committed reads with readConcern " + tojson(readConcern));
        // Only the replicated document is visible to snapshot read, even on primary.
        let res = assert.commandWorked(db.runCommand({find: collName, readConcern: readConcern}));
        assert.sameMembers(res.cursor.firstBatch, [{_id: 0}], tojson(res));

        res = assert.commandWorked(db.runCommand(
            {aggregate: collName, pipeline: [], cursor: {}, readConcern: readConcern}));
        assert.sameMembers(res.cursor.firstBatch, [{_id: 0}], tojson(res));
    }

    for (let readConcern of [{level: "snapshot", atClusterTime: nonCommittedTimestamp},
                             {level: "snapshot", afterClusterTime: nonCommittedTimestamp}]) {
        jsTestLog("Testing non-committed reads with readConcern " + tojson(readConcern));
        // Test that snapshot reads ahead of the committed timestamp are blocked.
        assert.commandFailedWithCode(db.runCommand({
            find: collName,
            readConcern: readConcern,
            maxTimeMS: 1000,
        }),
                                     ErrorCodes.MaxTimeMSExpired);

        assert.commandFailedWithCode(db.runCommand({
            aggregate: collName,
            pipeline: [],
            cursor: {},
            readConcern: readConcern,
            maxTimeMS: 1000
        }),
                                     ErrorCodes.MaxTimeMSExpired);
    }
}

restartReplicationOnSecondaries(replSet);
replSet.stopSet();
}());
