var should = require('should'),
    sinon = require('sinon'),
    testUtils = require('../../utils'),
    Promise = require('bluebird'),
    _ = require('lodash'),
    models = require('../../../server/models'),
    common = require('../../../server/lib/common'),
    UserAPI = require('../../../server/api/v0.1/users'),
    db = require('../../../server/data/db'),
    context = testUtils.context,
    userIdFor = testUtils.users.ids,
    roleIdFor = testUtils.roles.ids,

    sandbox = sinon.sandbox.create();

describe('Users API', function () {
    var eventsTriggered;

    // Keep the DB clean
    before(testUtils.teardown);

    before(testUtils.setup(
        'settings', 'users:roles', 'users:extra', 'user-token', 'perms:user', 'perms:role', 'perms:setting', 'perms:init', 'posts'
    ));

    beforeEach(function () {
        eventsTriggered = {};

        sandbox.stub(common.events, 'emit').callsFake(function (eventName, eventObj) {
            if (!eventsTriggered[eventName]) {
                eventsTriggered[eventName] = [];
            }

            eventsTriggered[eventName].push(eventObj);
        });
    });

    afterEach(function () {
        sandbox.restore();
    });

    after(testUtils.teardown);

    function checkForErrorType(type, done) {
        return function checkForErrorType(error) {
            if (error.errorType) {
                error.errorType.should.eql(type);
                done();
            } else {
                done(error);
            }
        };
    }

    describe('Browse', function () {
        function checkBrowseResponse(response, count, additional, missing, only, options) {
            should.exist(response);
            testUtils.API.checkResponse(response, 'users');
            should.exist(response.users);
            response.users.should.have.length(count);
            testUtils.API.checkResponse(response.users[0], 'user', additional, missing, only, options);
            testUtils.API.checkResponse(response.users[1], 'user', additional, missing, only, options);
            testUtils.API.checkResponse(response.users[2], 'user', additional, missing, only, options);
            testUtils.API.checkResponse(response.users[3], 'user', additional, missing, only, options);
            testUtils.API.checkResponse(response.users[4], 'user', additional, missing, only, options);
        }

        it('Owner can browse', function (done) {
            UserAPI.browse(context.owner).then(function (response) {
                checkBrowseResponse(response, 9);
                done();
            }).catch(done);
        });

        it('Admin can browse', function (done) {
            UserAPI.browse(context.admin).then(function (response) {
                checkBrowseResponse(response, 9);
                done();
            }).catch(done);
        });

        it('Editor can browse', function (done) {
            UserAPI.browse(context.editor).then(function (response) {
                checkBrowseResponse(response, 9);
                done();
            }).catch(done);
        });

        it('Author can browse active', function (done) {
            UserAPI.browse(context.author).then(function (response) {
                checkBrowseResponse(response, 9);
                done();
            }).catch(done);
        });

        it('Contributor can browse active', function (done) {
            UserAPI.browse(context.contributor).then(function (response) {
                checkBrowseResponse(response, 9);
                done();
            }).catch(done);
        });

        it('No-auth CAN browse, but only gets filtered active users', function (done) {
            UserAPI.browse().then(function (response) {
                checkBrowseResponse(response, 9, null, null, null, {public: true});
                done();
            }).catch(done);
        });

        it('Can browse all', function (done) {
            UserAPI.browse(_.extend({}, testUtils.context.admin, {status: 'all'})).then(function (response) {
                checkBrowseResponse(response, 9);
                done();
            }).catch(done);
        });

        it('Can browse with roles', function (done) {
            UserAPI.browse(_.extend({}, testUtils.context.admin, {
                status: 'all',
                include: 'roles'
            })).then(function (response) {
                should.exist(response);
                testUtils.API.checkResponse(response, 'users');
                should.exist(response.users);
                response.users.should.have.length(9);
                testUtils.API.checkResponse(response.users[0], 'user', 'roles');
                testUtils.API.checkResponse(response.users[1], 'user', 'roles');
                testUtils.API.checkResponse(response.users[2], 'user', 'roles');
                testUtils.API.checkResponse(response.users[3], 'user', 'roles');
                done();
            }).catch(done);
        });

        it('can browse and order by name using asc', function (done) {
            var expectedUsers;

            UserAPI.browse(testUtils.context.admin)
                .then(function (results) {
                    should.exist(results);

                    expectedUsers = _(results.users).map('slug').sortBy().value();

                    return UserAPI.browse(_.extend({}, testUtils.context.admin, {order: 'slug asc'}));
                })
                .then(function (results) {
                    var users;

                    should.exist(results);

                    users = _.map(results.users, 'slug');
                    users.should.eql(expectedUsers);
                })
                .then(done)
                .catch(done);
        });

        it('can browse and order by name using desc', function (done) {
            var expectedUsers;

            UserAPI.browse(testUtils.context.admin)
                .then(function (results) {
                    should.exist(results);

                    expectedUsers = _(results.users).map('slug').sortBy().reverse().value();

                    return UserAPI.browse(_.extend({}, testUtils.context.admin, {order: 'slug desc'}));
                })
                .then(function (results) {
                    var users;

                    should.exist(results);

                    users = _.map(results.users, 'slug');
                    users.should.eql(expectedUsers);
                })
                .then(done)
                .catch(done);
        });

        it('can browse with include count.posts', function (done) {
            UserAPI.browse(_.extend({}, testUtils.context.admin, {include: 'count.posts'})).then(function (response) {
                should.exist(response);
                testUtils.API.checkResponse(response, 'users');
                should.exist(response.users);
                response.users.should.have.length(9);

                response.users.map((user) => {
                  testUtils.API.checkResponse(user, 'user', 'count');
                });

                response.users.map((user) => {
                  if (user.email === 'jbloggs@example.com') {
                    user.count.posts.should.eql(8);
                  } else if (user.email === 'jbOgendAth@example.com') {
                    user.count.posts.should.eql(1);
                  } else {
                    response.users[1].count.posts.should.eql(0);
                  }
                });

                response.meta.pagination.should.have.property('page', 1);
                response.meta.pagination.should.have.property('limit', 15);
                response.meta.pagination.should.have.property('pages', 1);
                response.meta.pagination.should.have.property('total', 9);
                response.meta.pagination.should.have.property('next', null);
                response.meta.pagination.should.have.property('prev', null);

                done();
            }).catch(done);
        });
    });

    describe('Read', function () {
        function checkReadResponse(response, noEmail, additional, missing, only, options) {
            should.exist(response);
            should.not.exist(response.meta);
            should.exist(response.users);
            response.users[0].id.should.eql(testUtils.DataGenerator.Content.users[0].id);

            if (noEmail) {
                testUtils.API.checkResponse(response.users[0], 'user', additional, missing, only, options);
            } else {
                testUtils.API.checkResponse(response.users[0], 'user', additional, missing, only, options);
                response.users[0].created_at.should.be.an.instanceof(Date);
            }
        }

        it('Owner can read', function (done) {
            UserAPI.read(_.extend({}, context.owner, {id: userIdFor.owner})).then(function (response) {
                checkReadResponse(response);
                done();
            }).catch(done);
        });

        it('Admin can read', function (done) {
            var stuff = _.extend({}, context.admin, {id: userIdFor.owner});
            UserAPI.read(stuff).then(function (response) {
                checkReadResponse(response);

                done();
            }).catch(done);
        });

        it('Editor can read', function (done) {
            UserAPI.read(_.extend({}, context.editor, {id: userIdFor.owner})).then(function (response) {
                checkReadResponse(response);
                done();
            }).catch(done);
        });

        it('Author can read', function (done) {
            UserAPI.read(_.extend({}, context.author, {id: userIdFor.owner})).then(function (response) {
                checkReadResponse(response);
                done();
            }).catch(done);
        });

        it('Contributor can read', function (done) {
            UserAPI.read(_.extend({}, context.contributor, {id: userIdFor.owner})).then(function (response) {
                checkReadResponse(response);
                done();
            }).catch(done);
        });

        it('No-auth can read', function (done) {
            UserAPI.read({id: userIdFor.owner}).then(function (response) {
                checkReadResponse(response, true, null, null, null, {public: true});
                done();
            }).catch(done);
        });

        // TODO: this should be a 422?
        it('cannot fetch a user with an invalid slug', function (done) {
            UserAPI.read({slug: 'invalid!'}).then(function () {
                done(new Error('Should not return a result with invalid slug'));
            }).catch(function (err) {
                should.exist(err);
                err.message.should.eql('User not found.');

                done();
            });
        });
    });

    describe('Edit', function () {
        var newName = 'Jo McBlogger';

        function checkEditResponse(response) {
            should.exist(response);
            should.not.exist(response.meta);
            should.exist(response.users);
            response.users.should.have.length(1);
            testUtils.API.checkResponse(response.users[0], 'user');
            response.users[0].name.should.equal(newName);
            response.users[0].updated_at.should.be.an.instanceof(Date);
        }

        it('throws an error if there is an id mismatch', function (done) {
            var options = _.extend({}, context.author, {id: userIdFor.author});
            UserAPI.edit({users: [{id: userIdFor.owner, name: 'Override'}]}, options)
                .then(function () {
                    done(new Error('ID mismatches should not be permitted'));
                }).catch(checkForErrorType('BadRequestError', done));
        });

        it('Owner can edit all roles', function (done) {
            UserAPI.edit({users: [{name: newName}]}, _.extend({}, context.owner, {id: userIdFor.owner}))
                .then(function (response) {
                    checkEditResponse(response);

                    return UserAPI.edit({users: [{name: newName}]}, _.extend({}, context.owner, {id: userIdFor.admin}));
                }).then(function (response) {
                checkEditResponse(response);
                return UserAPI.edit({users: [{name: newName}]}, _.extend({}, context.owner, {id: userIdFor.editor}));
            }).then(function (response) {
                checkEditResponse(response);

                return UserAPI.edit({users: [{name: newName}]}, _.extend({}, context.owner, {id: userIdFor.author}));
            }).then(function (response) {
                checkEditResponse(response);

                return UserAPI.edit({users: [{name: newName}]}, _.extend({}, context.owner, {id: userIdFor.contributor}));
            }).then(function (response) {
                checkEditResponse(response);

                done();
            }).catch(done);
        });

        it('Admin can edit Admin, Editor, Author, and Contributor roles', function (done) {
            UserAPI.edit({users: [{name: newName}]}, _.extend({}, context.admin, {id: userIdFor.admin}))
                .then(function (response) {
                    checkEditResponse(response);
                    return UserAPI.edit({users: [{name: newName}]}, _.extend({}, context.admin, {id: userIdFor.editor}));
                }).then(function (response) {
                checkEditResponse(response);

                return UserAPI.edit({users: [{name: newName}]}, _.extend({}, context.admin, {id: userIdFor.author}));
            }).then(function (response) {
                checkEditResponse(response);

                return UserAPI.edit({users: [{name: newName}]}, _.extend({}, context.admin, {id: userIdFor.contributor}));
            }).then(function (response) {
                checkEditResponse(response);

                done();
            }).catch(done);
        });

        it('Admin CANNOT edit Owner role', function (done) {
            UserAPI.edit({users: [{name: newName}]}, _.extend({}, context.admin, {id: userIdFor.owner}))
                .then(function () {
                    done(new Error('Admin should not be able to edit owner account'));
                }).catch(function (error) {
                error.errorType.should.eql('NoPermissionError');
                done();
            });
        });

        it('Admin can edit Admin, Editor, Author, and Contributor roles with roles in payload', function (done) {
            UserAPI.edit({
                users: [{
                    name: newName,
                    roles: [roleIdFor.admin]
                }]
            }, _.extend({}, context.admin, {id: userIdFor.admin})).then(function (response) {
                checkEditResponse(response);
                return UserAPI.edit({
                    users: [{
                        name: newName,
                        roles: [roleIdFor.editor]
                    }]
                }, _.extend({}, context.admin, {id: userIdFor.editor}));
            }).then(function (response) {
                checkEditResponse(response);

                return UserAPI.edit({
                    users: [{
                        name: newName,
                        roles: [roleIdFor.author]
                    }]
                }, _.extend({}, context.admin, {id: userIdFor.author}));
            }).then(function (response) {
                checkEditResponse(response);

                return UserAPI.edit({
                    users: [{
                        name: newName,
                        roles: [roleIdFor.contributor]
                    }]
                }, _.extend({}, context.admin, {id: userIdFor.contributor}));
            }).then(function (response) {
                checkEditResponse(response);

                done();
            }).catch(done);
        });

        it('Editor can edit self', function () {
            return UserAPI.edit(
                {users: [{name: newName}]}, _.extend({}, context.editor, {id: userIdFor.editor})
            ).then(function (response) {
                checkEditResponse(response);
            });
        });

        it('Author CANNOT edit all roles', function (done) {
            // Cannot edit owner
            UserAPI.edit(
                {users: [{name: newName}]}, _.extend({}, context.author, {id: userIdFor.owner})
            ).then(function () {
                done(new Error('Author should not be able to edit owner account'));
            }).catch(function (error) {
                error.errorType.should.eql('NoPermissionError');
            }).finally(function () {
                // Cannot edit admin
                UserAPI.edit(
                    {users: [{name: newName}]}, _.extend({}, context.author, {id: userIdFor.admin})
                ).then(function () {
                    done(new Error('Author should not be able to edit admin account'));
                }).catch(function (error) {
                    error.errorType.should.eql('NoPermissionError');
                }).finally(function () {
                    UserAPI.edit(
                        {users: [{name: newName}]}, _.extend({}, context.author, {id: testUtils.DataGenerator.Content.extraUsers[2].id})
                    ).then(function () {
                        done(new Error('Author should not be able to edit author account which is not their own'));
                    }).catch(function (error) {
                        error.errorType.should.eql('NoPermissionError');
                        done();
                    });
                });
            });
        });

        it('Author can edit self', function (done) {
            // Next test that author CAN edit self
            UserAPI.edit(
                {users: [{name: newName}]}, _.extend({}, context.author, {id: userIdFor.author})
            ).then(function (response) {
                checkEditResponse(response);
                done();
            }).catch(done);
        });

        it('Author can edit self with role set', function (done) {
            // Next test that author CAN edit self
            UserAPI.edit(
                {
                    users: [{
                        name: newName,
                        roles: [roleIdFor.author]
                    }]
                }, _.extend({}, context.author, {id: userIdFor.author})
            ).then(function (response) {
                checkEditResponse(response);
                done();
            }).catch(done);
        });

        it('Author can edit self with role set as string', function (done) {
            // Next test that author CAN edit self
            UserAPI.edit(
                {
                    users: [{
                        name: newName,
                        roles: [roleIdFor.author.toString()]
                    }]
                }, _.extend({}, context.author, {id: userIdFor.author})
            ).then(function (response) {
                checkEditResponse(response);
                done();
            }).catch(done);
        });

        it('Contributor can edit self', function (done) {
            // Next test that contributor CAN edit self
            UserAPI.edit(
                {users: [{name: newName}]}, _.extend({}, context.contributor, {id: userIdFor.contributor})
            ).then(function (response) {
                checkEditResponse(response);
                done();
            }).catch(done);
        });

        it('Does not allow password to be set', function (done) {
            UserAPI.edit(
                {
                    users: [{
                        name: 'newname',
                        password: 'thisissupersafe'
                    }]
                }, _.extend({}, context.author, {id: userIdFor.author})
            ).then(function () {
                return models.User.findOne({id: userIdFor.author}).then(function (response) {
                    response.get('name').should.eql('newname');
                    response.get('password').should.not.eql('thisissupersafe');
                    done();
                });
            }).catch(done);
        });

        it('dateTime fields are returned as Date objects', function (done) {
            var userData = testUtils.DataGenerator.forModel.users[0];

            models.User.check({email: userData.email, password: userData.password}).then(function (user) {
                return UserAPI.read(_.merge({id: user.id}, context.internal));
            }).then(function (response) {
                response.users[0].created_at.should.be.an.instanceof(Date);
                response.users[0].updated_at.should.be.an.instanceof(Date);
                response.users[0].last_seen.should.be.an.instanceof(Date);

                done();
            }).catch(done);
        });

        describe('Change status', function () {
            describe('as owner', function () {
                it('[success] can change status to inactive for admin', function () {
                    return UserAPI.edit(
                        {
                            users: [
                                {
                                    status: 'inactive'
                                }
                            ]
                        }, _.extend({}, context.owner, {id: userIdFor.admin})
                    ).then(function () {
                        Object.keys(eventsTriggered).length.should.eql(2);
                        should.exist(eventsTriggered['user.edited']);
                        should.exist(eventsTriggered['user.deactivated']);

                        return models.User.findOne({id: userIdFor.admin, status: 'all'}).then(function (response) {
                            response.get('status').should.eql('inactive');
                        });
                    });
                });

                it('[success] can change status to inactive for editor', function () {
                    return UserAPI.edit(
                        {
                            users: [
                                {
                                    status: 'inactive'
                                }
                            ]
                        }, _.extend({}, context.owner, {id: userIdFor.editor})
                    ).then(function () {
                        return models.User.findOne({id: userIdFor.editor, status: 'all'}).then(function (response) {
                            response.get('status').should.eql('inactive');
                        });
                    });
                });

                it('[failure] can\' change my own status to inactive', function () {
                    return UserAPI.edit(
                        {
                            users: [
                                {
                                    status: 'inactive'
                                }
                            ]
                        }, _.extend({}, context.owner, {id: userIdFor.owner})
                    ).then(function () {
                        throw new Error('this is not allowed');
                    }).catch(function (err) {
                        (err instanceof common.errors.NoPermissionError).should.eql(true);
                    });
                });

                it('[failure] can\' change my own status to locked', function () {
                    return UserAPI.edit(
                        {
                            users: [
                                {
                                    status: 'locked'
                                }
                            ]
                        }, _.extend({}, context.owner, {id: userIdFor.owner})
                    ).then(function () {
                        throw new Error('this is not allowed');
                    }).catch(function (err) {
                        (err instanceof common.errors.NoPermissionError).should.eql(true);
                    });
                });
            });

            describe('as admin', function () {
                it('[failure] can\'t change status to inactive for owner', function () {
                    return UserAPI.edit(
                        {
                            users: [
                                {
                                    status: 'inactive'
                                }
                            ]
                        }, _.extend({}, context.admin, {id: userIdFor.owner})
                    ).then(function () {
                        throw new Error('this is not allowed');
                    }).catch(function (err) {
                        (err instanceof common.errors.NoPermissionError).should.eql(true);
                    });
                });

                it('[failure] can\'t change status to inactive for admin', function () {
                    return UserAPI.edit(
                        {
                            users: [
                                {
                                    status: 'inactive'
                                }
                            ]
                        }, _.extend({}, context.admin, {id: userIdFor.admin2})
                    ).then(function () {
                        throw new Error('this is not allowed');
                    }).catch(function (err) {
                        (err instanceof common.errors.NoPermissionError).should.eql(true);
                    });
                });

                it('[failure] can\' change my own status to inactive', function () {
                    return UserAPI.edit(
                        {
                            users: [
                                {
                                    status: 'inactive'
                                }
                            ]
                        }, _.extend({}, context.admin, {id: userIdFor.admin})
                    ).then(function () {
                        throw new Error('this is not allowed');
                    }).catch(function (err) {
                        (err instanceof common.errors.NoPermissionError).should.eql(true);
                    });
                });

                it('[success] can change status to inactive for editor', function () {
                    return UserAPI.edit(
                        {
                            users: [
                                {
                                    status: 'inactive'
                                }
                            ]
                        }, _.extend({}, context.admin, {id: userIdFor.editor})
                    ).then(function () {
                        return models.User.findOne({id: userIdFor.editor, status: 'all'}).then(function (response) {
                            response.get('status').should.eql('inactive');
                        });
                    });
                });
            });

            describe('as editor', function () {
                it('[failure] can\'t change status to inactive for owner', function () {
                    return UserAPI.edit(
                        {
                            users: [
                                {
                                    status: 'inactive'
                                }
                            ]
                        }, _.extend({}, context.editor, {id: userIdFor.owner})
                    ).then(function () {
                        throw new Error('this is not allowed');
                    }).catch(function (err) {
                        (err instanceof common.errors.NoPermissionError).should.eql(true);
                    });
                });

                it('[failure] can\' change my own status to inactive', function () {
                    return UserAPI.edit(
                        {
                            users: [
                                {
                                    status: 'inactive'
                                }
                            ]
                        }, _.extend({}, context.editor, {id: userIdFor.editor})
                    ).then(function () {
                        throw new Error('this is not allowed');
                    }).catch(function (err) {
                        (err instanceof common.errors.NoPermissionError).should.eql(true);
                    });
                });

                it('[failure] can\'t change status to inactive for admin', function () {
                    return UserAPI.edit(
                        {
                            users: [
                                {
                                    status: 'inactive'
                                }
                            ]
                        }, _.extend({}, context.editor, {id: userIdFor.admin})
                    ).then(function () {
                        throw new Error('this is not allowed');
                    }).catch(function (err) {
                        (err instanceof common.errors.NoPermissionError).should.eql(true);
                    });
                });

                it('[failure] can\'t change status to inactive for editor', function () {
                    return UserAPI.edit(
                        {
                            users: [
                                {
                                    status: 'inactive'
                                }
                            ]
                        }, _.extend({}, context.editor, {id: userIdFor.editor2})
                    ).then(function () {
                        throw new Error('this is not allowed');
                    }).catch(function (err) {
                        (err instanceof common.errors.NoPermissionError).should.eql(true);
                    });
                });

                it('[success] can change status to inactive for author', function () {
                    return UserAPI.edit(
                        {
                            users: [
                                {
                                    status: 'inactive'
                                }
                            ]
                        }, _.extend({}, context.editor, {id: userIdFor.author})
                    ).then(function () {
                        return models.User.findOne({id: userIdFor.author, status: 'all'}).then(function (response) {
                            response.get('status').should.eql('inactive');
                        });
                    });
                });
            });

            describe('as author', function () {
                it('[failure] can\'t change status to inactive for owner', function () {
                    return UserAPI.edit(
                        {
                            users: [
                                {
                                    status: 'inactive'
                                }
                            ]
                        }, _.extend({}, context.author, {id: userIdFor.owner})
                    ).then(function () {
                        throw new Error('this is not allowed');
                    }).catch(function (err) {
                        (err instanceof common.errors.NoPermissionError).should.eql(true);
                    });
                });

                it('[failure] can\' change my own status to inactive', function () {
                    return UserAPI.edit(
                        {
                            users: [
                                {
                                    status: 'inactive'
                                }
                            ]
                        }, _.extend({}, context.author, {id: userIdFor.author})
                    ).then(function () {
                        throw new Error('this is not allowed');
                    }).catch(function (err) {
                        (err instanceof common.errors.NoPermissionError).should.eql(true);
                    });
                });

                it('[failure] can\'t change status to inactive for admin', function () {
                    return UserAPI.edit(
                        {
                            users: [
                                {
                                    status: 'inactive'
                                }
                            ]
                        }, _.extend({}, context.author, {id: userIdFor.admin})
                    ).then(function () {
                        throw new Error('this is not allowed');
                    }).catch(function (err) {
                        (err instanceof common.errors.NoPermissionError).should.eql(true);
                    });
                });

                it('[failure] can\'t change status to inactive for editor', function () {
                    return UserAPI.edit(
                        {
                            users: [
                                {
                                    status: 'inactive'
                                }
                            ]
                        }, _.extend({}, context.author, {id: userIdFor.editor})
                    ).then(function () {
                        throw new Error('this is not allowed');
                    }).catch(function (err) {
                        (err instanceof common.errors.NoPermissionError).should.eql(true);
                    });
                });

                it('[failure] can\'t change status to inactive for author', function () {
                    return UserAPI.edit(
                        {
                            users: [
                                {
                                    status: 'inactive'
                                }
                            ]
                        }, _.extend({}, context.author, {id: userIdFor.author2})
                    ).then(function () {
                        throw new Error('this is not allowed');
                    }).catch(function (err) {
                        (err instanceof common.errors.NoPermissionError).should.eql(true);
                    });
                });
            });

            describe('as contributor', function () {
                it('[failure] can\' change my own status to inactive', function () {
                    return UserAPI.edit(
                        {
                            users: [
                                {
                                    status: 'inactive'
                                }
                            ]
                        }, _.extend({}, context.contributor, {id: userIdFor.contributor})
                    ).then(function () {
                        throw new Error('this is not allowed');
                    }).catch(function (err) {
                        (err instanceof common.errors.NoPermissionError).should.eql(true);
                    });
                });
            });
        });
    });

    describe('Destroy', function () {
        before(testUtils.teardown);

        describe('General Tests', function () {
            beforeEach(testUtils.setup(
                'settings', 'users:roles', 'users:extra', 'user-token', 'perms:user', 'perms:role', 'perms:setting', 'perms:init', 'posts'
            ));

            afterEach(testUtils.teardown);

            it('ensure posts get deleted', function (done) {
                var postIdsToDelete = [], postIsToKeep = [], options = {};

                Promise.mapSeries(testUtils.DataGenerator.forKnex.posts, function (post, i) {
                    post = _.cloneDeep(post);
                    delete post.id;

                    if (i % 2) {
                        post.author_id = userIdFor.editor;
                        post.status = 'published';
                        post.tags = testUtils.DataGenerator.forKnex.tags.slice(0, 1);
                        return models.Post.add(post, _.merge({}, options, context.editor));
                    } else {
                        post.author_id = userIdFor.author;
                        post.status = 'published';
                        post.tags = testUtils.DataGenerator.forKnex.tags.slice(2, 4);
                        return models.Post.add(post, _.merge({}, options, context.author));
                    }
                }).then(function (added) {
                    // 6 posts, 2 pages
                    added.length.should.eql(8);

                    // find all posts of the editor
                    return models.Post.findAll(_.merge({}, {
                        context: context.editor.context,
                        filter: 'author_id:' + userIdFor.editor,
                        withRelated: ['tags']
                    }, options));
                }).then(function (posts) {
                    posts.models.length.should.eql(3);
                    posts.models[0].relations.tags.length.should.eql(1);

                    _.each(posts.models, function (post) {
                        postIdsToDelete.push(post.get('id'));
                    });

                    // find all posts of the author
                    return models.Post.findAll(_.merge({
                        context: context.author.context,
                        filter: 'author_id:' + userIdFor.author,
                        withRelated: ['tags']
                    }, options));
                }).then(function (posts) {
                    posts.models.length.should.eql(3);
                    posts.models[0].relations.tags.length.should.eql(2);

                    _.each(posts.models, function (post) {
                        postIsToKeep.push(post.get('id'));
                    });

                    return Promise.mapSeries(postIdsToDelete, function (id) {
                        return db.knex('posts_tags').where('post_id', id);
                    });
                }).then(function (result) {
                    _.flatten(result).length.should.eql(3);

                    return db.knex('tags');
                }).then(function (allTags) {
                    allTags.length.should.eql(5);

                    return Promise.mapSeries(postIdsToDelete.concat(postIsToKeep), function (id) {
                        return db.knex('posts_authors').where('post_id', id);
                    });
                }).then(function (result) {
                    _.flatten(result).length.should.eql(6);

                    return db.knex('posts_authors');
                }).then(function (result) {
                    // 11 relations from the default posts from the data generator
                    // 6 relations from the newly added posts
                    _.flatten(result).length.should.eql(17);

                    return db.knex('users');
                }).then(function (result) {
                    _.flatten(result).length.should.eql(9);

                    return UserAPI.destroy(_.extend({}, context.owner, _.merge({}, options, {id: userIdFor.editor})));
                }).then(function () {
                    return models.User.findOne(_.merge({}, options, {id: userIdFor.editor}));
                }).then(function (user) {
                    should.not.exist(user);
                    return models.User.findOne(_.merge({}, options, {id: userIdFor.author}));
                }).then(function (user) {
                    should.exist(user);
                    return models.Post.findAll(_.merge({}, options, {filter: 'author_id:' + userIdFor.editor}));
                }).then(function (posts) {
                    posts.models.length.should.eql(0);

                    return models.Post.findAll(_.merge({}, options, {filter: 'author_id:' + userIdFor.author}));
                }).then(function (posts) {
                    posts.models.length.should.eql(3);

                    return Promise.mapSeries(postIdsToDelete, function (id) {
                        return db.knex('posts_tags').where('post_id', id);
                    });
                }).then(function (result) {
                    _.flatten(result).length.should.eql(0);

                    return Promise.mapSeries(postIsToKeep, function (id) {
                        return db.knex('posts_tags').where('post_id', id);
                    });
                }).then(function (result) {
                    _.flatten(result).length.should.eql(6);
                    return db.knex('tags');
                }).then(function (allTags) {
                    allTags.length.should.eql(5);

                    return Promise.mapSeries(postIdsToDelete.concat(postIsToKeep), function (id) {
                        return db.knex('posts_authors').where('post_id', id);
                    });
                }).then(function (result) {
                    // 3 relations where related, because the user owned 3 posts
                    _.flatten(result).length.should.eql(3);

                    return db.knex('posts_authors');
                }).then(function (result) {
                    // the user is secondary author on one post
                    // the user is the owner of one page
                    _.flatten(result).length.should.eql(12);

                    return db.knex('users');
                }).then(function (allUsers) {
                    // one user was deleted, that's why 8
                    allUsers.length.should.eql(8);
                    done();
                }).catch(done);
            });
        });

        describe('Permissions', function () {
            beforeEach(testUtils.setup(
                'settings', 'users:roles', 'users:extra', 'perms:user', 'perms:role', 'perms:setting', 'perms:init'
            ));

            afterEach(testUtils.teardown);

            describe('Owner', function () {
                it('CANNOT destroy self', function (done) {
                    UserAPI.destroy(_.extend({}, context.owner, {id: userIdFor.owner}))
                        .then(function () {
                            done(new Error('Owner should not be able to delete itself'));
                        }).catch(checkForErrorType('NoPermissionError', done));
                });

                it('Can destroy admin, editor, author, contributor', function (done) {
                    // Admin
                    UserAPI.destroy(_.extend({}, context.owner, {id: userIdFor.admin}))
                        .then(function (response) {
                            should.not.exist(response);
                            // Editor
                            return UserAPI.destroy(_.extend({}, context.owner, {id: userIdFor.editor}));
                        }).then(function (response) {
                        should.not.exist(response);

                        // Author
                        return UserAPI.destroy(_.extend({}, context.owner, {id: userIdFor.author}));
                    }).then(function (response) {
                        should.not.exist(response);

                        // Contributor
                        return UserAPI.destroy(_.extend({}, context.owner, {id: userIdFor.contributor}));
                    }).then(function (response) {
                        should.not.exist(response);
                        done();
                    }).catch(done);
                });
            });

            describe('Admin', function () {
                it('CANNOT destroy owner', function (done) {
                    UserAPI.destroy(_.extend({}, context.admin, {id: userIdFor.owner}))
                        .then(function () {
                            done(new Error('Admin should not be able to delete owner'));
                        }).catch(checkForErrorType('NoPermissionError', done));
                });

                it('Can destroy admin, editor, author, contributor', function (done) {
                    // Admin
                    UserAPI.destroy(_.extend({}, context.admin, {id: testUtils.DataGenerator.Content.extraUsers[0].id}))
                        .then(function (response) {
                            should.not.exist(response);

                            // Editor
                            return UserAPI.destroy(_.extend({}, context.admin, {id: testUtils.DataGenerator.Content.extraUsers[1].id}));
                        }).then(function (response) {
                        should.not.exist(response);

                        // Author
                        return UserAPI.destroy(_.extend({}, context.admin, {id: testUtils.DataGenerator.Content.extraUsers[2].id}));
                    }).then(function (response) {
                        should.not.exist(response);

                        // Contributor
                        return UserAPI.destroy(_.extend({}, context.admin, {id: testUtils.DataGenerator.Content.extraUsers[3].id}));
                    }).then(function (response) {
                        should.not.exist(response);
                        done();
                    }).catch(done);
                });
            });

            describe('Editor', function () {
                it('CANNOT destroy owner', function (done) {
                    UserAPI.destroy(_.extend({}, context.editor, {id: userIdFor.owner}))
                        .then(function () {
                            done(new Error('Editor should not be able to delete owner'));
                        }).catch(checkForErrorType('NoPermissionError', done));
                });

                it('CANNOT destroy admin', function (done) {
                    UserAPI.destroy(_.extend({}, context.editor, {id: userIdFor.admin}))
                        .then(function () {
                            done(new Error('Editor should not be able to delete admin'));
                        }).catch(checkForErrorType('NoPermissionError', done));
                });

                it('CANNOT destroy other editor', function (done) {
                    UserAPI.destroy(_.extend({}, context.editor, {id: testUtils.DataGenerator.Content.extraUsers[1].id}))
                        .then(function () {
                            done(new Error('Editor should not be able to delete other editor'));
                        }).catch(checkForErrorType('NoPermissionError', done));
                });

                it('Can destroy self', function (done) {
                    UserAPI.destroy(_.extend({}, context.editor, {id: userIdFor.editor}))
                        .then(function (response) {
                            should.not.exist(response);
                            done();
                        }).catch(done);
                });

                it('Can destroy author', function (done) {
                    UserAPI.destroy(_.extend({}, context.editor, {id: userIdFor.author}))
                        .then(function (response) {
                            should.not.exist(response);
                            done();
                        }).catch(done);
                });
            });

            describe('Author', function () {
                it('CANNOT destroy owner', function (done) {
                    UserAPI.destroy(_.extend({}, context.author, {id: userIdFor.owner}))
                        .then(function () {
                            done(new Error('Author should not be able to delete owner'));
                        }).catch(checkForErrorType('NoPermissionError', done));
                });

                it('CANNOT destroy admin', function (done) {
                    UserAPI.destroy(_.extend({}, context.author, {id: userIdFor.admin}))
                        .then(function () {
                            done(new Error('Author should not be able to delete admin'));
                        }).catch(checkForErrorType('NoPermissionError', done));
                });

                it('CANNOT destroy editor', function (done) {
                    UserAPI.destroy(_.extend({}, context.author, {id: userIdFor.editor}))
                        .then(function () {
                            done(new Error('Author should not be able to delete editor'));
                        }).catch(checkForErrorType('NoPermissionError', done));
                });

                it('CANNOT destroy other author', function (done) {
                    UserAPI.destroy(_.extend({}, context.author, {id: testUtils.DataGenerator.Content.extraUsers[2].id}))
                        .then(function () {
                            done(new Error('Author should not be able to delete other author'));
                        }).catch(checkForErrorType('NoPermissionError', done));
                });

                it('CANNOT destroy self', function (done) {
                    UserAPI.destroy(_.extend({}, context.author, {id: userIdFor.author}))
                        .then(function () {
                            done(new Error('Author should not be able to delete self'));
                        }).catch(checkForErrorType('NoPermissionError', done));
                });
            });

            describe('Contributor', function () {
                it('CANNOT destroy owner', function (done) {
                    UserAPI.destroy(_.extend({}, context.contributor, {id: userIdFor.owner}))
                        .then(function () {
                            done(new Error('Contributor should not be able to delete owner'));
                        }).catch(checkForErrorType('NoPermissionError', done));
                });

                it('CANNOT destroy self', function (done) {
                    UserAPI.destroy(_.extend({}, context.contributor, {id: userIdFor.contributor}))
                        .then(function () {
                            done(new Error('Contributor should not be able to delete self'));
                        }).catch(checkForErrorType('NoPermissionError', done));
                });
            });
        });
    });

    describe('Edit and assign role', function () {
        var newName = 'Jo McBlogger';

        before(testUtils.teardown);

        before(testUtils.setup(
            'settings', 'users:roles', 'users:extra', 'perms:user', 'perms:role', 'perms:setting', 'perms:init'
        ));

        beforeEach(function () {
            return testUtils.fixtures.resetRoles();
        });

        after(testUtils.teardown);

        function checkEditResponse(response) {
            should.exist(response);
            should.not.exist(response.meta);
            should.exist(response.users);
            response.users.should.have.length(1);
            testUtils.API.checkResponse(response.users[0], 'user', ['roles']);
            response.users[0].name.should.equal(newName);
            response.users[0].updated_at.should.be.an.instanceof(Date);
        }

        it('throws an error if there is an id mismatch', function (done) {
            var options = _.extend({}, context.author, {id: userIdFor.author});
            UserAPI.edit({users: [{id: userIdFor.owner, name: 'Override', roles: [roleIdFor.author]}]}, options)
                .then(function () {
                    done(new Error('ID mismatches should not be permitted'));
                }).catch(checkForErrorType('BadRequestError', done));
        });

        describe('Owner', function () {
            it('Can assign Admin role', function (done) {
                var options = _.extend({}, context.owner, {id: userIdFor.author}, {include: 'roles'});
                UserAPI.read(options).then(function (response) {
                    response.users[0].id.should.equal(userIdFor.author);
                    response.users[0].roles[0].name.should.equal('Author');

                    return UserAPI.edit({
                        users: [
                            {name: newName, roles: [roleIdFor.admin]}
                        ]
                    }, options).then(function (response) {
                        checkEditResponse(response);
                        response.users[0].id.should.equal(userIdFor.author);
                        response.users[0].roles[0].name.should.equal('Administrator');

                        done();
                    }).catch(done);
                });
            });

            it('Can assign Editor role', function (done) {
                var options = _.extend({}, context.owner, {id: userIdFor.admin}, {include: 'roles'});
                UserAPI.read(options).then(function (response) {
                    response.users[0].id.should.equal(userIdFor.admin);
                    response.users[0].roles[0].name.should.equal('Administrator');

                    return UserAPI.edit({
                        users: [
                            {name: newName, roles: [roleIdFor.editor]}
                        ]
                    }, options).then(function (response) {
                        checkEditResponse(response);
                        response.users[0].id.should.equal(userIdFor.admin);
                        response.users[0].roles[0].name.should.equal('Editor');

                        done();
                    }).catch(done);
                });
            });

            it('Can assign Author role', function (done) {
                var options = _.extend({}, context.owner, {id: userIdFor.admin}, {include: 'roles'});
                UserAPI.read(options).then(function (response) {
                    response.users[0].id.should.equal(userIdFor.admin);
                    response.users[0].roles[0].name.should.equal('Administrator');

                    return UserAPI.edit({
                        users: [
                            {name: newName, roles: [roleIdFor.author]}
                        ]
                    }, options).then(function (response) {
                        checkEditResponse(response);
                        response.users[0].id.should.equal(userIdFor.admin);
                        response.users[0].roles[0].name.should.equal('Author');

                        done();
                    }).catch(done);
                });
            });

            it('Can assign Contributor role', function (done) {
                var options = _.extend({}, context.owner, {id: userIdFor.admin}, {include: 'roles'});
                UserAPI.read(options).then(function (response) {
                    response.users[0].id.should.equal(userIdFor.admin);
                    response.users[0].roles[0].name.should.equal('Administrator');

                    return UserAPI.edit({
                        users: [
                            {name: newName, roles: [roleIdFor.contributor]}
                        ]
                    }, options).then(function (response) {
                        checkEditResponse(response);
                        response.users[0].id.should.equal(userIdFor.admin);
                        response.users[0].roles[0].name.should.equal('Contributor');

                        done();
                    }).catch(done);
                });
            });
        });

        describe('Admin', function () {
            it('Can assign Admin role', function (done) {
                var options = _.extend({}, context.admin, {id: userIdFor.author}, {include: 'roles'});
                UserAPI.read(options).then(function (response) {
                    response.users[0].id.should.equal(userIdFor.author);
                    response.users[0].roles[0].name.should.equal('Author');

                    return UserAPI.edit({
                        users: [
                            {name: newName, roles: [roleIdFor.admin]}
                        ]
                    }, options).then(function (response) {
                        checkEditResponse(response);
                        response.users[0].id.should.equal(userIdFor.author);
                        response.users[0].roles[0].name.should.equal('Administrator');

                        done();
                    }).catch(done);
                });
            });

            it('Can assign Editor role', function (done) {
                var options = _.extend({}, context.admin, {id: userIdFor.author}, {include: 'roles'});
                UserAPI.read(options).then(function (response) {
                    response.users[0].id.should.equal(userIdFor.author);
                    response.users[0].roles[0].name.should.equal('Author');

                    return UserAPI.edit({
                        users: [
                            {name: newName, roles: [roleIdFor.editor]}
                        ]
                    }, options).then(function (response) {
                        checkEditResponse(response);
                        response.users[0].id.should.equal(userIdFor.author);
                        response.users[0].roles[0].name.should.equal('Editor');

                        done();
                    }).catch(done);
                });
            });

            it('Can assign Author role', function (done) {
                var options = _.extend({}, context.admin, {id: userIdFor.editor}, {include: 'roles'});
                UserAPI.read(options).then(function (response) {
                    response.users[0].id.should.equal(userIdFor.editor);
                    response.users[0].roles[0].name.should.equal('Editor');

                    return UserAPI.edit({
                        users: [
                            {name: newName, roles: [roleIdFor.author]}
                        ]
                    }, options).then(function (response) {
                        checkEditResponse(response);
                        response.users[0].id.should.equal(userIdFor.editor);
                        response.users[0].roles[0].name.should.equal('Author');

                        done();
                    }).catch(done);
                });
            });

            it('Can assign Contributor role', function (done) {
                var options = _.extend({}, context.admin, {id: userIdFor.editor}, {include: 'roles'});
                UserAPI.read(options).then(function (response) {
                    response.users[0].id.should.equal(userIdFor.editor);
                    response.users[0].roles[0].name.should.equal('Editor');

                    return UserAPI.edit({
                        users: [
                            {name: newName, roles: [roleIdFor.contributor]}
                        ]
                    }, options).then(function (response) {
                        checkEditResponse(response);
                        response.users[0].id.should.equal(userIdFor.editor);
                        response.users[0].roles[0].name.should.equal('Contributor');

                        done();
                    }).catch(done);
                });
            });

            it('CANNOT downgrade owner', function (done) {
                var options = _.extend({}, context.admin, {id: userIdFor.owner}, {include: 'roles'});
                UserAPI.read(options).then(function (response) {
                    response.users[0].id.should.equal(userIdFor.owner);
                    response.users[0].roles[0].name.should.equal('Owner');

                    return UserAPI.edit({
                        users: [{name: newName, roles: [roleIdFor.author]}]
                    }, options).then(function () {
                        done(new Error('Author should not be able to downgrade owner'));
                    }).catch(checkForErrorType('NoPermissionError', done));
                });
            });
        });

        describe('Editor', function () {
            it('Can assign author role to author', function (done) {
                UserAPI.edit(
                    {users: [{name: newName, roles: [roleIdFor.author]}]},
                    _.extend({}, context.editor, {id: testUtils.DataGenerator.Content.extraUsers[2].id}, {include: 'roles'})
                ).then(function (response) {
                    checkEditResponse(response);
                    response.users[0].id.should.equal(testUtils.DataGenerator.Content.extraUsers[2].id);
                    response.users[0].roles[0].name.should.equal('Author');

                    done();
                }).catch(done);
            });

            it('Can assign contributor role to author', function (done) {
                UserAPI.edit(
                    {users: [{name: newName, roles: [roleIdFor.contributor]}]},
                    _.extend({}, context.editor, {id: testUtils.DataGenerator.Content.extraUsers[2].id}, {include: 'roles'})
                ).then(function (response) {
                    checkEditResponse(response);
                    response.users[0].id.should.equal(testUtils.DataGenerator.Content.extraUsers[2].id);
                    response.users[0].roles[0].name.should.equal('Contributor');

                    done();
                }).catch(done);
            });

            it('Can assign contributor role to contributor', function (done) {
                UserAPI.edit(
                    {users: [{name: newName, roles: [roleIdFor.contributor]}]},
                    _.extend({}, context.editor, {id: testUtils.DataGenerator.Content.extraUsers[3].id}, {include: 'roles'})
                ).then(function (response) {
                    checkEditResponse(response);
                    response.users[0].id.should.equal(testUtils.DataGenerator.Content.extraUsers[3].id);
                    response.users[0].roles[0].name.should.equal('Contributor');

                    done();
                }).catch(done);
            });

            it('CANNOT assign author role to self', function (done) {
                UserAPI.edit(
                    {users: [{name: newName, roles: [roleIdFor.author]}]},
                    _.extend({}, context.editor, {id: userIdFor.editor}, {include: 'roles'})
                ).then(function () {
                    done(new Error('Editor should not be able to downgrade their role'));
                }).catch(checkForErrorType('NoPermissionError', done));
            });

            it('CANNOT assign contributor role to self', function (done) {
                UserAPI.edit(
                    {users: [{name: newName, roles: [roleIdFor.contributor]}]},
                    _.extend({}, context.editor, {id: userIdFor.editor}, {include: 'roles'})
                ).then(function () {
                    done(new Error('Editor should not be able to downgrade their role'));
                }).catch(checkForErrorType('NoPermissionError', done));
            });

            it('CANNOT assign author role to other Editor', function (done) {
                UserAPI.edit(
                    {users: [{name: newName, roles: [roleIdFor.author]}]},
                    _.extend({}, context.editor, {id: testUtils.DataGenerator.Content.extraUsers[1].id}, {include: 'roles'})
                ).then(function () {
                    done(new Error('Editor should not be able to change the roles of other editors'));
                }).catch(checkForErrorType('NoPermissionError', done));
            });

            it('CANNOT assign contributor role to other Editor', function (done) {
                UserAPI.edit(
                    {users: [{name: newName, roles: [roleIdFor.contributor]}]},
                    _.extend({}, context.editor, {id: testUtils.DataGenerator.Content.extraUsers[1].id}, {include: 'roles'})
                ).then(function () {
                    done(new Error('Editor should not be able to change the roles of other editors'));
                }).catch(checkForErrorType('NoPermissionError', done));
            });

            it('CANNOT assign author role to admin', function (done) {
                UserAPI.edit(
                    {users: [{name: newName, roles: [roleIdFor.author]}]},
                    _.extend({}, context.editor, {id: userIdFor.admin}, {include: 'roles'})
                ).then(function () {
                    done(new Error('Editor should not be able to change the roles of admins'));
                }).catch(checkForErrorType('NoPermissionError', done));
            });

            it('CANNOT assign contributor role to admin', function (done) {
                UserAPI.edit(
                    {users: [{name: newName, roles: [roleIdFor.contributor]}]},
                    _.extend({}, context.editor, {id: userIdFor.admin}, {include: 'roles'})
                ).then(function () {
                    done(new Error('Editor should not be able to change the roles of admins'));
                }).catch(checkForErrorType('NoPermissionError', done));
            });

            it('CANNOT assign admin role to author', function (done) {
                UserAPI.edit(
                    {users: [{name: newName, roles: [roleIdFor.admin]}]},
                    _.extend({}, context.editor, {id: userIdFor.author}, {include: 'roles'})
                ).then(function () {
                    done(new Error('Editor should not be able to upgrade the role of contributors'));
                }).catch(checkForErrorType('NoPermissionError', done));
            });

            it('CANNOT assign admin role to contributor', function (done) {
                UserAPI.edit(
                    {users: [{name: newName, roles: [roleIdFor.admin]}]},
                    _.extend({}, context.editor, {id: userIdFor.contributor}, {include: 'roles'})
                ).then(function () {
                    done(new Error('Editor should not be able to upgrade the role of contributors'));
                }).catch(checkForErrorType('NoPermissionError', done));
            });
        });

        describe('Author', function () {
            it('CANNOT assign higher role to self', function (done) {
                UserAPI.edit(
                    {users: [{name: newName, roles: [roleIdFor.editor]}]},
                    _.extend({}, context.author, {id: userIdFor.author}, {include: 'roles'})
                ).then(function () {
                    done(new Error('Author should not be able to upgrade their role'));
                }).catch(checkForErrorType('NoPermissionError', done));
            });
        });

        describe('Contributor', function () {
            it('CANNOT assign higher role to self', function (done) {
                UserAPI.edit(
                    {users: [{name: newName, roles: [roleIdFor.editor]}]},
                    _.extend({}, context.contributor, {id: userIdFor.contributor}, {include: 'roles'})
                ).then(function () {
                    done(new Error('Contributor should not be able to upgrade their role'));
                }).catch(checkForErrorType('NoPermissionError', done));
            });
        });
    });

    describe('Transfer ownership', function () {
        before(testUtils.teardown);

        beforeEach(testUtils.setup(
            'settings', 'users:roles', 'perms:user', 'perms:role', 'perms:setting', 'perms:init'
        ));

        afterEach(testUtils.teardown);

        it('Owner can transfer ownership', function (done) {
            // transfer ownership to admin user id:2
            UserAPI.transferOwnership(
                {owner: [{id: userIdFor.admin}]},
                context.owner
            ).then(function (response) {
                should.exist(response);
                response.users.should.have.length(2);
                testUtils.API.checkResponse(response.users[0], 'user', ['roles']);
                testUtils.API.checkResponse(response.users[1], 'user', ['roles']);
                response.users[0].roles[0].id.should.equal(testUtils.DataGenerator.Content.roles[0].id);
                response.users[1].roles[0].id.should.equal(testUtils.DataGenerator.Content.roles[3].id);
                done();
            }).catch(done);
        });

        it('Owner CANNOT downgrade own role', function (done) {
            // Cannot change own role to admin
            UserAPI.transferOwnership(
                {owner: [{id: userIdFor.owner}]},
                context.owner
            ).then(function () {
                done(new Error('Owner should not be able to downgrade their role'));
            }).catch(checkForErrorType('ValidationError', done));
        });

        it('Admin CANNOT transfer ownership', function (done) {
            // transfer ownership to user id: 2
            UserAPI.transferOwnership(
                {owner: [{id: userIdFor.editor}]},
                context.admin
            ).then(function () {
                done(new Error('Admin is not denied transferring ownership.'));
            }).catch(checkForErrorType('NoPermissionError', done));
        });

        it('Editor CANNOT transfer ownership', function (done) {
            // transfer ownership to user id: 2
            UserAPI.transferOwnership(
                {owner: [{id: userIdFor.admin}]},
                context.editor
            ).then(function () {
                done(new Error('Editor is not denied transferring ownership.'));
            }).catch(checkForErrorType('NoPermissionError', done));
        });

        it('Author CANNOT transfer ownership', function (done) {
            // transfer ownership to user id: 2
            UserAPI.transferOwnership(
                {owner: [{id: userIdFor.admin}]},
                context.author
            ).then(function () {
                done(new Error('Author is not denied transferring ownership.'));
            }).catch(checkForErrorType('NoPermissionError', done));
        });

        it('Contributor CANNOT transfer ownership', function (done) {
            // transfer ownership to user id: 2
            UserAPI.transferOwnership(
                {owner: [{id: userIdFor.admin}]},
                context.contributor
            ).then(function () {
                done(new Error('Contributor is not denied transferring ownership.'));
            }).catch(checkForErrorType('NoPermissionError', done));
        });

        it('Blog is still setup', function (done) {
            // transfer ownership to admin user
            UserAPI.transferOwnership(
                {owner: [{id: userIdFor.admin}]},
                context.owner
            ).then(function (response) {
                should.exist(response);
                return models.User.isSetup();
            }).then(function (isSetup) {
                isSetup.should.eql(true);
                done();
            }).catch(done);
        });

        it('Blog is still setup, new owner is locked', function (done) {
            // transfer ownership to admin user
            UserAPI.transferOwnership(
                {owner: [{id: userIdFor.admin}]},
                context.owner
            ).then(function (response) {
                should.exist(response);
                return models.User.edit({status: 'locked'}, {id: userIdFor.admin});
            }).then(function (modifiedUser) {
                modifiedUser.get('status').should.eql('locked');
                return models.User.isSetup();
            }).then(function (isSetup) {
                isSetup.should.eql(true);
                done();
            }).catch(done);
        });
    });

    describe('Change Password', function () {
        before(testUtils.teardown);

        beforeEach(testUtils.setup(
            'settings', 'users:roles', 'perms:user', 'perms:role', 'perms:setting', 'perms:init'
        ));

        afterEach(testUtils.teardown);

        it('Owner can change own password', function (done) {
            var payload = {
                password: [{
                    user_id: userIdFor.owner,
                    oldPassword: 'Sl1m3rson99',
                    newPassword: 'newSl1m3rson',
                    ne2Password: 'newSl1m3rson'
                }]
            };
            UserAPI.changePassword(payload, _.extend({}, context.owner, {id: userIdFor.owner}))
                .then(function (response) {
                    response.password[0].message.should.eql('Password changed successfully.');
                    done();
                }).catch(done);
        });

        it('Owner can\'t change password with wrong oldPassword', function (done) {
            var payload = {
                password: [{
                    user_id: userIdFor.owner,
                    oldPassword: 'wrong',
                    newPassword: 'Sl1m3rson9',
                    ne2Password: 'Sl1m3rson9'
                }]
            };
            UserAPI.changePassword(payload, _.extend({}, context.owner, {id: userIdFor.owner}))
                .then(function () {
                    done(new Error('Password change is not denied.'));
                }).catch(checkForErrorType('ValidationError', done));
        });

        it('Owner can\'t change password without old password', function (done) {
            var payload = {
                password: [{
                    user_id: userIdFor.owner,
                    oldPassword: '',
                    newPassword: 'Sl1m3rson19',
                    ne2Password: 'Sl1m3rson19'
                }]
            };
            UserAPI.changePassword(payload, _.extend({}, context.owner, {id: userIdFor.owner}))
                .then(function () {
                    done(new Error('Password change is not denied.'));
                }).catch(checkForErrorType('ValidationError', done));
        });

        it('Owner can\'t change password without matching passwords', function (done) {
            var payload = {
                password: [{
                    user_id: userIdFor.owner,
                    oldPassword: 'Sl1m3rson99',
                    newPassword: 'Sl1m3rson19',
                    ne2Password: 'Sl1m3rson29'
                }]
            };
            UserAPI.changePassword(payload, _.extend({}, context.owner, {id: userIdFor.owner}))
                .then(function () {
                    done(new Error('Password change is not denied.'));
                }).catch(checkForErrorType('ValidationError', done));
        });

        it('Owner can\'t change editor password without matching passwords', function (done) {
            var payload = {
                password: [{
                    user_id: userIdFor.editor,
                    newPassword: 'Sl1m3rson19',
                    ne2Password: 'Sl1m3rson29'
                }]
            };
            UserAPI.changePassword(payload, _.extend({}, context.owner, {id: userIdFor.owner}))
                .then(function () {
                    done(new Error('Password change is not denied.'));
                }).catch(checkForErrorType('ValidationError', done));
        });

        it('Owner can\'t change editor password with too short passwords', function (done) {
            var payload = {
                password: [{
                    user_id: userIdFor.editor,
                    newPassword: 'only8car',
                    ne2Password: 'only8car'
                }]
            };
            UserAPI.changePassword(payload, _.extend({}, context.owner, {id: userIdFor.owner}))
                .then(function () {
                    done(new Error('Password change is not denied.'));
                }).catch(checkForErrorType('ValidationError', done));
        });

        it('Owner can change password for editor', function (done) {
            var payload = {
                password: [{
                    user_id: userIdFor.editor,
                    newPassword: 'newSl1m3rson',
                    ne2Password: 'newSl1m3rson'
                }]
            };
            UserAPI.changePassword(payload, _.extend({}, context.owner, {id: userIdFor.owner}))
                .then(function (response) {
                    response.password[0].message.should.eql('Password changed successfully.');
                    done();
                }).catch(done);
        });

        it('Editor can\'t change password for admin', function (done) {
            var payload = {
                password: [{
                    user_id: userIdFor.admin,
                    newPassword: 'newSl1m3rson',
                    ne2Password: 'newSl1m3rson'
                }]
            };
            UserAPI.changePassword(payload, _.extend({}, context.editor, {id: userIdFor.editor}))
                .then(function () {
                    done(new Error('Password change is not denied.'));
                }).catch(checkForErrorType('NoPermissionError', done));
        });
    });
});
