// Tests that $$USER_ROLES is able to be used in an "update" and "findAndModify" commands.
// @tags: [requires_fcv_70]

import {ShardingTest} from "jstests/libs/shardingtest.js";

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

function initialize(db) {
    let engDoc = {
        _id: 0,
        allowedRoles: ["eng-app-prod", "eng-app-stg", "read"],
        allowedRole: "read",
        comment: "only for engineering team",
        teamMembers: ["John", "Ashley", "Gina"],
        yearlyEduBudget: 15000,
        yearlyTnEBudget: 2000,
        salesWins: 1000
    };

    let salesDoc = {
        _id: 1,
        allowedRoles: ["sales-person"],
        allowedRole: "observe",
        comment: "only for sales team",
        salesWins: 1000
    };

    let testUpdate = {_id: 2, allowedRole: "test", teamMembersRights: ["testUpdate"]};

    let testFindAndModify = {
        _id: 3,
        allowedRole: "write",
        teamMembersRights: ["testFindAndModify"]
    };

    let coll = db.getCollection(collName);
    assert.commandWorked(coll.insertMany([engDoc, salesDoc, testUpdate, testFindAndModify]));
}

// Test accessing $$USER_ROLES in the query portion of "update" command.
function runUpdateQuery(db) {
    let coll = db.getCollection(collName);

    let pre = coll.findOne(
        {$expr: {$eq: [{$setIntersection: ["$allowedRoles", "$$USER_ROLES.role"]}, []]}});
    var preSalesWins = pre.salesWins;

    assert.commandWorked(coll.update(
        {$expr: {$eq: [{$setIntersection: ["$allowedRoles", "$$USER_ROLES.role"]}, []]}},
        {$inc: {salesWins: 1000}},
        {multi: true}));

    let post = coll.findOne(
        {$expr: {$eq: [{$setIntersection: ["$allowedRoles", "$$USER_ROLES.role"]}, []]}});
    var postSalesWins = post.salesWins;

    assert.eq(postSalesWins, preSalesWins + 1000);
}

// Test accessing $$USER_ROLES in the update portion of "update" command.
function runUpdateUpdate(db) {
    let coll = db.getCollection(collName);

    assert.commandWorked(
        coll.update({_id: 2}, [{$set: {"teamMembersRights": "$$USER_ROLES.role"}}]));

    let post = coll.findOne({_id: 2});

    let expectedResult = {
        _id: 2,
        allowedRole: "test",
        teamMembersRights: ["readWriteAnyDatabase", "read"]
    };

    assert.eq(post, expectedResult);
}

// Test accessing $$USER_ROLES in the query portion of "findAndModify" command.
function runFindAndModifyQuery(db) {
    let coll = db.getCollection(collName);

    let pre = coll.findOne({$expr: {allowedRole: "$$USER_ROLES.role"}});
    var preSalesWins = pre.salesWins;

    db.coll.findAndModify({
        query: {allowedRole: "read", $expr: {allowedRole: "$$USER_ROLES.role"}},
        update: {$inc: {salesWins: 1000}}
    });

    let post = coll.findOne({$expr: {allowedRole: "$$USER_ROLES.role"}});
    var postSalesWins = post.salesWins;

    assert.eq(postSalesWins, preSalesWins + 1000);
}

// Test accessing $$USER_ROLES in the update portion of "findAndModify" command.
function runFindAndModifyUpdate(db) {
    let coll = db.getCollection(collName);

    coll.findAndModify({
        query: {allowedRole: "write"},
        update: [{$set: {"teamMembersRights": "$$USER_ROLES.role"}}]
    });

    let post = coll.findOne({_id: 3});

    let expectedResult = {
        _id: 3,
        allowedRole: "write",
        teamMembersRights: ["readWriteAnyDatabase", "read"]
    };

    assert.eq(post, expectedResult);
}

function runTest(conn, st = null) {
    // Create a user on the admin database.
    let admin = conn.getDB("admin");
    assert.commandWorked(admin.runCommand({createUser: "admin", pwd: "admin", roles: ["root"]}));
    admin.auth("admin", "admin");

    if (st) {
        // Shard the collection that will be used in the update and findAndModify commands.
        assert.commandWorked(conn.getDB("admin").runCommand({enableSharding: dbName}));
        st.shardColl(conn.getDB(dbName).getCollection(collName), {allowedRole: 1});
    }

    const db = conn.getDB(dbName);

    // Create a user that has roles on more than one database. The readWriteAnyDatabase is
    // necessary for the inserts that follow to work.
    assert.commandWorked(db.runCommand({
        createUser: "user",
        pwd: "pwd",
        roles: [{role: "readWriteAnyDatabase", db: "admin"}, {role: "read", db: dbName}]
    }));

    // Logout of the admin user so that we can log into the other user so we can access those
    // roles with $$USER_ROLES below.
    admin.logout();
    db.auth("user", "pwd");

    initialize(db);

    runUpdateQuery(db);

    runUpdateUpdate(db);

    runFindAndModifyQuery(db);

    runFindAndModifyUpdate(db);

    db.logout();
}

jsTest.log("Test standalone");
const mongod = MongoRunner.runMongod({auth: ""});
runTest(mongod);
MongoRunner.stopMongod(mongod);

jsTest.log("Test sharded cluster");
const st = new ShardingTest({
    mongos: 1,
    config: 1,
    shards: 2,
    keyFile: 'jstests/libs/key1',
});

runTest(st.s, st);
st.stop();
