/*
 * Copyright 2013 Apereo Foundation (AF) Licensed under the
 * Educational Community License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may
 * obtain a copy of the License at
 *
 *     http://opensource.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

var _ = require('underscore');
var assert = require('assert');
var AuthzAPI = require('oae-authz');
var AuthzUtil = require('oae-authz/lib/util');

describe('Authz-Groups', function() {

    /**
     * Creates a batch of userIds for the given tenant
     *
     * @param  {String} tenantAlias The tenant for which to generate the users.
     * @param  {Number} num         The number of tenants to generate
     * @return {String[]}           An array of user ids that were generated
     */
    var createUserIds = function(tenantAlias, num) {
        var ids = [];
        for (var i = 0; i < num; i++) {
            ids.push(AuthzUtil.toId('u', tenantAlias, i));
        }
        return ids;
    };

    /**
     * Verifies using assertions that the user does not have any membership in the provided group.
     *
     * @param  {String}      groupId    The id of the group to check
     * @param  {String}      memberId   The id of the principal to check
     * @param  {Function}    callback   Standard callback function
     */
    var verifyNoBidirectionalGroupMembership = function(groupId, memberId, callback) {
        AuthzAPI.hasAnyRole(memberId, groupId, function(err, hasRole) {
            assert.ok(!err);
            assert.ok(!hasRole);

            AuthzAPI.getAuthzMembers(groupId, undefined, undefined, function(err, members) {
                assert.ok(!err);
                assert.ok(members);
                assert.ok(!_.find(members, function(member){ if (member.id === memberId) { return member.role; }}));
                callback();
            });
        });
    };

    /**
     * Verifies using asertions that the user has membership in the provided group both by group memberships and
     * role checks.
     *
     * @param  {String}      groupId    The id of the group to check
     * @param  {String}      memberId   The id of the principal to check
     * @param  {Function}    callback   Standard callback function
     */
    var verifyBidirectionalGroupMembership = function(groupId, memberId, role, callback) {
        AuthzAPI.hasRole(memberId, groupId, role, function(err, hasRole) {
            assert.ok(!err);
            assert.ok(hasRole);

            // also verify from the group membership
            AuthzAPI.getAuthzMembers(groupId, undefined, undefined, function(err, members) {
                assert.ok(!err);
                assert.ok(members);
                assert.ok(_.find(members, function(member) {
                    if (member.id === memberId) {
                        return member.role;
                    }
                }));
                callback();
            });
        });
    };

    /**
     * Make a single membership change object to apply to a group membership.
     *
     * @param  {String} principalId   The principalId whose membership to change
     * @param  {String} role          The role to change to
     * @return {Object}               The change JSON Object to apply to the group
     */
    var makeMembershipChange = function(principalId, role) {
        var change = {};
        change[principalId] = role;
        return change;
    };

    describe('Add group member', function() {
        it('verify invalid group id error', function(callback) {
            AuthzAPI.updateRoles('not a valid id', makeMembershipChange('u:cam:mrvisser', 'member'), function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify non-group group id error', function(callback) {
            AuthzAPI.updateRoles('u:cam:mrvisser', makeMembershipChange('u:cam:mrvisser', 'member'), function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify invalid member id error', function(callback) {
            AuthzAPI.updateRoles('g:oae:oae-team', makeMembershipChange('not a valid id', 'member'), function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify non-principal member id error', function(callback) {
            AuthzAPI.updateRoles('g:oae:oae-team', makeMembershipChange('c:content:id', 'member'), function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify null role error', function(callback) {
            AuthzAPI.updateRoles('g:oae:oae-team', makeMembershipChange('u:cam:mrvisser', null), function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify undefined role error', function(callback) {
            AuthzAPI.updateRoles('g:oae:oae-team', makeMembershipChange('u:cam:mrvisser', undefined), function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify user gets added to group', function(callback) {
            var groupId = 'g:agm-add-user:oae-team';
            var memberId = 'u:agm-add-user:mrvisser';
            AuthzAPI.updateRoles(groupId, makeMembershipChange(memberId, 'member'), function(err) {
                assert.ok(!err);
                verifyBidirectionalGroupMembership(groupId, memberId, 'member', callback);
            });
        });

        it('verify update user membership role', function(callback) {
            var groupId = 'g:agm-update-user:oae-team';
            var memberId = 'u:agm-update-user:mrvisser';
            AuthzAPI.updateRoles(groupId, makeMembershipChange(memberId, 'member'), function(err) {
                assert.ok(!err);
                verifyBidirectionalGroupMembership(groupId, memberId, 'member', function() {
                    AuthzAPI.updateRoles(groupId, makeMembershipChange(memberId, 'manager'), function(err) {
                        assert.ok(!err);
                        verifyBidirectionalGroupMembership(groupId, memberId, 'manager', function() {
                            // also ensure that the number of members in the group is still 1
                            AuthzAPI.getAuthzMembers(groupId, undefined, undefined, function(err, members) {
                                assert.ok(!err);
                                assert.equal(members.length, 1);
                                callback();
                            });
                        });
                    });
                });
            });
        });

        it('verify group gets added to group', function(callback) {
            var groupId = 'g:agm-add-group:oae-team';
            var memberId = 'g:agm-add-group:mrvisser';
            AuthzAPI.updateRoles(groupId, makeMembershipChange(memberId, 'member'), function(err) {
                assert.ok(!err);

                // verify membership exists VIA roles api (bottom-to-top association)
                AuthzAPI.hasRole(memberId, groupId, 'member', function(err, hasRole) {
                    assert.ok(!err);
                    assert.ok(hasRole);

                    // verify membership exists VIA groups api (top-to-bottom association)
                    AuthzAPI.getAuthzMembers(groupId, undefined, undefined, function(err, members) {
                        assert.ok(!err);
                        assert.equal(members.length, 1);
                        callback();
                    });
                });
            });
        });
    });

    describe('Remove group member', function() {

        it('verify invalid group id error', function(callback) {
            AuthzAPI.updateRoles('not a valid id', makeMembershipChange('u:cam:mrvisser', false), function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify non-group group id error', function(callback) {
            AuthzAPI.updateRoles('u:cam:mrvisser', makeMembershipChange('u:cam:mrvisser', false), function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify invalid member id error', function(callback) {
            AuthzAPI.updateRoles('g:oae:oae-team', makeMembershipChange('not a valid id', false), function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify non-principal member id error', function(callback) {
            AuthzAPI.updateRoles('g:oae:oae-team', makeMembershipChange('c:content:id', false), function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify user gets removed from group', function(callback) {
            var groupId = 'g:rgm-remove-user:oae-team';
            var memberId = 'u:rgm-remove-user:mrvisser';

            // 1. add the user
            AuthzAPI.updateRoles(groupId, makeMembershipChange(memberId, 'member'), function(err) {
                assert.ok(!err);

                // 2. sanity check the membership
                verifyBidirectionalGroupMembership(groupId, memberId, 'member', function() {

                    // 3. remove the user
                    AuthzAPI.updateRoles(groupId, makeMembershipChange(memberId, false), function(err) {
                        assert.ok(!err);

                        // verify they are removed
                        verifyNoBidirectionalGroupMembership(groupId, memberId, callback);
                    });
                });
            });
        });

        it('verify group gets removed from group', function(callback) {
            var groupId = 'g:rgm-remove-user:oae-team';
            var memberId = 'g:rgm-remove-user:mrvisser';

            // 1. add the user
            AuthzAPI.updateRoles(groupId, makeMembershipChange(memberId, 'member'), function(err) {
                assert.ok(!err);

                // 2. sanity check user is added
                verifyBidirectionalGroupMembership(groupId, memberId, 'member', function() {

                    // 3. remove the user
                    AuthzAPI.updateRoles(groupId, makeMembershipChange(memberId, false), function(err) {
                        assert.ok(!err);

                        // 4. verify the user is removed
                        verifyNoBidirectionalGroupMembership(groupId, memberId, callback);
                    });
                });
            });
        });
    });

    describe('#updateAuthzGroupMembers()', function() {

        it('verify invalid group id error', function(callback) {
            AuthzAPI.updateRoles('not a valid id', {'u:cam:mrvisser': 'member'}, function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify non-group group id error', function(callback) {
            AuthzAPI.updateRoles('u:cam:mrvisser', {'u:cam:mrvisser': 'member'}, function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify invalid member id error', function(callback) {
            AuthzAPI.updateRoles('g:oae:oae-team', {'not a valid id': 'member'}, function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify non-principal member id error', function(callback) {
            AuthzAPI.updateRoles('g:oae:oae-team', {'c:oae:mrvisser': 'member'}, function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify null role error', function(callback) {
            AuthzAPI.updateRoles('g:oae:oae-team', {'u:cam:mrvisser': null}, function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify undefined role error', function(callback) {
            AuthzAPI.updateRoles('g:oae:oae-team', {'u:cam:mrvisser': undefined}, function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify blank role error', function(callback) {
            AuthzAPI.updateRoles('g:oae:oae-team', {'u:cam:mrvisser': '  '}, function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify general functionality', function(callback) {
            var groupId = 'g:agmc-general:oae-team';
            var mrvisserId = 'u:agmc-general:mrvisser';
            var simongId = 'u:agmc-general:simong';
            var bertId = 'u:agmc-general:physx';
            var nicoId = 'u:agmc-general:nicolaas';

            var changes = {};
            changes[mrvisserId] = 'member';
            changes[simongId] = 'manager';
            changes[bertId] = 'manager';
            changes[nicoId] = 'member';

            AuthzAPI.updateRoles(groupId, changes, function(err) {
                assert.ok(!err);
                verifyBidirectionalGroupMembership(groupId, mrvisserId, 'member', function() {
                    verifyBidirectionalGroupMembership(groupId, simongId, 'manager', function() {
                        verifyBidirectionalGroupMembership(groupId, bertId, 'manager', function() {
                            verifyBidirectionalGroupMembership(groupId, nicoId, 'member', function() {

                                // now inverse the group roles and check
                                changes[mrvisserId] = 'manager';
                                changes[simongId] = 'member';
                                changes[bertId] = 'member';
                                changes[nicoId] = 'manager';

                                AuthzAPI.updateRoles(groupId, changes, function(err) {
                                    assert.ok(!err);

                                    verifyBidirectionalGroupMembership(groupId, mrvisserId, 'manager', function() {
                                        verifyBidirectionalGroupMembership(groupId, simongId, 'member', function() {
                                            verifyBidirectionalGroupMembership(groupId, bertId, 'member', function() {
                                                verifyBidirectionalGroupMembership(groupId, nicoId, 'manager', function() {

                                                    // now remove mrvisser and nico, while setting bert and simon back to manager
                                                    changes[mrvisserId] = false;
                                                    changes[simongId] = 'manager';
                                                    changes[bertId] = 'manager';
                                                    changes[nicoId] = false;

                                                    AuthzAPI.updateRoles(groupId, changes, function(err) {
                                                        assert.ok(!err);
                                                        verifyNoBidirectionalGroupMembership(groupId, mrvisserId, function() {
                                                            verifyNoBidirectionalGroupMembership(groupId, nicoId, function() {
                                                                verifyBidirectionalGroupMembership(groupId, simongId, 'manager', function() {
                                                                    verifyBidirectionalGroupMembership(groupId, bertId, 'manager', callback);
                                                                });
                                                            });
                                                        });
                                                    });
                                                });
                                            });
                                        });
                                    });
                                });
                            });
                        });
                    });
                });
            });
        });
    });

    describe('#getAuthzMembers()', function() {

        it('verify invalid group id error', function(callback) {
            AuthzAPI.getAuthzMembers('not a valid id', undefined, undefined, function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify non-group group id error', function(callback) {
            AuthzAPI.getAuthzMembers('u:cam:mrvisser', undefined, undefined, function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify non-numeric limit error', function(callback) {
            AuthzAPI.getAuthzMembers('g:cam:mrvisser', undefined, 'not a number', function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify get user from group', function(callback) {
            var groupId = 'g:ggm-get:oae-team';
            var memberId = 'u:ggm-get:mrvisser';
            AuthzAPI.updateRoles(groupId, makeMembershipChange(memberId, 'member'), function(err) {
                assert.ok(!err);
                AuthzAPI.getAuthzMembers(groupId, undefined, undefined, function(err, members) {
                    assert.ok(!err);
                    assert.ok(members);
                    assert.equal(members.length, 1);
                    assert.equal(members[0].id, memberId);
                    callback();
                });
            });
        });

        it('verify user paging', function(callback) {
            var groupId = 'g:ggm-paging:oae-team';
            var memberIds = createUserIds('ggm-paging', 11);

            var changes = {};
            for (var i = 0; i < memberIds.length; i++) {
                changes[memberIds[i]] = 'member';
            }

            AuthzAPI.updateRoles(groupId, changes, function(err) {
                assert.ok(!err);
                AuthzAPI.getAuthzMembers(groupId, undefined, 10, function(err, members, nextToken) {
                    assert.ok(!err);
                    assert.equal(members.length, 10);
                    assert.equal(nextToken, members[9].id);

                    AuthzAPI.getAuthzMembers(groupId, members[9].id, 10, function(err, members, nextToken) {
                        assert.ok(!err);
                        assert.equal(members.length, 1);
                        assert.ok(!nextToken);
                        callback();
                    });
                });
            });
        });
    });

    describe('#getPrincipalMemberships()', function() {

        it('verify invalid principal id', function(callback) {
            AuthzAPI.getPrincipalMemberships('not an id', undefined, undefined, function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify non-principal id', function(callback) {
            AuthzAPI.getPrincipalMemberships('c:cam:Foo.docx', undefined, undefined, function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify empty membership is empty array', function(callback) {
            var userId = AuthzUtil.toId('u', 'gmo-empty', 'mrvisser');
            AuthzAPI.getPrincipalMemberships(userId, undefined, undefined, function(err, groupIds) {
                assert.ok(!err);
                assert.ok(groupIds);
                assert.equal(groupIds.length, 0);
                callback();
            });
        });

        it('verify single group membership', function(callback) {
            var groupId = AuthzUtil.toId('g', 'gmo-single', 'oae-team');
            var userId = AuthzUtil.toId('u', 'gmo-single', 'mrvisser');
            AuthzAPI.updateRoles(groupId, makeMembershipChange(userId, 'member'), function(err) {
                assert.ok(!err);

                // Get the memberships uncached
                AuthzAPI.getPrincipalMemberships(userId, undefined, undefined, function(err, groupIds) {
                    assert.ok(!err);
                    assert.ok(groupIds);
                    assert.equal(groupIds.length, 1);
                    assert.equal(groupIds[0], groupId);

                    // Get the memberships cached
                    AuthzAPI.getPrincipalMemberships(userId, undefined, undefined, function(err, groupIds) {
                        assert.ok(!err);
                        assert.ok(groupIds);
                        assert.equal(groupIds.length, 1);
                        assert.equal(groupIds[0], groupId);
                        callback();
                    });
                });
            });
        });

        it('verify two direct group membership', function(callback) {
            var groupId1 = AuthzUtil.toId('g', 'gmo-two', 'oae-team');
            var groupId2 = AuthzUtil.toId('g', 'gmo-two', 'oae-backend-team');
            var userId = AuthzUtil.toId('u', 'gmo-two', 'mrvisser');
            AuthzAPI.updateRoles(groupId1, makeMembershipChange(userId, 'member'), function(err) {
                assert.ok(!err);
                AuthzAPI.updateRoles(groupId2, makeMembershipChange(userId, 'member'), function(err) {
                    assert.ok(!err);

                    // Get the memberships uncached
                    AuthzAPI.getPrincipalMemberships(userId, undefined, undefined, function(err, groupIds) {
                        assert.ok(!err);
                        assert.ok(groupIds);
                        assert.equal(groupIds.length, 2);
                        assert.ok(_.contains(groupIds, groupId1));
                        assert.ok(_.contains(groupIds, groupId2));

                        // Get the memberships cached
                        AuthzAPI.getPrincipalMemberships(userId, undefined, undefined, function(err, groupIds) {
                            assert.ok(!err);
                            assert.ok(groupIds);
                            assert.equal(groupIds.length, 2);
                            assert.ok(_.contains(groupIds, groupId1));
                            assert.ok(_.contains(groupIds, groupId2));
                            callback();
                        });
                    });
                });
            });
        });

        it('verify simple group hierarchy', function(callback) {
            var groupId1 = AuthzUtil.toId('g', 'gmo-hier', 'oae-team');
            var groupId2 = AuthzUtil.toId('g', 'gmo-hier', 'oae-backend-team');
            var nonMemberGroupId3 = AuthzUtil.toId('g', 'gmo-hier', 'non-member');
            var userId = AuthzUtil.toId('u', 'gmo-hier', 'mrvisser');
            AuthzAPI.updateRoles(groupId1, makeMembershipChange(userId, 'member'), function(err) {
                assert.ok(!err);
                AuthzAPI.updateRoles(groupId1, makeMembershipChange(nonMemberGroupId3, 'member'), function(err) {
                    assert.ok(!err);
                    AuthzAPI.updateRoles(groupId2, makeMembershipChange(groupId1, 'member'), function(err) {
                        assert.ok(!err);

                        // Get the memberships uncached
                        AuthzAPI.getPrincipalMemberships(userId, undefined, undefined, function(err, groupIds) {
                            assert.ok(!err);
                            assert.ok(groupIds);
                            assert.equal(groupIds.length, 2);
                            assert.ok(_.contains(groupIds, groupId1));
                            assert.ok(_.contains(groupIds, groupId2));

                            // Get the memberships cached
                            AuthzAPI.getPrincipalMemberships(userId, undefined, undefined, function(err, groupIds) {
                                assert.ok(!err);
                                assert.ok(groupIds);
                                assert.equal(groupIds.length, 2);
                                assert.ok(_.contains(groupIds, groupId1));
                                assert.ok(_.contains(groupIds, groupId2));
                                callback();
                            });
                        });
                    });
                });
            });
        });

        it('verify circular group hierarchy', function(callback) {
            var groupId1 = AuthzUtil.toId('g', 'gmo-circ', 'oae-team');
            var groupId2 = AuthzUtil.toId('g', 'gmo-circ', 'oae-backend-team');
            var groupId3 = AuthzUtil.toId('g', 'gmo-circ', 'oae-ui-team');
            var userId = AuthzUtil.toId('u', 'gmo-circ', 'mrvisser');
            AuthzAPI.updateRoles(groupId1, makeMembershipChange(userId, 'member'), function(err) {
                assert.ok(!err);
                AuthzAPI.updateRoles(groupId2, makeMembershipChange(groupId1, 'member'), function(err) {
                    assert.ok(!err);
                    AuthzAPI.updateRoles(groupId3, makeMembershipChange(groupId2, 'member'), function(err) {
                        assert.ok(!err);
                        AuthzAPI.updateRoles(groupId1, makeMembershipChange(groupId3, 'member'), function(err) {
                            assert.ok(!err);

                            // Get the memberships uncached
                            AuthzAPI.getPrincipalMemberships(userId, undefined, undefined, function(err, groupIds) {
                                assert.ok(!err);
                                assert.ok(groupIds);
                                assert.equal(groupIds.length, 3);
                                assert.ok(_.contains(groupIds, groupId1));
                                assert.ok(_.contains(groupIds, groupId2));
                                assert.ok(_.contains(groupIds, groupId3));

                                // Get the memberships cached
                                AuthzAPI.getPrincipalMemberships(userId, undefined, undefined, function(err, groupIds) {
                                    assert.ok(!err);
                                    assert.ok(groupIds);
                                    assert.equal(groupIds.length, 3);
                                    assert.ok(_.contains(groupIds, groupId1));
                                    assert.ok(_.contains(groupIds, groupId2));
                                    assert.ok(_.contains(groupIds, groupId3));
                                    callback();
                                });
                            });
                        });
                    });
                });
            });
        });

        /**
         * Test that verifies that the list of user memberships is properly paged
         */
        it('verify paging', function(callback) {
            var groupId1 = AuthzUtil.toId('g', 'gmo-pag', 'oae-group1');
            var groupId2 = AuthzUtil.toId('g', 'gmo-pag', 'oae-group2');
            var groupId3 = AuthzUtil.toId('g', 'gmo-pag', 'oae-group3');
            var groupId4 = AuthzUtil.toId('g', 'gmo-pag', 'oae-group4');
            var groupId5 = AuthzUtil.toId('g', 'gmo-pag', 'oae-group5');
            var groupId6 = AuthzUtil.toId('g', 'gmo-pag', 'oae-group6');
            var groupId7 = AuthzUtil.toId('g', 'gmo-pag', 'oae-group7');
            var userId = AuthzUtil.toId('u', 'gmo-pag', 'mrvisser');

            // Make him a member of all groups
            AuthzAPI.updateRoles(groupId1, makeMembershipChange(userId, 'member'), function(err) {
                assert.ok(!err);
                AuthzAPI.updateRoles(groupId2, makeMembershipChange(userId, 'manager'), function(err) {
                    assert.ok(!err);
                    AuthzAPI.updateRoles(groupId3, makeMembershipChange(userId, 'member'), function(err) {
                        assert.ok(!err);
                        AuthzAPI.updateRoles(groupId4, makeMembershipChange(userId, 'member'), function(err) {
                            assert.ok(!err);
                            AuthzAPI.updateRoles(groupId5, makeMembershipChange(userId, 'manager'), function(err) {
                                assert.ok(!err);
                                AuthzAPI.updateRoles(groupId6, makeMembershipChange(userId, 'member'), function(err) {
                                    assert.ok(!err);
                                    AuthzAPI.updateRoles(groupId7, makeMembershipChange(userId, 'manager'), function(err) {
                                        assert.ok(!err);

                                        // Get the paged memberships uncached
                                        AuthzAPI.getPrincipalMemberships(userId, groupId3, 3, function(err, groupIds, nextToken) {
                                            assert.ok(!err);
                                            assert.ok(groupIds);
                                            assert.equal(groupIds.length, 3);
                                            assert.ok(_.contains(groupIds, groupId4));
                                            assert.ok(_.contains(groupIds, groupId5));
                                            assert.ok(_.contains(groupIds, groupId6));
                                            assert.equal(nextToken, groupIds[2]);

                                            // Get the paged memberships cached
                                            AuthzAPI.getPrincipalMemberships(userId, groupId3, 3, function(err, groupIds, nextToken) {
                                                assert.ok(!err);
                                                assert.ok(groupIds);
                                                assert.equal(groupIds.length, 3);
                                                assert.ok(_.contains(groupIds, groupId4));
                                                assert.ok(_.contains(groupIds, groupId5));
                                                assert.ok(_.contains(groupIds, groupId6));
                                                assert.equal(nextToken, groupIds[2]);
                                                callback();
                                            });
                                        });
                                    });
                                });
                            });
                        });
                    });
                });
            });
        });
    });

    describe('Is Authz group member', function() {
        it('verify invalid principal id', function(callback) {
            AuthzAPI.hasAnyRole('not an id', 'g:oae:mrvisser', function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify non-principal id', function(callback) {
            AuthzAPI.hasAnyRole('c:cam:Foo.docx', 'g:oae:mrvisser', function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify invalid group id error', function(callback) {
            AuthzAPI.hasAnyRole('u:oae:mrvisser', 'not an id', function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });

        it('verify non-group group id error', function(callback) {
            AuthzAPI.hasAnyRole('u:oae:mrvisser', 'u:oae:mrvisser', function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                callback();
            });
        });


        it('verify empty membership', function(callback) {
            var userId = AuthzUtil.toId('u', 'imo-empty', 'mrvisser');
            AuthzAPI.hasAnyRole(userId, 'g:oae:oae-team', function(err, hasAnyRole) {
                assert.ok(!err);
                assert.ok(!hasAnyRole);
                callback();
            });
        });

        it('verify single group membership', function(callback) {
            var groupId = AuthzUtil.toId('g', 'imo-single', 'oae-team');
            var userId = AuthzUtil.toId('u', 'imo-single', 'mrvisser');
            AuthzAPI.updateRoles(groupId, makeMembershipChange(userId, 'member'), function(err) {
                assert.ok(!err);
                AuthzAPI.hasAnyRole(userId, groupId, function(err, hasAnyRole) {
                    assert.ok(!err);
                    assert.ok(hasAnyRole);
                    callback();
                });
            });
        });

        it('verify two direct group membership', function(callback) {
            var groupId1 = AuthzUtil.toId('g', 'imo-two', 'oae-team');
            var groupId2 = AuthzUtil.toId('g', 'imo-two', 'oae-backend-team');
            var userId = AuthzUtil.toId('u', 'imo-two', 'mrvisser');
            AuthzAPI.updateRoles(groupId1, makeMembershipChange(userId, 'member'), function(err) {
                assert.ok(!err);
                AuthzAPI.updateRoles(groupId2, makeMembershipChange(userId, 'member'), function(err) {
                    assert.ok(!err);
                    AuthzAPI.hasAnyRole(userId, groupId1, function(err, hasAnyRole) {
                        assert.ok(!err);
                        assert.ok(hasAnyRole);
                        AuthzAPI.hasAnyRole(userId, groupId2, function(err, hasAnyRole) {
                            assert.ok(!err);
                            assert.ok(hasAnyRole);
                            callback();
                        });
                    });
                });
            });
        });

        it('verify simple group hierarchy', function(callback) {
            var groupId1 = AuthzUtil.toId('g', 'gmo-hier', 'oae-team');
            var groupId2 = AuthzUtil.toId('g', 'gmo-hier', 'oae-backend-team');
            var nonMemberGroupId3 = AuthzUtil.toId('g', 'gmo-hier', 'non-member');
            var userId = AuthzUtil.toId('u', 'gmo-hier', 'mrvisser');
            AuthzAPI.updateRoles(groupId1, makeMembershipChange(userId, 'member'), function(err) {
                assert.ok(!err);
                AuthzAPI.updateRoles(groupId1, makeMembershipChange(nonMemberGroupId3, 'member'), function(err) {
                    AuthzAPI.updateRoles(groupId2, makeMembershipChange(groupId1, 'member'), function(err) {
                        assert.ok(!err);
                        AuthzAPI.hasAnyRole(userId, groupId1, function(err, hasAnyRole) {
                            assert.ok(!err);
                            assert.ok(hasAnyRole);
                            AuthzAPI.hasAnyRole(userId, groupId2, function(err, hasAnyRole) {
                                assert.ok(!err);
                                assert.ok(hasAnyRole);
                                AuthzAPI.hasAnyRole(userId, nonMemberGroupId3, function(err, hasAnyRole) {
                                    assert.ok(!err);
                                    assert.ok(!hasAnyRole);
                                    callback();
                                });
                            });
                        });
                    });
                });
            });
        });

        it('verify circular group hierarchy', function(callback) {
            var groupId1 = AuthzUtil.toId('g', 'gmo-circ', 'oae-team');
            var groupId2 = AuthzUtil.toId('g', 'gmo-circ', 'oae-backend-team');
            var groupId3 = AuthzUtil.toId('g', 'gmo-circ', 'oae-ui-team');
            var userId = AuthzUtil.toId('u', 'gmo-circ', 'mrvisser');
            AuthzAPI.updateRoles(groupId1, makeMembershipChange(userId, 'member'), function(err) {
                assert.ok(!err);
                AuthzAPI.updateRoles(groupId2, makeMembershipChange(groupId1, 'member'), function(err) {
                    assert.ok(!err);
                    AuthzAPI.updateRoles(groupId3, makeMembershipChange(groupId2, 'member'), function(err) {
                        assert.ok(!err);
                        AuthzAPI.updateRoles(groupId1, makeMembershipChange(groupId3, 'member'), function(err) {
                            assert.ok(!err);
                            AuthzAPI.hasAnyRole(userId, groupId1, function(err, hasAnyRole) {
                                assert.ok(!err);
                                assert.ok(hasAnyRole);
                                AuthzAPI.hasAnyRole(userId, groupId2, function(err, hasAnyRole) {
                                    assert.ok(!err);
                                    assert.ok(hasAnyRole);
                                    AuthzAPI.hasAnyRole(userId, groupId3, function(err, hasAnyRole) {
                                        assert.ok(!err);
                                        assert.ok(hasAnyRole);
                                        callback();
                                    });
                                });
                            });
                        });
                    });
                });
            });
        });
    });

    describe('#computeMembershipAfterChanges()', function() {
        it('verify functionality', function(callback) {
            var groupId = AuthzUtil.toId('g', 'cmac', 'oae-team');
            var mrvisser = AuthzUtil.toId('u', 'cmac', 'mrvisser');
            var simong = AuthzUtil.toId('u', 'cmac', 'simong');
            var permissions = {};
            permissions[mrvisser] = 'member';
            permissions[simong] = 'member';
            AuthzAPI.updateRoles(groupId, permissions, function(err) {
                assert.ok(!err);

                // Verify adding a principal by adding bert as a member.
                var bert = AuthzUtil.toId('u', 'cmac', 'bert');
                var changes = makeMembershipChange(bert, 'member');
                AuthzAPI.computeMembershipAfterChanges(groupId, changes, function(err, permissions, newMembers, updateMembers, removedMembers) {
                    assert.ok(!err);
                    assert.equal(_.keys(permissions).length, 3);
                    assert.equal(newMembers.length, 1);
                    assert.equal(updateMembers.length, 0);
                    assert.equal(removedMembers.length, 0);

                    // Verify a role change by making mrvisser a manager.
                    changes = makeMembershipChange(mrvisser, 'manager');
                    AuthzAPI.computeMembershipAfterChanges(groupId, changes, function(err, permissions, newMembers, updateMembers, removedMembers) {
                        assert.ok(!err);
                        assert.equal(_.keys(permissions).length, 2);
                        assert.equal(newMembers.length, 0);
                        assert.equal(updateMembers.length, 1);
                        assert.equal(removedMembers.length, 0);

                        // Verify a non-update by making simong a member (he already is a member).
                        changes = makeMembershipChange(simong, 'member');
                        AuthzAPI.computeMembershipAfterChanges(groupId, changes, function(err, permissions, newMembers, updateMembers, removedMembers) {
                            assert.ok(!err);
                            assert.equal(_.keys(permissions).length, 2);
                            assert.equal(newMembers.length, 0);
                            assert.equal(updateMembers.length, 0);
                            assert.equal(removedMembers.length, 0);

                            // Verify removing a principal by removing simong
                            changes = makeMembershipChange(simong, false);
                            AuthzAPI.computeMembershipAfterChanges(groupId, changes, function(err, permissions, newMembers, updateMembers, removedMembers) {
                                assert.ok(!err);
                                assert.equal(_.keys(permissions).length, 1);
                                assert.equal(newMembers.length, 0);
                                assert.equal(updateMembers.length, 0);
                                assert.equal(removedMembers.length, 1);

                                // Trying to remove the membership for a principal that has no permission should result in no change.
                                var unknownUser = AuthzUtil.toId('u', 'cmac', 'unknown');
                                changes = makeMembershipChange(unknownUser, false);
                                AuthzAPI.computeMembershipAfterChanges(groupId, changes, function(err, permissions, newMembers, updateMembers, removedMembers) {
                                    assert.ok(!err);
                                    assert.equal(_.keys(permissions).length, 2);
                                    assert.equal(newMembers.length, 0);
                                    assert.equal(updateMembers.length, 0);
                                    assert.equal(removedMembers.length, 0);
                                    callback();
                                });
                            });
                        });
                    });
                });
            });
        });
    });
});
