// Test invocations of {rolesInfo: ...} command
//
// @tags: [
//   # The test runs commands that are not allowed with security token: createRole, dropRole.
//   not_allowed_with_signed_security_token,
//   requires_multi_updates,
//   requires_non_retryable_commands,
//   # `rolesInfo` response has empty database name that doesn't work with the response checker of
//   # simulate_mongoq override.
//   simulate_mongoq_incompatible,
// ]

// Setup some sample roles.
const dbname = db.getName();
const fqReadRoleName = {
    role: 'read',
    db: dbname
};
const fqTestRoleName = {
    role: 'testRoleJSCoreRolesInfo',
    db: dbname
};
const testRolePrivs = [
    {resource: {db: dbname, collection: ''}, actions: ['insert']},
];
const testRoleRoles = [fqReadRoleName];
assert.commandWorked(db.runCommand(
    {createRole: 'testRoleJSCoreRolesInfo', privileges: testRolePrivs, roles: testRoleRoles}));

function matchRoleFn(match) {
    return (role) => (role.db === match.db) && (role.role === match.role);
}

function requireRole(roleSet, role) {
    const ret = roleSet.filter(matchRoleFn(role));
    assert.eq(ret.length, 1, "Didn't find " + tojson(role) + " in " + tojson(roleSet));
    return ret[0];
}

function requireNoRole(roleSet, role) {
    const ret = roleSet.filter(matchRoleFn(role));
    assert.eq(ret.length, 0, "Unexpectedly found " + tojson(role) + " in " + tojson(roleSet));
}

function checkForUserDefinedRole(roleSet, expectPrivs) {
    const role = requireRole(roleSet, fqTestRoleName);
    if (expectPrivs) {
        assert(bsonWoCompare(role.privileges, testRolePrivs) === 0,
               'Unexpected privileges in: ' + tojson(role));
    } else {
        assert(role.privileges === undefined, 'Unexpected privileges in: ' + tojson(role));
    }
    assert(bsonWoCompare(role.roles, testRoleRoles) === 0, 'Unexpected roles in: ' + tojson(role));
    assert(role.isBuiltin !== true, 'Unexpected isBuiltin: ' + tojson(role));
}

function checkForBuiltinRole(roleSet, fqRoleName) {
    const role = requireRole(roleSet, fqRoleName);
    assert.eq(role.roles.length, 0, 'Builtin roles must not have subordinates: ' + tojson(role));
    assert(role.isBuiltin !== false, 'Unexpected isBuiltin: ' + tojson(role));
}

function rolesInfo(query, showExtra = {}) {
    const cmd = Object.assign({rolesInfo: query}, showExtra);
    jsTest.log(tojson(cmd));
    const ret = assert.commandWorked(db.runCommand(cmd));
    assert(ret.roles !== undefined, 'No roles property in response');
    printjson(ret.roles);
    return ret.roles;
}

function rolesInfoSingle(query, extra = {}) {
    const ret = rolesInfo(query, extra);
    assert.eq(ret.length, 1, 'Unexpected roles (or no roles) in query: ' + tojson(query));
    return ret;
}

// {rolesInfo: 1, showBuiltinRoles: true, showPrivileges: true}
const allRoles = rolesInfo(1, {showBuiltinRoles: true, showPrivileges: true});
allRoles.forEach((r) => assert(r.isBuiltin !== undefined),
                 "Role must have 'isBuiltin' property for DB query");
checkForUserDefinedRole(allRoles, true);
checkForBuiltinRole(allRoles, fqReadRoleName);
requireNoRole(allRoles, {role: 'doesNotExist', db: dbname});

// {rolesInfo: 'testRoleJSCoreRolesInfo'} // User defined role
const stringRoles = rolesInfoSingle('testRoleJSCoreRolesInfo');
checkForUserDefinedRole(stringRoles, false);
requireNoRole(stringRoles, fqReadRoleName);

// {rolesInfo: 'read'} // Builtin role
const builtinStringRoles = rolesInfoSingle('read');
checkForBuiltinRole(builtinStringRoles, fqReadRoleName);

// {rolesInfo: {db: dbname, role: 'testRoleJSCoreRolesInfo'} // User defined role
const docRoles = rolesInfoSingle(fqTestRoleName);
checkForUserDefinedRole(docRoles, false);
requireNoRole(docRoles, fqReadRoleName);

// {rolesInfo: {db: dbname, role: 'read'} // Builtin role
const builtinDocRoles = rolesInfoSingle(fqReadRoleName);
checkForBuiltinRole(builtinDocRoles, fqReadRoleName);

// Multiroles: [ 'testRoleJSCoreRolesInfo', {db: dbname, role: 'read'}, 'readWrite' ]
const multiRoles = rolesInfo(['testRoleJSCoreRolesInfo', fqReadRoleName, 'readWrite']);
assert.eq(multiRoles.length, 3, 'Incorrect number of roles returned: ' + tojson(multiRoles));
checkForUserDefinedRole(multiRoles, false);
checkForBuiltinRole(multiRoles, fqReadRoleName);
checkForBuiltinRole(multiRoles, {db: dbname, role: 'readWrite'});

assert.commandWorked(db.runCommand({dropRole: 'testRoleJSCoreRolesInfo'}));
