/**
 * This file tests that "user:<username>@<db>" shows up in the logs.
 */

// TODO(schwerin) Re-enable this test after resolving corresponding TODO in mongo/util/log.cpp.
if (0) {
    /**
     * Extracts information from a mongod/mongos log entry.
     *
     * @param line {string} a single line of log.
     *
     * @return {Object} format:
     *
     *     {
     *       id: <string>, // thread id of the log line.
     *       // list of users logged in. Can be empty.
     *       users: <Object> // map of db name to user name
     *     }
     */
    var parseLog = function(line) {
        var THREAD_ID_PATTERN = / [012]?\d:\d\d:\d\d\.\d\d\d \[(.+)\] /;
        var ID_USER_PATTERN = new RegExp(THREAD_ID_PATTERN.source + 'user:([^ ]*) ');
        var res = THREAD_ID_PATTERN.exec(line);

        if (res == null) {
            return null;
        }

        var logInfo = {id: res[1], users: {}};

        var userLog = null;
        res = ID_USER_PATTERN.exec(line);

        if (res != null) {
            userLog = res[2];
            // should not have trailing commas
            assert.neq(',', userLog[userLog.length - 1], 'Bad user log list format: ' + line);

            userLog.split(',').forEach(function(userData) {
                var userAndDB = userData.split('@');
                assert.eq(2,
                          userAndDB.length,
                          'Bad user db pair format: ' + userData + ' from line: ' + line);
                logInfo.users[userAndDB[1]] = userAndDB[0];
            });
        }

        return logInfo;
    };

    /**
     * Performs a series of test on user id logging.
     *
     * @param conn1 {Mongo} the connection object to use for logging in users.
     * @param conn2 {Mongo} another connection object different from conn1.
     */
    var doTest = function(conn1, conn2) {
        var connInfo1 = {
            id: null,      // thread id of this connection
            mongo: conn1,  // connection object
            users: {}      // contains authenticated users represented as a map of db to user names.
        };

        var connInfo2 = {id: null, mongo: conn2, users: {}};

        var conn1Auth =
            [{user: 'foo', pwd: 'bar', db: 'test'}, {user: 'chun', pwd: 'li', db: 'sf'}];

        var conn2Auth =
            [{user: 'root', pwd: 'ugat', db: 'admin'}, {user: 'elbow', pwd: 'freeze', db: 'bboy'}];

        var loginUser = function(connInfo, connAuth) {
            var db = connInfo.mongo.getDB(connAuth.db);
            db.createUser({user: connAuth.user, pwd: connAuth.pwd, roles: jsTest.adminUserRoles});
            db.auth(connAuth.user, connAuth.pwd);
            connInfo.users[connAuth.db] = connAuth.user;
        };

        var logoutUser = function(connInfo, connAuth) {
            var db = connInfo.mongo.getDB(connAuth.db);
            db.runCommand({logout: 1});
            delete connInfo.users[connAuth.db];
        };

        /**
         * Performs a couple of test to make sure that the format of the log is correct.
         * Also checks that whether the right users show up in the logs.
         *
         * @param log {Array.<string>} list of log lines to check.
         * @param connInfo {Object}
         */
        var checkLogs = function(log, connInfo) {
            var foundOne = false;

            /**
             * @return true if the logInfo contains the same users as connIfo.
             */
            var checkUsers = function(logInfo) {
                for (var db in logInfo.users) {
                    if (logInfo.users.hasOwnProperty(db) &&
                        logInfo.users[db] != connInfo.users[db]) {
                        return false;
                    }
                }

                for (db in connInfo.users) {
                    if (connInfo.users.hasOwnProperty(db) &&
                        logInfo.users[db] != connInfo.users[db]) {
                        return false;
                    }
                }

                return true;
            };

            var hasUser = function(logInfo) {
                for (var db in logInfo.users) {
                    if (logInfo.users.hasOwnProperty(db)) {
                        return true;
                    }
                }

                return false;
            };

            log.forEach(function(line) {
                var logInfo = parseLog(line);

                if (logInfo == null)
                    return;
                if (connInfo.id == null) {
                    if (checkUsers(logInfo)) {
                        connInfo.id = logInfo.id;
                        foundOne = true;
                    }

                    return;
                }

                if (logInfo.id == connInfo.id) {
                    foundOne = true;
                    assert(checkUsers(logInfo),
                           'logged users does not match [' + tojson(connInfo.users) + '], log: ' +
                               line);
                } else if (hasUser(logInfo)) {
                    assert(!checkUsers(logInfo), 'Unexpected user log on another thread: ' + line);
                }
            });

            assert(foundOne, 'User log not found in: ' + tojson(log));
        };

        var testDB1 = connInfo1.mongo.getDB('test');
        var testDB2 = connInfo2.mongo.getDB('test');

        // Note: The succeeding tests should not be re-ordered.
        (function() {
            jsTest.log('Test single user on 1 connection.');
            loginUser(connInfo1, conn1Auth[0]);
            testDB1.runCommand({dbStats: 1});
            var log = testDB1.adminCommand({getLog: 'global'});
            checkLogs(log.log, connInfo1);
        })();

        (function() {
            jsTest.log('Test multiple conn with 1 user each');
            loginUser(connInfo2, conn2Auth[0]);
            testDB2.runCommand({dbStats: 1});
            var log = testDB1.adminCommand({getLog: 'global'});
            checkLogs(log.log, connInfo2);
        })();

        (function() {
            jsTest.log('Test multiple conn with 1 multiple user');
            loginUser(connInfo1, conn1Auth[1]);
            var log = testDB1.adminCommand({getLog: 'global'});
            var lastLogLine = log.log.pop();  // Used for trimming out logs before this point.
            testDB1.runCommand({dbStats: 1});
            log = testDB1.adminCommand({getLog: 'global'});

            // Remove old log entries.
            while (log.log.shift() != lastLogLine) {
            }
            assert(log.log.length > 0);
            checkLogs(log.log, connInfo1);
        })();

        (function() {
            jsTest.log('Test multiple conn with multiple users each');
            loginUser(connInfo2, conn2Auth[1]);
            var log = testDB2.adminCommand({getLog: 'global'});
            var lastLogLine = log.log.pop();  // Used for trimming out logs before this point.
            testDB1.runCommand({dbStats: 1});
            log = testDB2.adminCommand({getLog: 'global'});

            // Remove old log entries.
            while (log.log.shift() != lastLogLine) {
            }
            assert(log.log.length > 0);
            checkLogs(log.log, connInfo2);
        })();

        (function() {
            // Case for logout older user first.
            jsTest.log('Test log line will not show foo');
            logoutUser(connInfo1, conn1Auth[0]);
            var log = testDB1.adminCommand({getLog: 'global'});
            var lastLogLine = log.log.pop();  // Used for trimming out logs before this point.
            testDB1.runCommand({dbStats: 1});
            log = testDB1.adminCommand({getLog: 'global'});

            // Remove old log entries.
            while (log.log.shift() != lastLogLine) {
            }
            assert(log.log.length > 0);
            checkLogs(log.log, connInfo1);
        })();

        (function() {
            jsTest.log('Test that log for conn1 will not show \'user:\'');
            logoutUser(connInfo1, conn1Auth[1]);
            var log = testDB1.adminCommand({getLog: 'global'});
            var lastLogLine = log.log.pop();  // Used for trimming out logs before this point.
            testDB1.runCommand({dbStats: 1});
            log = testDB1.adminCommand({getLog: 'global'});

            // Remove old log entries.
            while (log.log.shift() != lastLogLine) {
            }
            assert(log.log.length > 0);
            checkLogs(log.log, connInfo1);
        })();

        (function() {
            // Case for logout newer user first.
            jsTest.log('Test log line will not show elbow');
            logoutUser(connInfo2, conn2Auth[1]);
            var log = testDB2.adminCommand({getLog: 'global'});
            var lastLogLine = log.log.pop();  // Used for trimming out logs before this point.
            testDB1.runCommand({dbStats: 1});
            log = testDB2.adminCommand({getLog: 'global'});

            // Remove old log entries.
            while (log.log.shift() != lastLogLine) {
            }
            assert(log.log.length > 0);
            checkLogs(log.log, connInfo2);
        })();
    };

    var mongo = MongoRunner.runMongod({verbose: 5, setParameter: 'logUserIds=1'});
    doTest(mongo, new Mongo(mongo.host));
    MongoRunner.stopMongod(mongo.port);

    var st = new ShardingTest(
        {shards: 1, verbose: 5, other: {mongosOptions: {setParameter: 'logUserIds=1'}}});
    doTest(st.s, new Mongo(st.s.host));
    st.stop();
}
