/*
 * Copyright 2014 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 util = require('util');

var ConfigTestUtil = require('oae-config/lib/test/util');
var RestAPI = require('oae-rest');
var RestContext = require('oae-rest/lib/model').RestContext;
var ShibbolethAPI = require('oae-authentication/lib/strategies/shibboleth/api');
var TestsUtil = require('oae-tests');

var TenantsAPI = require('oae-tenants');
var TenantsUtil = require('oae-tenants/lib/util');
var TenantsTestUtil = require('oae-tenants/lib/test/util');

describe('Tenants', function() {

    // Rest context that can be used every time we need to make a request as an anonymous user
    var anonymousCamRestContext = null;
    // Rest context that can be used for anonymous requests on the global tenant
    var anonymousGlobalRestContext = null;
    // Rest context that can be used every time we need to make a request as a Cambridge tenant admin
    var camAdminRestContext = null;
    // Rest context that can be used every time we need to use a request as a global admin
    var globalAdminRestContext = null;

    /**
     * Function that will fill up the anonymous and the tenant admin context
     */
    before(function(callback) {
        // Fill up anonymous rest context
        anonymousCamRestContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.cam.host);
        // Fill up the anonymous global rest context
        anonymousGlobalRestContext = TestsUtil.createGlobalRestContext();
        // Fill up Cam tenant admin rest context
        camAdminRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.cam.host);
        // Fill up the global admin rest context
        globalAdminRestContext = TestsUtil.createGlobalAdminRestContext();
        callback();
    });


    describe('Get tenant', function() {

        /**
         * Test that verifies that all tenants can be retrieved
         */
        it('verify get all tenants', function(callback) {
            var tenantAlias = TenantsTestUtil.generateTestTenantAlias();
            var tenantDescription = TestsUtil.generateRandomText();
            var tenantHost = TenantsTestUtil.generateTestTenantHost();

            // Get all tenants, check that there are 2
            RestAPI.Tenants.getTenants(globalAdminRestContext, function(err, tenants) {
                assert.ok(!err);
                assert.ok(tenants);
                assert.ok(tenants['camtest']);
                assert.equal(tenants['camtest'].host, 'cambridge.oae.com');
                assert.ok(tenants['gttest']);
                assert.equal(tenants['gttest'].host, 'gt.oae.com');

                // Create a new tenant
                TenantsTestUtil.createTenantAndWait(globalAdminRestContext, tenantAlias, tenantDescription, tenantHost, function(err) {
                    assert.ok(!err);
                    // Get all tenants, check that there are 3
                    RestAPI.Tenants.getTenants(globalAdminRestContext, function(err, tenants) {
                        assert.ok(tenants);
                        assert.ok(tenants['camtest']);
                        assert.equal(tenants['camtest'].host, 'cambridge.oae.com');
                        assert.ok(tenants['gttest']);
                        assert.equal(tenants['gttest'].host, 'gt.oae.com');
                        assert.equal(tenants['camtest'].host, 'cambridge.oae.com');
                        assert.ok(tenants[tenantAlias]);
                        assert.equal(tenants[tenantAlias].host, tenantHost);
                        // Verify that the global admin tenant is not included
                        assert.ok(!tenants['admin']);

                        callback();
                    });
                });
            });
        });

        /**
         * Test that verifies that the current tenant's information can be retrieved
         */
        it('verify get tenant', function(callback) {
            RestAPI.Tenants.getTenant(anonymousCamRestContext, null, function(err, tenant) {
                assert.ok(!err);
                assert.ok(tenant);
                assert.equal(tenant.alias, 'camtest');
                assert.equal(tenant.host, 'cambridge.oae.com');

                // Verify that the tenant information is available through the global tenant
                RestAPI.Tenants.getTenant(globalAdminRestContext, 'camtest', function(err, tenant) {
                    assert.ok(!err);
                    assert.ok(tenant);
                    assert.equal(tenant.alias, 'camtest');
                    assert.equal(tenant.host, 'cambridge.oae.com');

                    // Get the tenant by host name
                    var tenantByHost = TenantsAPI.getTenantByHost('cambridge.oae.com');
                    assert.ok(tenantByHost);
                    assert.equal(tenantByHost.alias, 'camtest');
                    assert.equal(tenantByHost.host, 'cambridge.oae.com');
                    callback();
                });
            });
        });

        /**
         *  Test that verifies that getting the global tenant succeeds
         */
        it('verify get global tenant', function(callback) {
            RestAPI.Tenants.getTenant(globalAdminRestContext, null, function(err, tenant) {
                assert.ok(!err);
                assert.ok(tenant);
                assert.equal(tenant.isGlobalAdminServer, true);
                assert.equal(tenant.alias, 'admin');

                // Get the global admin tenant by host name
                var globalAdminTenant = TenantsAPI.getTenantByHost('localhost:2000');
                assert.ok(globalAdminTenant);
                assert.equal(globalAdminTenant.isGlobalAdminServer, true);
                assert.equal(globalAdminTenant.alias, 'admin');
                callback();
            });
        });

        /**
         * Test that verifies that getting the tenant information through the global server requires a valid alias
         */
        it('verify get tenant validation', function(callback) {
            RestAPI.Tenants.getTenant(globalAdminRestContext, ' ', function(err, tenant) {
                assert.ok(err);
                assert.equal(err.code, 404);
                callback();
            });
        });

        /**
         * Test that verifies that a tenant can be retrieved by its mapped alias through the internal API.
         */
        it('verify get tenant by alias', function(callback) {
            // Get the Cambridge tenant
            var tenant = TenantsAPI.getTenant('camtest');
            assert.ok(tenant);
            assert.equal(tenant.alias, 'camtest');
            assert.equal(tenant.displayName, 'Cambridge University Test');
            assert.equal(tenant.host, 'cambridge.oae.com');

            // Get the GT tenant
            tenant = TenantsAPI.getTenant('gttest');
            assert.ok(tenant);
            assert.equal(tenant.alias, 'gttest');
            assert.equal(tenant.displayName, 'Georgia Tech Test');
            assert.equal(tenant.host, 'gt.oae.com');

            // Get the global admin tenant
            tenant = TenantsAPI.getTenant('admin');
            assert.ok(tenant);
            assert.equal(tenant.alias, 'admin');
            assert.equal(tenant.displayName, 'Global admin server');
            assert.equal(tenant.host, 'localhost:2000');
            assert.equal(tenant.isGlobalAdminServer, true);

            // Get non-existing tenant
            tenant = TenantsAPI.getTenant('non-existing');
            assert.ok(!tenant);
            callback();
        });

        /**
         * Test that verifies that a tenant can be retrieved by its mapped host name. This uses the internal
         * API as there is no REST feed available that offers this functionality.
         */
        it('verify get tenant by host', function(callback) {
            // Get the Cambridge tenant
            var tenant = TenantsAPI.getTenantByHost('cambridge.oae.com');
            assert.ok(tenant);
            assert.equal(tenant.alias, 'camtest');
            assert.equal(tenant.displayName, 'Cambridge University Test');
            assert.equal(tenant.host, 'cambridge.oae.com');

            // Get the GT tenant
            tenant = TenantsAPI.getTenantByHost('gt.oae.com');
            assert.ok(tenant);
            assert.equal(tenant.alias, 'gttest');
            assert.equal(tenant.displayName, 'Georgia Tech Test');
            assert.equal(tenant.host, 'gt.oae.com');

            // Get the global admin tenant
            tenant = TenantsAPI.getTenantByHost('localhost:2000');
            assert.ok(tenant);
            assert.equal(tenant.alias, 'admin');
            assert.equal(tenant.displayName, 'Global admin server');
            assert.equal(tenant.host, 'localhost:2000');
            assert.equal(tenant.isGlobalAdminServer, true);

            // Get non-existing tenant
            tenant = TenantsAPI.getTenantByHost('nonexisting.oae.com');
            assert.ok(!tenant);
            callback();
        });

        /**
         * Test that ensures a 418 HTTP response code when accessing the me feed from a non-existing tenant
         */
        it('verify accessing an endpoint from a non-existing tenant results in a 418 HTTP response code', function(callback) {
            // Get the me feed on an existing tenant
            RestAPI.User.getMe(anonymousCamRestContext, function(err, meObj) {
                assert.ok(!err);
                assert.equal(meObj.anon, true);

                // Get the me feed on a non-existing tenant
                var anonymousNonExistingRestContext = TestsUtil.createTenantRestContext('harvard.oae.com');
                RestAPI.User.getMe(anonymousNonExistingRestContext, function(err, meObj) {
                    assert.ok(err);
                    assert.equal(err.code, 418);
                    callback();
                });
            });
        });
    });


    describe('Tenant actions', function() {

        /**
         * Test that verifies that a tenant can not be created by an anonymous user
         */
        it('verify create tenant as anonymous user fails', function(callback) {
            var tenantAlias = TenantsTestUtil.generateTestTenantAlias();
            var tenantDescription = TestsUtil.generateRandomText();
            var tenantHost = TenantsTestUtil.generateTestTenantHost();

            // Try to create a tenant as an anonymous user
            TenantsTestUtil.createTenantAndWait(anonymousGlobalRestContext, tenantAlias, tenantDescription, tenantHost, function(err) {
                assert.ok(err);
                assert.equal(err.code, 401);
                callback();
            });
        });

        /**
         * Test that verifies that it is possible to create a new tenant
         */
        it('verify create tenant', function(callback) {
            var tenantAlias = TenantsTestUtil.generateTestTenantAlias();
            var tenantDescription = TestsUtil.generateRandomText();
            var tenantHost = TenantsTestUtil.generateTestTenantHost();

            TenantsTestUtil.createTenantAndWait(globalAdminRestContext, tenantAlias, tenantDescription, tenantHost, function(err, tenant) {
                assert.ok(!err);
                assert.ok(tenant);
                assert.equal(tenant.alias, tenantAlias);
                assert.equal(tenant.host, tenantHost);

                // Get the tenant
                var restContext = TestsUtil.createTenantRestContext(tenantHost);
                RestAPI.Tenants.getTenant(restContext, null, function(err, tenant) {
                    assert.ok(!err);
                    assert.ok(tenant);
                    assert.equal(tenant.alias, tenantAlias);
                    assert.equal(tenant.host, tenantHost);

                    // Get the tenant by host
                    tenant = TenantsAPI.getTenantByHost(tenantHost);
                    assert.ok(tenant);
                    assert.equal(tenant.alias, tenantAlias);
                    assert.equal(tenant.host, tenantHost);
                    callback();
                });
            });
        });

        /**
         * Test that verifies that creating a tenant needs an alias, a displayName and a host specified
         */
        it('verify create tenant validation', function(callback) {
            // Try creating a tenant with no alias
            TenantsTestUtil.createTenantAndWait(globalAdminRestContext, null, 'AAR', 'aar.oae.com', function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);

                // Try creating a tenant with an invalid alias, using spaces in the alias
                TenantsTestUtil.createTenantAndWait(globalAdminRestContext, 'American Academic of Religion', 'AAR', 'aar.oae.com', function(err) {
                    assert.ok(err);
                    assert.equal(err.code, 400);

                    // Try creating a tenant with an invalid alias, using a colon in the alias
                    TenantsTestUtil.createTenantAndWait(globalAdminRestContext, 'aar:test', 'AAR', 'aar.oae.com', function(err) {
                        assert.ok(err);
                        assert.equal(err.code, 400);

                        // Try creating a tenant with an alias that's already taken
                        TenantsTestUtil.createTenantAndWait(globalAdminRestContext, 'camtest', 'Cambridge University', 'cam.oae.com', function(err) {
                            assert.ok(err);
                            assert.equal(err.code, 400);

                            // Try creating a tenant with no displayName
                            TenantsTestUtil.createTenantAndWait(globalAdminRestContext, 'aartest', null, 'aar.oae.com', function(err) {
                                assert.ok(err);
                                assert.equal(err.code, 400);

                                // Try creating a tenant with no base URL
                                TenantsTestUtil.createTenantAndWait(globalAdminRestContext, 'aartest', 'AAR', null, function(err) {
                                    assert.ok(err);
                                    assert.equal(err.code, 400);

                                    // Try creating a tenant with a host name that's already taken
                                    TenantsTestUtil.createTenantAndWait(globalAdminRestContext, 'somethingnottaken', 'Cambridge University', 'cambridge.oae.com', function(err) {
                                        assert.ok(err);
                                        assert.equal(err.code, 400);

                                        // Verify that the tenant does not exist
                                        var aarRestContext = TestsUtil.createTenantRestContext('aar.oae.com');

                                        RestAPI.Tenants.getTenant(aarRestContext, null, function(err, tenant) {
                                            assert.ok(err);
                                            assert.equal(err.code, 418);
                                            assert.ok(!tenant);
                                            callback();
                                        });
                                    });
                                });
                            });
                        });
                    });
                });
            });
        });

        /**
         * Test that verifies that an uppercase host name for a tenant is lowercased and a tenant can be retrieved using an uppercase host name
         */
        it('verify create tenant uppercase host', function(callback) {
            var tenantAlias = TenantsTestUtil.generateTestTenantAlias();
            var tenantDescription = TestsUtil.generateRandomText();
            var tenantHost = TestsUtil.generateRandomText().toUpperCase();

            TenantsTestUtil.createTenantAndWait(globalAdminRestContext, tenantAlias, tenantDescription, tenantHost, function(err) {
                assert.ok(!err);

                // Verify that the existing tenant is still running
                var uppercaseRestContext = TestsUtil.createTenantRestContext(tenantHost);
                RestAPI.Tenants.getTenant(uppercaseRestContext, null, function(err, tenant) {
                    assert.ok(!err);
                    assert.ok(tenant);
                    assert.equal(tenant.alias, tenantAlias);
                    assert.equal(tenant.host, tenantHost.toLowerCase());
                    return callback();
                });
            });
        });

        /**
         * Test that verifies that an uppercase alias for a tenant is lowercased
         */
        it('verify create tenant uppercase alias', function(callback) {
            var tenantAlias = TestsUtil.generateRandomText().toUpperCase();
            var tenantDescription = TestsUtil.generateRandomText();
            var tenantHost = TenantsTestUtil.generateTestTenantHost();

            TenantsTestUtil.createTenantAndWait(globalAdminRestContext, tenantAlias, tenantDescription, tenantHost, function(err) {
                assert.ok(!err);

                var uppercaseRestContext = TestsUtil.createTenantRestContext(tenantHost);
                RestAPI.Tenants.getTenant(uppercaseRestContext, null, function(err, tenant) {
                    assert.ok(!err);
                    assert.ok(tenant);
                    assert.equal(tenant.alias, tenantAlias.toLowerCase());
                    assert.equal(tenant.host, tenantHost);
                    callback();
                });
            });
        });

        /**
         * Test that verifies that a tenant cannot be created with a duplicate alias
         */
        it('verify create tenant duplicate alias', function(callback) {
            TenantsTestUtil.createTenantAndWait(globalAdminRestContext, 'camtest', 'AAR', 'camtest.oae.com', function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);

                // Verify that the existing tenant is still running
                RestAPI.Tenants.getTenant(anonymousCamRestContext, null, function(err, tenant) {
                    assert.ok(!err);
                    assert.ok(tenant);
                    assert.equal(tenant.alias, 'camtest');
                    assert.equal(tenant.host, 'cambridge.oae.com');
                    callback();
                });
            });
        });

        /**
         * Test that verifies that a tenant cannot be created with a duplicate host
         */
        it('verify create tenant duplicate host', function(callback) {
            TenantsTestUtil.createTenantAndWait(globalAdminRestContext, 'angliaruskin', 'Anglia Ruskin University', 'cambridge.oae.com', function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);

                // Verify that the tenant with that hostname is still running
                RestAPI.Tenants.getTenant(anonymousCamRestContext, null, function(err, tenant) {
                    assert.ok(!err);
                    assert.ok(tenant);
                    assert.equal(tenant.alias, 'camtest');
                    assert.equal(tenant.host, 'cambridge.oae.com');
                    callback();
                });
            });
        });

        /**
         * Test that verifies that creating a tenant with the same hostname as the configured Shibboleth SP host is not allowed
         */
        it('verify creating a tenant with the Shibboleth SP host as hostname is not allowed', function(callback) {
            var spHost = ShibbolethAPI.getSPHost();
            TenantsTestUtil.createTenantAndWait(globalAdminRestContext, Math.random(), 'bladiebla', spHost, function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                TenantsTestUtil.createTenantAndWait(globalAdminRestContext, Math.random(), 'bladiebla', spHost.toUpperCase(), function(err) {
                    assert.ok(err);
                    assert.equal(err.code, 400);
                    return callback();
                });
            });
        });

        /**
         * Test that verifies that creating a tenant with an alias that contains a dash does not
         * break the authentication strategy data of the `me` feed. This is a regression test for
         * https://github.com/oaeproject/Hilary/issues/1172
         */
        it('verify creating a tenant with a dash in the alias does not break authentication strategy', function(callback) {
            var tenantHost = TestsUtil.generateRandomText(1);
            var tenantAlias = 'test-with-dash';
            TestsUtil.createTenantWithAdmin('alias-with-dash', tenantHost, function(err, tenant, tenantAdminRestContext) {
                assert.ok(!err);
                TestsUtil.generateTestUsers(tenantAdminRestContext, 1, function(err, users, mrvisser) {

                    // Ensure the tenant admin's me object properly represents the authentication
                    // strategy
                    RestAPI.User.getMe(tenantAdminRestContext, function(err, me) {
                        assert.ok(!err);
                        assert.strictEqual(me.isTenantAdmin, true);
                        assert.strictEqual(me.authenticationStrategy, 'local');

                        // Ensure the regular user's me object properly represents the
                        // authentication strategy
                        RestAPI.User.getMe(mrvisser.restContext, function(err, me) {
                            assert.ok(!err);
                            assert.ok(!me.anon);
                            assert.strictEqual(me.authenticationStrategy, 'local');
                            return callback();
                        });
                    });
                });
            });
        });

        /**
         * Test that verifies that a tenant can be stopped
         */
        it('verify stop tenant', function(callback) {
            // Create a new tenant
            TenantsTestUtil.generateTestTenants(globalAdminRestContext, 1, function(testTenant) {
                var restContext = TestsUtil.createTenantRestContext(testTenant.host);

                // Verify that the tenant is running
                RestAPI.Tenants.getTenant(restContext, null, function(err, tenant) {
                    assert.ok(!err);
                    assert.ok(tenant);
                    assert.equal(tenant.alias, testTenant.alias);

                    // Verify it's in the list of running tenant aliases
                    assert.ok(TenantsAPI.getTenants(true)[testTenant.alias]);

                    // Stop the tenant
                    TenantsTestUtil.stopTenantAndWait(globalAdminRestContext, testTenant.alias, function(err) {
                        assert.ok(!err);

                        // Verify that the tenant is no longer running
                        RestAPI.Tenants.getTenant(restContext, null, function(err, tenant) {
                            assert.ok(err);
                            assert.equal(err.code, 503);
                            assert.ok(!tenant);

                            // Verify it is no longer in the list of running tenant aliases
                            assert.ok(!TenantsAPI.getTenants(true)[testTenant.alias]);
                            // Verify it is in the list of disabled tenant aliases
                            assert.ok(TenantsAPI.getTenants()[testTenant.alias]);
                            assert.equal(TenantsAPI.getTenants()[testTenant.alias].active, false);

                            // Verify that it's still part of the all tenants feed
                            RestAPI.Tenants.getTenants(globalAdminRestContext, function(err, tenants) {
                                assert.ok(!err);
                                assert.ok(tenants);
                                assert.ok(tenants[testTenant.alias]);
                                assert.equal(tenants[testTenant.alias].host, testTenant.host);
                                assert.equal(tenants[testTenant.alias].active, false);
                                callback();
                            });
                        });
                    });
                });
            });
        });

        /**
         * Test that verifies that a tenant can not be stopped by an anonymous user
         */
        it('verify stop tenant as anonymous user fails', function(callback) {
            // Create a tenant to try and stop
            TenantsTestUtil.generateTestTenants(globalAdminRestContext, 1, function(testTenant) {

                // Try to stop the tenant as an anonymous user
                RestAPI.Tenants.stopTenant(anonymousGlobalRestContext, testTenant.alias, function(err) {
                    assert.ok(err);
                    assert.equal(err.code, 401);

                    // Sanity check that global admin can stop the tenant
                    TenantsTestUtil.stopTenantAndWait(globalAdminRestContext, testTenant.alias, function(err) {
                        assert.ok(!err);
                        return callback();
                    });
                });
            });
        });

        /**
         * Test that verifes that a non-existing tenant cannot be stopped
         */
        it('verify stop non-existing tenant', function(callback) {
            // Stop tenant with no alias
            RestAPI.Tenants.stopTenant(globalAdminRestContext, null, function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);

                // Stop tenant with non-existing alias
                RestAPI.Tenants.stopTenant(globalAdminRestContext, TestsUtil.generateRandomText(), function(err) {
                    assert.ok(err);
                    assert.equal(err.code, 404);

                    return callback();
                });
            });
        });

        /**
         * Test that verifies that a stopped tenant can be started
         */
        it('verify start tenant', function(callback) {
            // Generate a tenant to stop and start
            TenantsTestUtil.generateTestTenants(globalAdminRestContext, 1, function(testTenant) {

                // Stop the tenant
                TenantsTestUtil.stopTenantAndWait(globalAdminRestContext, testTenant.alias, function(err) {
                    assert.ok(!err);

                    // Verify that the tenant has indeed stopped
                    var restContext = TestsUtil.createTenantRestContext(testTenant.host);
                    RestAPI.Tenants.getTenant(restContext, null, function(err, tenant) {
                        assert.ok(err);
                        assert.equal(err.code, 503);

                        // Now start the tenant
                        TenantsTestUtil.startTenantAndWait(globalAdminRestContext, testTenant.alias, function(err) {
                            assert.ok(!err);

                            // Verify that the tenant has indeed been started
                            RestAPI.Tenants.getTenant(restContext, null, function(err, tenant) {
                                assert.ok(!err);
                                assert.equal(tenant.alias, testTenant.alias);
                                assert.strictEqual(tenant.active, true);
                                return callback();
                            });
                        });
                    });
                });
            });
        });

        /**
         * Test that verifies that a tenant can not be started by an anonymous user
         */
        it('verify start tenant as anonymous user fails', function(callback) {
            TenantsTestUtil.generateTestTenants(globalAdminRestContext, 1, function(testTenant) {
                // Stop the tenant
                TenantsTestUtil.stopTenantAndWait(globalAdminRestContext, testTenant.alias, function(err) {
                    assert.ok(!err);

                    // Try to start the tenant as an anonymous user
                    RestAPI.Tenants.startTenant(anonymousGlobalRestContext, testTenant.alias, function(err) {
                        assert.ok(err);
                        assert.equal(err.code, 401);

                        // Ensure the tenant is still stopped
                        RestAPI.Tenants.getTenant(globalAdminRestContext, testTenant.alias, function(err, tenant) {
                            assert.ok(!err);
                            assert.equal(tenant.alias, testTenant.alias);
                            assert.strictEqual(tenant.active, false);

                            // Sanity check that global admin can start the tenant
                            TenantsTestUtil.startTenantAndWait(globalAdminRestContext, testTenant.alias, function(err) {
                                assert.ok(!err);
                                return callback();
                            });
                        });
                    });
                });
            });
        });

        /**
         * Test that verifes that a non-existing tenant cannot be started
         */
        it('verify start non-existing tenant', function(callback) {
            // Start tenant with no alias
            RestAPI.Tenants.startTenant(globalAdminRestContext, null, function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);

                // Start tenant with non-existing alias
                RestAPI.Tenants.startTenant(globalAdminRestContext, TestsUtil.generateRandomText(), function(err) {
                    assert.ok(err);
                    assert.equal(err.code, 404);
                    return callback();
                });
            });
        });
    });

    describe('Update tenant', function() {

        /**
         * Test that verifies that a tenant's displayName can not be updated by an anonymous user or non-admin user
         */
        it('verify update tenant as non-admin user', function(callback) {
            // Try to update the tenant's display name as an anonymous user on the global admin tenant
            RestAPI.Tenants.updateTenant(anonymousGlobalRestContext, 'camtest', {'displayName': 'Anglia Ruskin University'}, function(err) {
                assert.ok(err);
                assert.equal(err.code, 401);

                // Try to update the tenant's host as an anonymous user on the global admin tenant
                RestAPI.Tenants.updateTenant(anonymousGlobalRestContext, 'camtest', {'host': 'newcamtest.oae.com'}, function(err) {
                    assert.ok(err);
                    assert.equal(err.code, 401);

                    // Try to update tenant's display name and host as an anonymous user on the global admin tenant
                    RestAPI.Tenants.updateTenant(anonymousGlobalRestContext, 'camtest', {'displayName': 'Anglia Ruskin University', 'host': 'newcamtest.oae.com'}, function(err) {
                        assert.ok(err);
                        assert.equal(err.code, 401);

                        // Try to update the tenant's display name as an anonymous user on a user tenant
                        RestAPI.Tenants.updateTenant(anonymousCamRestContext, null, {'displayName': 'Anglia Ruskin University'}, function(err) {
                            assert.ok(err);
                            assert.equal(err.code, 401);

                            // Try to update the tenant's host as an anonymous user on a user tenant
                            RestAPI.Tenants.updateTenant(anonymousCamRestContext, null, {'host': 'newcamtest.oae.com'}, function(err) {
                                assert.ok(err);
                                assert.equal(err.code, 401);

                                // Try to update tenant's display name and host as an anonymous user on a user tenant
                                RestAPI.Tenants.updateTenant(anonymousCamRestContext, null, {'displayName': 'Anglia Ruskin University', 'host': 'newcamtest.oae.com'}, function(err) {
                                    assert.ok(err);
                                    assert.equal(err.code, 401);

                                    // Create a regular non-admin user
                                    var userId = TestsUtil.generateTestUserId('john');
                                    RestAPI.User.createUser(camAdminRestContext, userId, 'password', 'John Doe', null, function(err, createdUser) {
                                        var johnCamRestContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.cam.host, userId, 'password');

                                        // Try to update the tenant's display name as a non-admin user on a user tenant
                                        RestAPI.Tenants.updateTenant(johnCamRestContext, null, {'displayName': 'Anglia Ruskin University'}, function(err) {
                                            assert.ok(err);
                                            assert.equal(err.code, 401);

                                            // Try to update the tenant's host as a non-admin user on a user tenant
                                            RestAPI.Tenants.updateTenant(johnCamRestContext, null, {'host': 'newcamtest.oae.com'}, function(err) {
                                                assert.ok(err);
                                                assert.equal(err.code, 401);

                                                // Try to update tenant's display name and host as a non-admin user on a user tenant
                                                RestAPI.Tenants.updateTenant(johnCamRestContext, null, {'displayName': 'Anglia Ruskin University', 'host': 'newcamtest.oae.com'}, function(err) {
                                                    assert.ok(err);
                                                    assert.equal(err.code, 401);
                                                    callback();
                                                });
                                            });
                                        });
                                    });
                                });
                            });
                        });
                    });
                });
            });
        });

        /**
         * Test that verifies that a tenant can only be updated when at least 1 correct update value has been supplied
         */
        it('verify update tenant validation', function(callback) {
            // Verify through the global admin tenant
            RestAPI.Tenants.updateTenant(globalAdminRestContext, 'camtest', null, function(err) {
                assert.ok(err);
                assert.equal(err.code, 400);
                // Verify update with an invalid property
                RestAPI.Tenants.updateTenant(globalAdminRestContext, 'camtest', {'alias': 'foobar'}, function(err) {
                    assert.ok(err);
                    assert.equal(err.code, 400);
                    // Verify update with a valid and an invalid property
                    RestAPI.Tenants.updateTenant(globalAdminRestContext, 'camtest', {'alias': 'foobar', 'displayName': 'Anglia Ruskin University'}, function(err) {
                        assert.ok(err);
                        assert.equal(err.code, 400);

                        // Verify through a user tenant
                        RestAPI.Tenants.updateTenant(camAdminRestContext, null, null, function(err) {
                            assert.ok(err);
                            assert.equal(err.code, 400);
                            // Verify update with an invalid property
                            RestAPI.Tenants.updateTenant(camAdminRestContext, null, {'alias': 'foobar'}, function(err) {
                                assert.ok(err);
                                assert.equal(err.code, 400);
                                // Verify update with a valid and an invalid property
                                RestAPI.Tenants.updateTenant(camAdminRestContext, null, {'alias': 'foobar', 'displayName': 'Anglia Ruskin University'}, function(err) {
                                    assert.ok(err);
                                    assert.equal(err.code, 400);

                                    // Verify updating to host that's already used
                                    RestAPI.Tenants.updateTenant(camAdminRestContext, null, {'host': 'caMBriDGe.oae.com'}, function(err) {
                                        assert.ok(err);
                                        assert.equal(err.code, 400);

                                        // Verify updating a non-existing tenant fails
                                        RestAPI.Tenants.updateTenant(globalAdminRestContext, TestsUtil.generateRandomText(), {'displayName': 'I\'m totally legit...'}, function(err) {
                                            assert.ok(err);
                                            assert.equal(err.code, 404);

                                            return callback();
                                        });
                                    });
                                });
                            });
                        });
                    });
                });
            });
        });

        /**
         * Test that verifies that a tenant displayName can be updated
         */
        it('verify update tenant', function(callback) {
            var tenant1Alias = TenantsTestUtil.generateTestTenantAlias();
            var tenant1Host = TenantsTestUtil.generateTestTenantHost();
            var tenant2Host = TenantsTestUtil.generateTestTenantHost();
            var tenant3Host = TenantsTestUtil.generateTestTenantHost();
            var tenant4Host = TenantsTestUtil.generateTestTenantHost();
            var tenant4Description = TestsUtil.generateRandomText();

            // Update the tenant display name as the global admin
            TenantsTestUtil.updateTenantAndWait(globalAdminRestContext, 'camtest', {'displayName': 'Anglia Ruskin University'}, function(err) {
                assert.ok(!err);

                // Check if the update was successful
                RestAPI.Tenants.getTenant(camAdminRestContext, null, function(err, tenant) {
                    assert.ok(!err);
                    assert.ok(tenant);
                    assert.equal(tenant.alias, 'camtest');
                    assert.equal(tenant.host, 'cambridge.oae.com');
                    assert.equal(tenant.displayName, 'Anglia Ruskin University');

                    // Update the tenant display name as the tenant admin
                    TenantsTestUtil.updateTenantAndWait(camAdminRestContext, null, {'displayName': 'Queens College'}, function(err) {
                        assert.ok(!err);

                        // Check if the update was successful
                        RestAPI.Tenants.getTenant(camAdminRestContext, null, function(err, tenant) {
                            assert.ok(!err);
                            assert.ok(tenant);
                            assert.equal(tenant.alias, 'camtest');
                            assert.equal(tenant.host, 'cambridge.oae.com');
                            assert.equal(tenant.displayName, 'Queens College');

                            // Update the tenant host as the global admin
                            TenantsTestUtil.updateTenantAndWait(globalAdminRestContext, 'camtest', {'host': tenant1Host}, function(err) {
                                assert.ok(!err);

                                // Check if the update was successful.
                                // The old host name should no longer be accepting requests
                                RestAPI.Tenants.getTenant(camAdminRestContext, null, function(err, tenant) {
                                    assert.ok(err);
                                    assert.equal(err.code, 418);
                                    // The new host name should now be responding to requests
                                    var tenant1AdminRestContext = TestsUtil.createTenantAdminRestContext(tenant1Host);
                                    RestAPI.Tenants.getTenant(tenant1AdminRestContext, null, function(err, tenant) {
                                        assert.ok(!err);
                                        assert.ok(tenant);
                                        assert.equal(tenant.alias, 'camtest');
                                        assert.equal(tenant.host, tenant1Host);
                                        assert.equal(tenant.displayName, 'Queens College');

                                        // Update the tenant host to have uppercase characters
                                        TenantsTestUtil.updateTenantAndWait(globalAdminRestContext, 'camtest', {'host': tenant2Host.toUpperCase()}, function(err) {
                                            assert.ok(!err);

                                            // Check if the update was successful
                                            // The host name should come back changed but lowercased
                                            var tenant2UpperCaseAdminRestContext = TestsUtil.createTenantAdminRestContext(tenant2Host.toUpperCase());
                                            RestAPI.Tenants.getTenant(tenant2UpperCaseAdminRestContext, null, function(err, tenant) {
                                                assert.ok(!err);
                                                assert.ok(tenant);
                                                assert.equal(tenant.alias, 'camtest');
                                                assert.equal(tenant.host, tenant2Host);
                                                assert.equal(tenant.displayName, 'Queens College');

                                                // Update the tenant host as the tenant admin
                                                TenantsTestUtil.updateTenantAndWait(tenant2UpperCaseAdminRestContext, null, {'host': tenant3Host}, function(err) {
                                                    assert.ok(!err);

                                                    // Check if the update was successful.
                                                    // The old host name should no longer be accepting requests
                                                    RestAPI.Tenants.getTenant(tenant2UpperCaseAdminRestContext, null, function(err, tenant) {
                                                        assert.ok(err);
                                                        assert.equal(err.code, 418);
                                                        // The new host name should now be responding to requests
                                                        var tenant3AdminRestContext = TestsUtil.createTenantAdminRestContext(tenant3Host);
                                                        RestAPI.Tenants.getTenant(tenant3AdminRestContext, null, function(err, tenant) {
                                                            assert.ok(!err);
                                                            assert.ok(tenant);
                                                            assert.equal(tenant.alias, 'camtest');
                                                            assert.equal(tenant.host, tenant3Host);
                                                            assert.equal(tenant.displayName, 'Queens College');

                                                            // Update the tenant display name and host as the tenant admin
                                                            TenantsTestUtil.updateTenantAndWait(tenant3AdminRestContext, null, {'displayName': tenant4Description, 'host': tenant4Host}, function(err) {
                                                                assert.ok(!err);

                                                                // Check if the update was successful.
                                                                // The old host name should no longer be accepting requests
                                                                RestAPI.Tenants.getTenant(tenant3AdminRestContext, null, function(err, tenant) {
                                                                    assert.ok(err);
                                                                    assert.equal(err.code, 418);
                                                                    // The new host name should now be responding to requests
                                                                    var tenant4AdminRestContext = TestsUtil.createTenantAdminRestContext(tenant4Host);
                                                                    RestAPI.Tenants.getTenant(tenant4AdminRestContext, null, function(err, tenant) {
                                                                        assert.ok(!err);
                                                                        assert.ok(tenant);
                                                                        assert.equal(tenant.alias, 'camtest');
                                                                        assert.equal(tenant.host, tenant4Host);
                                                                        assert.equal(tenant.displayName, tenant4Description);

                                                                        // Update the tenant display name and host as the tenant admin
                                                                        TenantsTestUtil.updateTenantAndWait(tenant4AdminRestContext, null, {'displayName': 'Cambridge University Test', 'host': 'cambridge.oae.com'}, function(err) {
                                                                            assert.ok(!err);

                                                                            // Check if the update was successful
                                                                            // The old host name should no longer be accepting requests
                                                                            RestAPI.Tenants.getTenant(tenant4AdminRestContext, null, function(err, tenant) {
                                                                                assert.ok(err);
                                                                                assert.equal(err.code, 418);
                                                                                // The new host name should now be responding to requests
                                                                                RestAPI.Tenants.getTenant(camAdminRestContext, null, function(err, tenant) {
                                                                                    assert.ok(!err);
                                                                                    assert.ok(tenant);
                                                                                    assert.equal(tenant.alias, 'camtest');
                                                                                    assert.equal(tenant.host, 'cambridge.oae.com');
                                                                                    assert.equal(tenant.displayName, 'Cambridge University Test');
                                                                                    return callback();
                                                                                });
                                                                            });
                                                                        });
                                                                    });
                                                                });
                                                            });
                                                        });
                                                    });
                                                });
                                            });
                                        });
                                    });
                                });
                            });
                        });
                    });
                });
            });
        });

        /**
         * Test that verifies that updating a tenant's hostname to the Shibboleth SP host is not allowed
         */
        it('verify updating a tenant\'s hostname to the Shibboleth SP host is not allowed', function(callback) {
            // Create a tenant
            var tenantAlias = TenantsTestUtil.generateTestTenantAlias();
            var tenantDescription = TestsUtil.generateRandomText();
            var tenantHost = TenantsTestUtil.generateTestTenantHost();
            TenantsTestUtil.createTenantAndWait(globalAdminRestContext, tenantAlias, tenantDescription, tenantHost, function(err) {
                assert.ok(!err);

                // Updating the hostname to the SP hostname should fail
                var spHost = ShibbolethAPI.getSPHost();
                RestAPI.Tenants.updateTenant(globalAdminRestContext, tenantAlias, {'host': spHost}, function(err) {
                    assert.ok(err);
                    assert.equal(err.code, 400);

                    // Updating the hostname to any case of the SP hostname should fail
                    RestAPI.Tenants.updateTenant(globalAdminRestContext, tenantAlias, {'host': spHost.toUpperCase()}, function(err) {
                        assert.ok(err);
                        assert.equal(err.code, 400);
                        return callback();
                    });
                });
            });
        });
    });

    describe('Tenant domains', function() {

        /**
         * Test that verifies bad input
         */
        it('verify bad input', function(callback) {
            var tenant = TenantsUtil.getTenantForEmailDomain(null);
            assert.ok(!tenant);

            tenant = TenantsUtil.getTenantForEmailDomain('');
            assert.ok(!tenant);
            return callback();
        });

        /**
         * Test that verifies that tenants can be resolved by their configured email domain
         */
        it('verify tenants can be retrieved by a configured email domain', function(callback) {
            TestsUtil.setupMultiTenantPrivacyEntities(function(tenant1, tenant2, tenant3) {

                // Configure the first tenant
                var configUpdate1 = {'oae-tenants/domains/email': '^.*cam.ac.uk$'};
                ConfigTestUtil.updateConfigAndWait(globalAdminRestContext, tenant1.tenant.alias, configUpdate1, function() {

                    // Configure the second tenant
                    var configUpdate2 = {'oae-tenants/domains/email': '^.*gatech.edu$'};
                    ConfigTestUtil.updateConfigAndWait(globalAdminRestContext, tenant2.tenant.alias, configUpdate2, function() {

                        // Verify that the correct tenant can be retrieved
                        var tenant = TenantsUtil.getTenantForEmailDomain('foo@cam.ac.uk');
                        assert.ok(tenant);
                        assert.strictEqual(tenant.alias, tenant1.tenant.alias);

                        // Verify a subdomain of a configured regex
                        tenant = TenantsUtil.getTenantForEmailDomain('foo@admin.cam.ac.uk');
                        assert.ok(tenant);
                        assert.strictEqual(tenant.alias, tenant1.tenant.alias);

                        // Verify no matches
                        tenant = TenantsUtil.getTenantForEmailDomain('no.no.no.no');
                        assert.ok(!tenant);

                        // Verify another tenant
                        tenant = TenantsUtil.getTenantForEmailDomain('foo@gatech.edu');
                        assert.ok(tenant);
                        assert.strictEqual(tenant.alias, tenant2.tenant.alias);
                        return callback();
                    });
                });
            });
        });
    });
});
