Merge branch 'next' into reply

This commit is contained in:
Kiwi
2018-01-10 18:08:12 +01:00
committed by GitHub
221 changed files with 7193 additions and 4100 deletions
+41 -13
View File
@@ -54,24 +54,34 @@ module.exports = {
moderateNav: '.talk-admin-nav-moderate',
settingsButton: '.talk-admin-header-settings-button',
signOutButton: '.talk-admin-header-sign-out',
suspendUserDialog: '.talk-admin-suspend-user-dialog',
suspendUserConfirmButton: '.talk-admin-suspend-user-dialog-confirm',
supendUserSendButton: '.talk-admin-suspend-user-dialog-send',
usernameDialog: '.talk-reject-username-dialog',
usernameDialogButtons: '.talk-reject-username-dialog-buttons',
usernameDialogSuspend: '.talk-reject-username-dialog-button-k',
usernameDialogSuspensionMessage: '.talk-reject-username-dialog-suspension-message',
toast: '.toastify',
toastClose: '.toastify__close',
},
sections: {
suspendUserDialog: {
selector: '.talk-admin-suspend-user-dialog',
elements: {
step0: '.talk-admin-suspend-user-dialog-step-0',
step1: '.talk-admin-suspend-user-dialog-step-1',
confirmButton: '.talk-admin-suspend-user-dialog-confirm',
sendButton: '.talk-admin-suspend-user-dialog-send',
}
},
usernameDialog: {
selector: '.talk-admin-reject-username-dialog',
elements: {
step0: '.talk-admin-reject-username-dialog-step-0',
step1: '.talk-admin-reject-username-dialog-step-1',
buttons: '.talk-admin-reject-username-dialog-buttons',
suspend: '.talk-admin-reject-username-dialog-button-k',
suspensionMessage: '.talk-admin-reject-username-dialog-suspension-message',
}
},
moderate: {
selector: '.talk-admin-moderation-container',
elements: {
comment: '.talk-admin-moderate-comment',
commentActionMenu: '.talk-admin-moderate-comment-actions-menu',
actionItemSuspendUser: '.action-menu-item#suspendUser',
actionMenuButton: '.talk-admin-moderate-comment-actions-menu #actions-dropdown-0'
commentUsername: '.talk-admin-moderate-comment-username',
}
},
stories: {
@@ -109,13 +119,31 @@ module.exports = {
firstRow: '.talk-admin-community-people-row:first-child',
dropdownStatus: '.talk-admin-community-people-dd-status',
dropdownRole: '.talk-admin-community-people-dd-role',
dropdownStatusActive: '.talk-admin-community-people-dd-status .dd-list-active',
optionActive: '.dd-option#ACTIVE',
optionBanned: '.dd-option#BANNED',
dropdownStatusActive: '.talk-admin-community-people-dd-status .is-upgraded',
optionSuspendUser: '.action-menu-item#suspendUser',
optionBanUser: '.action-menu-item#banUser',
optionRemoveBan: '.action-menu-item#removeBan',
optionRemoveSuspension: '.action-menu-item#removeSuspension',
}
}
}
},
userDetailDrawer: {
selector: '.talk-admin-user-detail-drawer',
elements: {
'closeButton': '.talk-admin-user-detail-drawer-close-button',
'tabBar': '.talk-admin-user-detail-tab-bar',
'allTab': '.talk-admin-user-detail-all-tab',
'rejectedTab': '.talk-admin-user-detail-rejected-tab',
'historyTab': '.talk-admin-user-detail-history-tab',
'historyPane': '.talk-admin-user-detail-history-tab-pane',
'accountHistory': '.talk-admin-account-history',
'accountHistoryRowStatus':'.talk-admin-account-history-row-status',
'actionsMenu': '.talk-admin-user-detail-actions-button',
'actionItemSuspendUser': '.action-menu-item#suspendUser',
'actionMenuButton': '.talk-admin-user-detail-actions-menu #actions-dropdown-0',
}
},
drawer: {
selector: '.talk-admin-drawer-nav',
commands: [{
+2 -2
View File
@@ -105,8 +105,8 @@ module.exports = {
replyButton: '.talk-stream-comment.talk-stream-comment-level-0 .talk-plugin-replies-reply-button',
respectButton: '.talk-stream-comment.talk-stream-comment-level-0 .talk-stream-comment-footer .talk-plugin-respect-button',
restrictedMessageBox: '.talk-restricted-message-box',
suspendedAccountInput: '.talk-suspended-account-username-input',
suspendedAccountSubmitButton: '.talk-suspended-account-submit-button',
changeUsernameInput: '.talk-change-username-username-input',
changeUsernameSubmitButton: '.talk-change-username-submit-button',
},
sections: {
flag: {
+2 -1
View File
@@ -37,6 +37,7 @@ module.exports = {
adminPage
.openDrawer()
.goToCommunity();
.goToCommunity();
},
};
+1
View File
@@ -18,6 +18,7 @@ module.exports = {
},
'creates a new asset': (client) => {
const asset = 'newAssetTest';
const embedStream = client.page.embedStream();
+18 -20
View File
@@ -22,12 +22,10 @@ module.exports = {
adminPage.navigateAndLogin(admin);
},
'admin flags user\'s username as offensive': (client) => {
'admin flags users username as offensive': (client) => {
const embedStream = client.page.embedStream();
const comments = embedStream
.navigate()
.ready();
const comments = embedStream.navigate().ready();
comments
.waitForElementVisible('@firstComment')
@@ -65,17 +63,18 @@ module.exports = {
.click('@flaggedUserRejectButton');
},
'admin suspends the user': (client) => {
const adminPage = client.page.admin();
const community = client.page.admin().section.community;
const usernameDialog = client.page.admin().section.usernameDialog;
adminPage
.waitForElementVisible('@usernameDialog')
.waitForElementVisible('@usernameDialogButtons')
.waitForElementVisible('@usernameDialogSuspend')
.click('@usernameDialogSuspend')
.waitForElementVisible('@usernameDialogSuspensionMessage')
.click('@usernameDialogSuspend');
usernameDialog
.waitForElementVisible('@buttons')
.waitForElementVisible('@step0')
.waitForElementVisible('@suspend')
.click('@suspend')
.waitForElementVisible('@step1')
.waitForElementVisible('@suspend')
.click('@suspend');
community
.waitForElementNotPresent('@flaggedUser');
},
@@ -95,8 +94,7 @@ module.exports = {
const embedStream = client.page.embedStream();
const comments = embedStream.section.comments;
comments
.waitForElementVisible('@restrictedMessageBox');
comments.waitForElementVisible('@restrictedMessageBox');
},
'user should not be able to comment': (client) => {
const embedStream = client.page.embedStream();
@@ -112,11 +110,11 @@ module.exports = {
const {testData: {user}} = client.globals;
comments
.waitForElementVisible('@suspendedAccountInput')
.setValue('@suspendedAccountInput', `${user.username}_alternative`)
.waitForElementVisible('@suspendedAccountSubmitButton')
.click('@suspendedAccountSubmitButton')
.waitForElementNotPresent('@suspendedAccountInput');
.waitForElementVisible('@changeUsernameInput')
.setValue('@changeUsernameInput', `${user.username}_alternative`)
.waitForElementVisible('@changeUsernameSubmitButton')
.click('@changeUsernameSubmitButton')
.waitForElementNotPresent('@changeUsernameInput');
},
'user should be able to comment': (client) => {
const embedStream = client.page.embedStream();
+2 -2
View File
@@ -84,7 +84,7 @@ module.exports = {
adminPage
.goToCommunity()
.goToPeople();
.goToPeople();
},
'admin removes ban from user': (client) => {
const people = client.page.admin()
@@ -96,7 +96,7 @@ module.exports = {
.waitForElementVisible('@dropdownStatus')
.click('@dropdownStatus')
.waitForElementVisible('@dropdownStatusActive')
.click('@optionActive');
.click('@optionRemoveBan');
},
'admin logs out 2': (client) => {
client.page.admin().logout();
+34 -9
View File
@@ -83,7 +83,7 @@ module.exports = {
},
'admin suspends user': (client) => {
const adminPage = client.page.admin();
const moderate = adminPage.section.moderate;
const {moderate, userDetailDrawer, suspendUserDialog} = adminPage.section;
adminPage
.navigate()
@@ -92,18 +92,22 @@ module.exports = {
moderate
.waitForElementVisible('@comment')
.waitForElementVisible('@commentActionMenu')
.waitForElementVisible('@actionMenuButton')
.waitForElementVisible('@commentUsername')
.click('@commentUsername');
userDetailDrawer
.waitForElementVisible('@actionsMenu')
.click('@actionMenuButton')
.waitForElementVisible('@actionItemSuspendUser')
.click('@actionItemSuspendUser');
adminPage
.waitForElementVisible('@suspendUserDialog')
.waitForElementVisible('@suspendUserConfirmButton')
.click('@suspendUserConfirmButton')
.waitForElementVisible('@supendUserSendButton')
.click('@supendUserSendButton');
suspendUserDialog
.waitForElementVisible('@step0')
.waitForElementVisible('@confirmButton')
.click('@confirmButton')
.waitForElementVisible('@step1')
.waitForElementVisible('@sendButton')
.click('@sendButton');
adminPage
.waitForElementVisible('@toast')
@@ -111,6 +115,27 @@ module.exports = {
.click('@toastClose');
},
'admin checks user status and ban and suspension history': (client) => {
const adminPage = client.page.admin();
const {moderate, userDetailDrawer} = adminPage.section;
moderate
.waitForElementVisible('@comment')
.waitForElementVisible('@commentUsername')
.click('@commentUsername');
userDetailDrawer
.waitForElementVisible('@tabBar')
.waitForElementVisible('@allTab')
.click('@allTab')
.waitForElementVisible('@rejectedTab')
.click('@rejectedTab')
.waitForElementVisible('@historyTab')
.click('@historyTab')
.waitForElementVisible('@historyPane')
.waitForElementVisible('@accountHistory')
.click('@closeButton');
},
'admin logs out': (client) => {
const adminPage = client.page.admin();
+1 -2
View File
@@ -26,7 +26,6 @@ beforeEach(async () => {
}));
});
after(function(done) {
after(async function() {
mongoose.disconnect();
done();
});
+3
View File
@@ -0,0 +1,3 @@
process.on('unhandledRejection', function(reason, promise) {
console.error(promise);
});
+1 -1
View File
@@ -13,7 +13,7 @@ describe('graph.Context', () => {
let c;
beforeEach(() => {
c = new Context({user: new User({id: '1', roles: ['ADMIN']})});
c = new Context({user: new User({id: '1', role: 'ADMIN'})});
});
it('creates a context with a user', (done) => {
+2 -2
View File
@@ -31,7 +31,7 @@ describe('graph.mutations.addTag', () => {
`;
it('moderators can add tags to comments', async () => {
const user = new UserModel({roles: ['MODERATOR']});
const user = new UserModel({role: 'MODERATOR'});
const context = new Context({user});
const res = await graphql(schema, query, {}, context, {id: comment.id, asset_id: asset.id, name: 'BEST'}, 'AddCommentTag');
if (res.errors && res.errors.length) {
@@ -48,7 +48,7 @@ describe('graph.mutations.addTag', () => {
Object.entries({
'anonymous': undefined,
'regular commenter': new UserModel({}),
'banned moderator': new UserModel({roles: ['MODERATOR'], status: 'BANNED'})
'banned moderator': new UserModel({role: 'MODERATOR', banned: true})
}).forEach(([ userDescription, user ]) => {
it(userDescription, async () => {
const context = new Context({user});
@@ -0,0 +1,109 @@
const {graphql} = require('graphql');
const schema = require('../../../../graph/schema');
const Context = require('../../../../graph/context');
const SettingsService = require('../../../../services/settings');
const UsersService = require('../../../../services/users');
const {expect} = require('chai');
describe('graph.mutations.changeUsername', () => {
let user;
beforeEach(async () => {
await SettingsService.init();
user = await UsersService.createLocalUser('test@test.com', 'testpassword1!', 'kirk');
expect(user).to.have.property('username', 'kirk');
expect(user).to.have.property('lowercaseUsername', 'kirk');
expect(user.status.username.status).to.equal('SET');
});
const changeUsernameMutation = `
mutation ChangeUsername($user_id: ID!, $username: String!) {
changeUsername(id: $user_id, username: $username) {
errors {
translation_key
}
}
}
query User {
me {
state {
status {
username {
history {
status
assigned_by {
username
}
created_at
}
}
}
}
}
}
`;
[
{role: 'COMMENTER'},
{role: 'STAFF'},
{role: 'COMMENTER'},
{role: 'MODERATOR'},
{role: 'ADMIN'},
].forEach(({role}) => {
it(`can change the username with roles ${role}`, async () => {
let username = 'spock';
// Update the user role.
await UsersService.setRole(user.id, role);
user.role = role;
let ctx = new Context({user});
let res = await graphql(schema, changeUsernameMutation, {}, ctx, {
user_id: user.id,
username,
}, 'ChangeUsername');
if (res.errors && res.errors.length > 0) {
console.error(res.errors);
}
expect(res.errors).to.be.undefined;
expect(res.data.changeUsername).to.have.property('errors');
expect(res.data.changeUsername.errors).to.have.length(1);
expect(res.data.changeUsername.errors[0]).to.have.property('translation_key', 'NOT_AUTHORIZED');
// Set the user to the desired status.
user = await UsersService.setUsernameStatus(user.id, 'REJECTED');
expect(user.status.username.status, 'REJECTED');
ctx = new Context({user});
res = await graphql(schema, changeUsernameMutation, {}, ctx, {
user_id: user.id,
username,
}, 'ChangeUsername');
if (res.errors && res.errors.length > 0) {
console.error(res.errors);
}
expect(res.errors).to.be.undefined;
expect(res.data.changeUsername).to.be.null;
res = await graphql(schema, changeUsernameMutation, {}, ctx, {}, 'User');
if (res.errors && res.errors.length > 0) {
console.error(res.errors);
}
expect(res.errors).to.be.undefined;
expect(res.data.me.state.status.username.status, 'CHANGED');
});
});
});
+61 -68
View File
@@ -71,27 +71,28 @@ describe('graph.mutations.createComment', () => {
beforeEach(() => AssetModel.create({id: '123'}));
[
{user: new UserModel({status: 'ACTIVE'}), error: null},
{user: new UserModel({status: 'BANNED'}), error: 'NOT_AUTHORIZED'},
{user: new UserModel({status: 'PENDING'}), error: null},
{user: new UserModel({status: 'APPROVED'}), error: null}
{user: new UserModel({}), error: null},
{user: new UserModel({banned: true}), error: 'NOT_AUTHORIZED'},
{user: new UserModel({suspended: new Date((new Date()).getTime() - (10 * 86400000))}), error: null},
{user: new UserModel({suspended: new Date((new Date()).getTime() + (10 * 86400000))}), error: 'NOT_AUTHORIZED'},
].forEach(({user, error}) => {
describe(`user.status=${user.status}`, () => {
it(error ? 'does not create the comment' : 'creates the comment', () => {
describe(`user.banned=${user.banned} user.suspended=${user.suspended}`, () => {
it(error ? 'does not create the comment' : 'creates the comment', async () => {
const context = new Context({user});
const {data, errors} = await graphql(schema, query, {}, context);
return graphql(schema, query, {}, context)
.then(({data, errors}) => {
expect(errors).to.be.undefined;
if (error) {
expect(data.createComment).to.have.property('comment').null;
expect(data.createComment).to.have.property('errors').not.null;
expect(data.createComment.errors[0]).to.have.property('translation_key', error);
} else {
expect(data.createComment).to.have.property('comment').not.null;
expect(data.createComment).to.have.property('errors').null;
}
});
expect(errors).to.be.undefined;
if (error) {
expect(data.createComment).to.have.property('comment').null;
expect(data.createComment).to.have.property('errors').not.null;
expect(data.createComment.errors[0]).to.have.property('translation_key', error);
} else {
if (data.createComment.errors && data.createComment.errors.length > 0) {
console.error(data.createComment.errors);
}
expect(data.createComment).to.have.property('errors').null;
expect(data.createComment).to.have.property('comment').not.null;
}
});
});
});
@@ -109,7 +110,7 @@ describe('graph.mutations.createComment', () => {
beforeEach(() => asset.save());
it(error ? 'does not create the comment' : 'creates the comment', () => {
const context = new Context({user: new UserModel({status: 'ACTIVE'})});
const context = new Context({user: new UserModel({})});
return graphql(schema, query, {}, context)
.then(({data, errors}) => {
@@ -142,7 +143,7 @@ describe('graph.mutations.createComment', () => {
beforeEach(() => AssetModel.create({id: '123', settings: {moderation}}));
it(`creates comment with status=${status}`, () => {
const context = new Context({user: new UserModel({status: 'ACTIVE'})});
const context = new Context({user: new UserModel()});
return graphql(schema, query, {}, context)
.then(({data, errors}) => {
@@ -172,33 +173,30 @@ describe('graph.mutations.createComment', () => {
].forEach(({message, body, status, flagged}) => {
describe(message, () => {
it(`should create a comment with status=${status} and it ${flagged ? 'should' : 'should not'} be flagged`, () => {
const context = new Context({user: new UserModel({status: 'ACTIVE'})});
it(`should create a comment with status=${status} and it ${flagged ? 'should' : 'should not'} be flagged`, async () => {
const context = new Context({user: new UserModel({})});
return graphql(schema, query, {}, context, {
const {data, errors} = await graphql(schema, query, {}, context, {
input: {
asset_id: '123',
body
}
})
.then(({data, errors}) => {
expect(errors).to.be.undefined;
expect(data.createComment).to.have.property('comment').not.null;
expect(data.createComment.comment).to.have.property('status', status);
expect(data.createComment).to.have.property('errors').null;
});
return ActionModel.find({
item_id: data.createComment.comment.id,
action_type: 'FLAG'
});
})
.then((actions) => {
if (flagged) {
expect(actions).to.have.length(1);
} else {
expect(actions).to.have.length(0);
}
});
expect(errors).to.be.undefined;
expect(data.createComment).to.have.property('comment').not.null;
expect(data.createComment.comment).to.have.property('status', status);
expect(data.createComment).to.have.property('errors').null;
const actions = await ActionModel.find({
item_id: data.createComment.comment.id,
action_type: 'FLAG'
});
if (flagged) {
expect(actions).to.have.length(1);
} else {
expect(actions).to.have.length(0);
}
});
});
@@ -210,37 +208,32 @@ describe('graph.mutations.createComment', () => {
beforeEach(() => AssetModel.create({id: '123'}));
[
{roles: [], tag: null},
{roles: ['ADMIN'], tag: 'STAFF'},
{roles: ['MODERATOR'], tag: 'STAFF'},
{roles: ['ADMIN', 'MODERATOR'], tag: 'STAFF'}
].forEach(({roles, tag}) => {
describe(`user.roles=${JSON.stringify(roles)}`, () => {
{role: 'COMMENTER', tag: null},
{role: 'ADMIN', tag: 'STAFF'},
{role: 'MODERATOR', tag: 'STAFF'},
].forEach(({role, tag}) => {
describe(`user.role=${JSON.stringify(role)}`, () => {
it(`creates comment ${tag ? `with tag=${tag}` : 'without tags'}`, () => {
const context = new Context({user: new UserModel({roles})});
it(`creates comment ${tag ? `with tag=${tag}` : 'without tags'}`, async () => {
const context = new Context({user: new UserModel({role})});
return graphql(schema, query, {}, context)
.then(({data, errors}) => {
if (errors) {
console.error(errors);
}
expect(errors).to.be.undefined;
expect(data.createComment).to.have.property('comment').not.null;
expect(data.createComment).to.have.property('errors').null;
const {data, errors} = await graphql(schema, query, {}, context);
return CommentsService.findById(data.createComment.comment.id);
})
.then(({tags}) => {
if (tag) {
expect(tags).to.have.length(1);
expect(tags[0].tag.name).to.have.equal(tag);
} else {
expect(tags).length(0);
}
});
if (errors) {
console.error(errors);
}
expect(errors).to.be.undefined;
expect(data.createComment).to.have.property('comment').not.null;
expect(data.createComment).to.have.property('errors').null;
const {tags} = await CommentsService.findById(data.createComment.comment.id);
if (tag) {
expect(tags).to.have.length(1);
expect(tags[0].tag.name).to.have.equal(tag);
} else {
expect(tags).length(0);
}
});
});
});
+2 -2
View File
@@ -34,7 +34,7 @@ describe('graph.mutations.removeTag', () => {
`;
it('moderators can add remove tags from comments', async () => {
const user = new UserModel({roles: ['MODERATOR']});
const user = new UserModel({role: 'MODERATOR'});
const context = new Context({user});
// add a tag first
@@ -66,7 +66,7 @@ describe('graph.mutations.removeTag', () => {
Object.entries({
'anonymous': undefined,
'regular commenter': new UserModel({}),
'banned moderator': new UserModel({roles: ['MODERATOR'], status: 'BANNED'})
'banned moderator': new UserModel({role: 'MODERATOR', banned: true})
}).forEach(([userDescription, user]) => {
it(userDescription, async function () {
const context = new Context({user});
@@ -0,0 +1,132 @@
const {graphql} = require('graphql');
const schema = require('../../../../graph/schema');
const Context = require('../../../../graph/context');
const SettingsService = require('../../../../services/settings');
const UserModel = require('../../../../models/user');
const UsersService = require('../../../../services/users');
const mailer = require('../../../../services/mailer');
const sinon = require('sinon');
const chai = require('chai');
chai.use(require('sinon-chai'));
const {expect} = chai;
describe('graph.mutations.banUser', () => {
let user;
beforeEach(async () => {
await SettingsService.init();
user = await UsersService.createLocalUser('usernameA@example.com', 'password', 'usernameA');
});
let spy;
before(() => {
spy = sinon.spy(mailer, 'send');
});
afterEach(() => {
spy.reset();
});
after(() => {
spy.restore();
});
const banUserMutation = `
mutation BanUser($user_id: ID!, $message: String!) {
banUser(input: {
id: $user_id,
message: $message
}) {
errors {
translation_key
}
}
}
mutation UnBanUser($user_id: ID!) {
unbanUser(input: {
id: $user_id
}) {
errors {
translation_key
}
}
}
`;
[
{self: true, error: 'NOT_AUTHORIZED', role: 'COMMENTER'},
{self: true, error: 'NOT_AUTHORIZED', role: 'STAFF'},
{self: true, error: 'NOT_AUTHORIZED', role: 'COMMENTER'},
{error: 'NOT_AUTHORIZED', role: 'COMMENTER'},
{error: 'NOT_AUTHORIZED', role: 'STAFF'},
{error: 'NOT_AUTHORIZED', role: 'COMMENTER'},
{error: false, role: 'MODERATOR'},
{error: false, role: 'ADMIN'},
].forEach(({self, error, role}) => {
it(`${error ? 'can not' : 'can'} ban ${self ? 'themself' : 'another user'} as a user with role=${role}`, async () => {
const actor = new UserModel({role});
// If we're testing self assign, set the id of the actor to the user
// we're acting on.
if (self) {
actor.id = user.id;
}
const ctx = new Context({user: actor});
const {data, errors} = await graphql(schema, banUserMutation, {}, ctx, {
user_id: user.id,
message: 'This is a message'
}, 'BanUser');
if (errors && errors.length > 0) {
console.error(errors);
}
expect(errors).to.be.undefined;
if (error) {
expect(data.banUser).to.have.property('errors').not.null;
expect(data.banUser.errors[0]).to.have.property('translation_key', error);
} else {
expect(data.banUser).to.be.null;
user = await UserModel.findOne({id: user.id});
expect(user.status.banned.status).to.be.true;
expect(user.status.banned.history).to.have.length(1);
expect(user.status.banned.history[0]).to.have.property('status', true);
expect(user.status.banned.history[0]).to.have.property('message', 'This is a message');
expect(user.status.banned.history[0]).to.have.property('assigned_by', actor.id);
expect(user.status.banned.history[0]).to.have.property('created_at').not.null;
expect(user.banned).to.be.true;
expect(spy).to.have.been.calledOnce;
const res = await graphql(schema, banUserMutation, {}, ctx, {
user_id: user.id,
}, 'UnBanUser');
if (res.errors && res.errors.length > 0) {
console.error(res.errors);
}
expect(res.errors).to.be.undefined;
expect(res.data.unbanUser).to.be.null;
user = await UserModel.findOne({id: user.id});
expect(user.status.banned.status).to.be.false;
expect(user.status.banned.history).to.have.length(2);
expect(user.status.banned.history[0]).to.have.property('status').to.be.true;
expect(user.status.banned.history[0]).to.have.property('assigned_by', actor.id);
expect(user.status.banned.history[0]).to.have.property('created_at').not.null;
expect(user.status.banned.history[1]).to.have.property('status').to.be.false;
expect(user.status.banned.history[1]).to.have.property('assigned_by', actor.id);
expect(user.status.banned.history[1]).to.have.property('created_at').not.null;
expect(user.banned).to.be.false;
}
});
});
});
@@ -0,0 +1,145 @@
const {graphql} = require('graphql');
const timekeeper = require('timekeeper');
const schema = require('../../../../graph/schema');
const Context = require('../../../../graph/context');
const SettingsService = require('../../../../services/settings');
const UserModel = require('../../../../models/user');
const UsersService = require('../../../../services/users');
const mailer = require('../../../../services/mailer');
const sinon = require('sinon');
const chai = require('chai');
chai.use(require('chai-datetime'));
chai.use(require('sinon-chai'));
const {expect} = chai;
describe('graph.mutations.suspendUser', () => {
let user;
beforeEach(async () => {
await SettingsService.init();
user = await UsersService.createLocalUser('usernameA@example.com', 'password', 'usernameA');
});
let spy;
before(() => {
spy = sinon.spy(mailer, 'send');
});
afterEach(() => {
spy.reset();
});
after(() => {
spy.restore();
});
const mutation = `
mutation SuspendUser($user_id: ID!, $until: Date!, $message: String!) {
suspendUser(input: {
id: $user_id,
until: $until,
message: $message,
}) {
errors {
translation_key
}
}
}
mutation UnSuspendUser($user_id: ID!) {
unsuspendUser(input: {
id: $user_id,
}) {
errors {
translation_key
}
}
}
`;
[
{self: true, error: 'NOT_AUTHORIZED', role: 'COMMENTER'},
{self: true, error: 'NOT_AUTHORIZED', role: 'STAFF'},
{self: true, error: 'NOT_AUTHORIZED', role: 'COMMENTER'},
{error: 'NOT_AUTHORIZED', role: 'COMMENTER'},
{error: 'NOT_AUTHORIZED', role: 'STAFF'},
{error: 'NOT_AUTHORIZED', role: 'COMMENTER'},
{error: false, role: 'MODERATOR'},
{error: false, role: 'ADMIN'},
].forEach(({self, error, role}) => {
it(`${error ? 'can not' : 'can'} suspend ${self ? 'themself' : 'another user'} as a user with role ${role}`, async () => {
const actor = new UserModel({role});
// If we're testing self assign, set the id of the actor to the user
// we're acting on.
if (self) {
actor.id = user.id;
}
const ctx = new Context({user: actor});
const now = new Date();
const oneHourFromNow = new Date(new Date(now).setHours(now.getHours() + 1));
const {data, errors} = await graphql(schema, mutation, {}, ctx, {
user_id: user.id,
until: oneHourFromNow,
message: 'This is a message'
}, 'SuspendUser');
if (errors && errors.length > 0) {
console.error(errors);
}
expect(errors).to.be.undefined;
if (error) {
expect(data.suspendUser).to.have.property('errors').not.null;
expect(data.suspendUser.errors[0]).to.have.property('translation_key', error);
} else {
expect(data.suspendUser).to.be.null;
user = await UserModel.findOne({id: user.id});
// Mongoose messes with the date, check within a 2 second window.
expect(user.status.suspension.until).to.be.withinTime(new Date(oneHourFromNow.getTime() - 1000), new Date(oneHourFromNow.getTime() + 1000));
expect(user.status.suspension.history).to.have.length(1);
expect(user.status.suspension.history[0]).to.have.property('until').to.be.withinTime(new Date(oneHourFromNow.getTime() - 1000), new Date(oneHourFromNow.getTime() + 1000));
expect(user.status.suspension.history[0]).to.have.property('assigned_by', actor.id);
expect(user.status.suspension.history[0]).to.have.property('message', 'This is a message');
expect(user.status.suspension.history[0]).to.have.property('created_at').not.null;
expect(user.suspended).to.be.true;
timekeeper.travel(new Date(oneHourFromNow.getTime() + 10000));
expect(user.suspended).to.be.false;
timekeeper.reset();
expect(spy).to.have.been.calledOnce;
const res = await graphql(schema, mutation, {}, ctx, {
user_id: user.id,
until: null
}, 'UnSuspendUser');
if (res.errors && res.errors.length > 0) {
console.error(res.errors);
}
expect(res.errors).to.be.undefined;
expect(res.data.unsuspendUser).to.be.null;
user = await UserModel.findOne({id: user.id});
// Mongoose messes with the date, check within a 2 second window.
expect(user.status.suspension.until).to.be.null;
expect(user.status.suspension.history).to.have.length(2);
expect(user.status.suspension.history[0]).to.have.property('until').to.be.withinTime(new Date(oneHourFromNow.getTime() - 1000), new Date(oneHourFromNow.getTime() + 1000));
expect(user.status.suspension.history[0]).to.have.property('assigned_by', actor.id);
expect(user.status.suspension.history[0]).to.have.property('created_at').not.null;
expect(user.status.suspension.history[1]).to.have.property('until').to.be.null;
expect(user.status.suspension.history[1]).to.have.property('assigned_by', actor.id);
expect(user.status.suspension.history[1]).to.have.property('created_at').not.null;
expect(user.suspended).to.be.false;
}
});
});
});
@@ -0,0 +1,86 @@
const {graphql} = require('graphql');
const schema = require('../../../../graph/schema');
const Context = require('../../../../graph/context');
const SettingsService = require('../../../../services/settings');
const UserModel = require('../../../../models/user');
const UsersService = require('../../../../services/users');
const chai = require('chai');
chai.use(require('chai-datetime'));
const {expect} = chai;
[
{status: 'APPROVED', name: 'approve', mutation: 'approveUsername'},
{status: 'REJECTED', name: 'reject', mutation: 'rejectUsername'}
].forEach(({status, name, mutation}) => {
describe(`graph.mutations.${mutation}`, () => {
let user;
beforeEach(async () => {
await SettingsService.init();
user = await UsersService.createLocalUser('usernameA@example.com', 'password', 'usernameA');
});
const setUserUsernameStatusMutation = `
mutation SetUserUsernameStatus($user_id: ID!) {
${mutation}(id: $user_id) {
errors {
translation_key
}
}
}
`;
[
{self: true, error: 'NOT_AUTHORIZED', role: 'COMMENTER'},
{self: true, error: 'NOT_AUTHORIZED', role: 'STAFF'},
{self: true, error: 'NOT_AUTHORIZED', role: 'COMMENTER'},
{error: 'NOT_AUTHORIZED', role: 'COMMENTER'},
{error: 'NOT_AUTHORIZED', role: 'STAFF'},
{error: 'NOT_AUTHORIZED', role: 'COMMENTER'},
{error: false, role: 'MODERATOR'},
{error: false, role: 'ADMIN'},
].forEach(({self, error, role}) => {
it(`${error ? 'can not' : 'can'} ${name} a username with the user role ${role}${self ? ' on themself' : ''}`, async () => {
const actor = new UserModel({role});
// If we're testing self assign, set the id of the actor to the user
// we're acting on.
if (self) {
actor.id = user.id;
}
const ctx = new Context({user: actor});
const {data, errors} = await graphql(schema, setUserUsernameStatusMutation, {}, ctx, {
user_id: user.id,
});
if (errors && errors.length > 0) {
console.error(errors);
}
expect(errors).to.be.undefined;
if (error) {
expect(data[mutation]).to.have.property('errors').not.null;
expect(data[mutation].errors[0]).to.have.property('translation_key', error);
} else {
expect(data[mutation]).to.be.null;
user = await UserModel.findOne({id: user.id});
expect(user.status.username.status).to.equal(status);
expect(user.status.username.history).to.have.length(2);
expect(user.status.username.history[0]).to.have.property('status', 'SET');
expect(user.status.username.history[0]).to.have.property('assigned_by').is.null;
expect(user.status.username.history[0]).to.have.property('created_at').not.null;
expect(user.status.username.history[1]).to.have.property('status', status);
expect(user.status.username.history[1]).to.have.property('assigned_by', actor.id);
expect(user.status.username.history[1]).to.have.property('created_at').not.null;
expect(user.status.username.history[1].created_at).afterTime(user.status.username.history[0].created_at);
}
});
});
});
});
@@ -22,18 +22,18 @@ describe('graph.mutations.updateAssetSettings', () => {
translation_key
}
}
}
`;
}`;
describe('context with different user roles', () => {
[
{error: 'NOT_AUTHORIZED'},
{roles: ['ADMIN', 'MODERATOR']},
{roles: ['MODERATOR']},
].forEach(({roles, error}) => {
it(roles ? roles.join(', ') : '<None>', async () => {
const user = new UserModel({roles});
{role: 'COMMENTER', error: 'NOT_AUTHORIZED'},
{role: 'STAFF', error: 'NOT_AUTHORIZED'},
{role: 'ADMIN'},
{role: 'MODERATOR'},
].forEach(({role, error}) => {
it(`role = ${role}`, async () => {
const user = new UserModel({role});
const ctx = new Context({user});
const settings = {
@@ -28,12 +28,12 @@ describe('graph.mutations.updateAssetStatus', () => {
describe('context with different user roles', () => {
[
{error: 'NOT_AUTHORIZED'},
{roles: ['ADMIN', 'MODERATOR']},
{roles: ['MODERATOR']},
].forEach(({roles, error}) => {
it(roles ? roles.join(', ') : '<None>', async () => {
const user = new UserModel({roles});
{role: 'COMMENTER', error: 'NOT_AUTHORIZED'},
{role: 'ADMIN'},
{role: 'MODERATOR'},
].forEach(({role, error}) => {
it(`role = ${role}`, async () => {
const user = new UserModel({role});
const ctx = new Context({user});
const closedAt = (new Date()).toISOString();
+8 -14
View File
@@ -21,23 +21,17 @@ describe('graph.mutations.updateSettings', () => {
translation_key
}
}
}
`;
}`;
describe('context with different user roles', () => {
[
{error: 'NOT_AUTHORIZED'},
{error: 'NOT_AUTHORIZED', roles: []},
{roles: ['ADMIN']},
{roles: ['ADMIN', 'MODERATOR']},
{roles: ['MODERATOR']},
].forEach(({roles, error}) => {
it(roles ? roles.join(', ') : '<None>', async () => {
let user;
if (roles != null) {
user = new UserModel({roles});
}
{error: 'NOT_AUTHORIZED', role: 'COMMENTER'},
{role: 'ADMIN'},
{role: 'MODERATOR'},
].forEach(({role, error}) => {
it(`role = ${role}`, async () => {
const user = new UserModel({role});
const ctx = new Context({user});
const newSettings = {
@@ -75,7 +69,7 @@ describe('graph.mutations.updateSettings', () => {
});
describe('nested objects', () => {
const user = new UserModel({roles: ['ADMIN']});
const user = new UserModel({role: 'ADMIN'});
const ctx = new Context({user});
it('should handle nested objects', async () => {
+6 -12
View File
@@ -56,18 +56,12 @@ describe('graph.queries.settings', () => {
];
[
{bl: true},
{bl: true, roles: []},
{bl: false, roles: ['ADMIN']},
{bl: false, roles: ['ADMIN', 'MODERATOR']},
{bl: false, roles: ['MODERATOR']},
].forEach(({bl, roles}) => {
it(roles && roles.length > 0 ? roles.join(', ') : '<None>', async () => {
let user;
if (roles != null) {
user = new UserModel({roles});
}
{bl: true, role: 'COMMENTER'},
{bl: false, role: 'ADMIN'},
{bl: false, role: 'MODERATOR'},
].forEach(({bl, role}) => {
it(`role = ${role}`, async () => {
let user = new UserModel({role});
const ctx = new Context({user});
const res = await graphql(schema, QUERY, {}, ctx);
+87
View File
@@ -0,0 +1,87 @@
const {graphql} = require('graphql');
const schema = require('../../../../graph/schema');
const Context = require('../../../../graph/context');
const SettingsService = require('../../../../services/settings');
const UserModel = require('../../../../models/user');
const UsersService = require('../../../../services/users');
const chai = require('chai');
chai.use(require('chai-datetime'));
const {expect} = chai;
describe('graph.queries.user', () => {
let user;
beforeEach(async () => {
await SettingsService.init();
user = await UsersService.createLocalUser('usernameA@example.com', 'password', 'usernameA');
});
describe('state', () => {
const meQuery = `
query Me {
me {
state {
status {
username {
status
}
}
}
}
}
`;
it('can query me', async () => {
const ctx = new Context({user});
const {data, errors} = await graphql(schema, meQuery, {}, ctx);
expect(errors).to.be.undefined;
expect(data.me).to.not.be.null;
expect(data.me.state).not.to.be.null;
expect(data.me.state.status.username.status).to.be.equal('SET');
});
const query = `
query User($user_id: ID!) {
user(id: $user_id) {
state {
status {
username {
status
}
}
}
}
}
`;
[
{role: 'COMMENTER', can: false},
{role: 'STAFF', can: false},
{role: 'MODERATOR', can: true},
{role: 'ADMIN', can: true},
].forEach(({role, can}) => {
it(`${can ? 'can' : 'can not'} query with role = ${role}`, async () => {
const actor = new UserModel({role});
const ctx = new Context({user: actor});
const {data, errors} = await graphql(schema, query, {}, ctx, {
user_id: user.id,
});
expect(errors).to.be.undefined;
if (!can) {
expect(data.user).to.be.null;
} else {
expect(data.user).to.not.be.null;
expect(data.user.state).not.to.be.null;
expect(data.user.state.status.username.status).to.be.equal('SET');
}
});
});
});
});
+6 -6
View File
@@ -6,13 +6,13 @@ const authz = require('../../../middleware/authorization');
describe('middleware.authorization', () => {
describe('#has', () => {
it('allows if no roles are specified', () => {
expect(authz.has({roles: []})).to.be.true;
expect(authz.has({role: 'COMMENTER'})).to.be.true;
});
it('allows if the correct roles are met', () => {
expect(authz.has({roles: ['ADMIN']}, 'ADMIN', 'MODERATOR')).to.be.true;
expect(authz.has({role: 'ADMIN'}, 'ADMIN', 'MODERATOR')).to.be.true;
});
it('disallows if the role required is missing', () => {
expect(authz.has({roles: []}, 'ADMIN', 'MODERATOR')).to.be.false;
expect(authz.has({role: 'COMMENTER'}, 'ADMIN', 'MODERATOR')).to.be.false;
});
});
@@ -24,17 +24,17 @@ describe('middleware.authorization', () => {
};
it('allows if no roles are specified', () => {
needed()({user: {roles: []}}, {}, (err) => {
needed()({user: {role: 'COMMENTER'}}, {}, (err) => {
expect(err).to.be.undefined;
});
});
it('allows if the correct roles are met', () => {
needed()({user: {roles: ['ADMIN']}}, {}, (err) => {
needed()({user: {role: 'ADMIN'}}, {}, (err) => {
expect(err).to.be.undefined;
});
});
it('disallows if the role required is missing', () => {
needed('ADMIN', 'MODERATOR')({user: {roles: []}}, {}, (err) => {
needed('ADMIN', 'MODERATOR')({user: {role: 'COMMENTER'}}, {}, (err) => {
expect(err).to.not.be.undefined;
});
});
@@ -0,0 +1,203 @@
const migration = require('../../../migrations/1510174676_user_status');
const UserModel = require('../../../models/user');
const chai = require('chai');
chai.use(require('chai-datetime'));
const {expect} = chai;
describe('migration.1510174676_user_status', () => {
describe('active user', () => {
beforeEach(async () => {
await UserModel.collection.insert({
id: '123',
username: 'Kirk',
lowercaseUsername: 'kirk',
status: 'ACTIVE',
canEditName: false
});
});
it('completes the migration', async () => {
let user = await UserModel.collection.findOne({id: '123'});
expect(user).to.have.property('status', 'ACTIVE');
expect(user).to.have.property('canEditName', false);
// Perform the migration.
await migration.up();
user = await UserModel.collection.findOne({id: '123'});
// Check that it was correct.
expect(user).to.have.property('status');
expect(user.status).to.have.property('username');
expect(user.status.username).to.have.property('status', 'SET');
expect(user.status.username.history).to.have.length(1);
});
});
describe('social user', () => {
beforeEach(async () => {
await UserModel.collection.insert({
id: '123',
username: 'Kirk',
lowercaseUsername: 'kirk',
status: 'ACTIVE',
canEditName: true
});
});
it('completes the migration', async () => {
let user = await UserModel.collection.findOne({id: '123'});
expect(user).to.have.property('status', 'ACTIVE');
expect(user).to.have.property('canEditName', true);
// Perform the migration.
await migration.up();
user = await UserModel.collection.findOne({id: '123'});
// Check that it was correct.
expect(user).to.have.property('status');
expect(user.status).to.have.property('username');
expect(user.status.username).to.have.property('status', 'UNSET');
expect(user.status.username.history).to.have.length(1);
});
});
describe('rejected username', () => {
beforeEach(async () => {
await UserModel.collection.insert({
id: '123',
username: 'Kirk',
lowercaseUsername: 'kirk',
status: 'BANNED',
canEditName: true
});
});
it('completes the migration', async () => {
let user = await UserModel.collection.findOne({id: '123'});
expect(user).to.have.property('status');
expect(user.status).to.equal('BANNED');
expect(user.canEditName).to.equal(true);
// Perform the migration.
await migration.up();
user = await UserModel.collection.findOne({id: '123'});
// Check that it was correct.
expect(user).to.have.property('status');
expect(user.status).to.have.property('banned');
expect(user.status.banned).to.have.property('status', false);
expect(user.status.username).to.have.property('status', 'REJECTED');
expect(user.status.username.history).to.have.length(1);
});
});
describe('approved username', () => {
beforeEach(async () => {
await UserModel.collection.insert({
id: '123',
username: 'Kirk',
lowercaseUsername: 'kirk',
status: 'APPROVED',
canEditName: false
});
});
it('completes the migration', async () => {
let user = await UserModel.collection.findOne({id: '123'});
expect(user).to.have.property('status');
expect(user.status).to.equal('APPROVED');
expect(user.canEditName).to.equal(false);
// Perform the migration.
await migration.up();
user = await UserModel.collection.findOne({id: '123'});
// Check that it was correct.
expect(user).to.have.property('status');
expect(user.status).to.have.property('banned');
expect(user.status.banned).to.have.property('status', false);
expect(user.status.username).to.have.property('status', 'APPROVED');
expect(user.status.username.history).to.have.length(1);
});
});
describe('suspended user', () => {
beforeEach(async () => {
await UserModel.collection.insert({
id: '123',
username: 'Kirk',
lowercaseUsername: 'kirk',
status: 'ACTIVE',
suspension: {
until: new Date()
}
});
});
it('completes the migration', async () => {
let user = await UserModel.collection.findOne({id: '123'});
expect(user).to.have.property('suspension');
expect(user.suspension).to.have.property('until');
expect(user.suspension.until).to.not.be.null;
const until = user.suspension.until;
// Perform the migration.
await migration.up();
user = await UserModel.collection.findOne({id: '123'});
// Check that it was correct.
expect(user).to.have.property('status');
expect(user.status).to.have.property('suspension');
expect(user.status.suspension).to.have.property('until');
expect(user.status.suspension.until).to.not.be.null;
expect(user.status.suspension.until).to.be.withinTime(new Date(until.getTime() - 1000), new Date(until.getTime() + 1000));
});
});
describe('banned user', () => {
beforeEach(async () => {
await UserModel.collection.insert({
id: '123',
username: 'Kirk',
lowercaseUsername: 'kirk',
status: 'BANNED',
canEditName: false
});
});
it('completes the migration', async () => {
let user = await UserModel.collection.findOne({id: '123'});
expect(user).to.have.property('status');
expect(user.status).to.equal('BANNED');
// Perform the migration.
await migration.up();
user = await UserModel.collection.findOne({id: '123'});
// Check that it was correct.
expect(user).to.have.property('status');
expect(user.status).to.have.property('banned');
expect(user.status.banned).to.have.property('status', true);
});
});
});
-57
View File
@@ -1,57 +0,0 @@
const passport = require('../../../passport');
const app = require('../../../../../app');
const UsersService = require('../../../../../services/users');
const SettingsService = require('../../../../../services/settings');
const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['bad words'], suspect: ['suspect words']}};
const chai = require('chai');
chai.should();
chai.use(require('chai-http'));
const expect = chai.expect;
describe('/api/v1/account/username', () => {
let mockUser;
beforeEach(async () => {
await SettingsService.init(settings);
mockUser = await UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana');
});
describe('#put', () => {
it('it should enable a user to edit their username if canEditName is enabled', async () => {
await chai.request(app)
.post(`/api/v1/users/${mockUser.id}/username-enable`)
.set(passport.inject({id: '456', roles: ['ADMIN']}));
const res = await chai.request(app)
.put('/api/v1/account/username')
.set(passport.inject({id: mockUser.id, roles: []}))
.send({username: 'MojoJojo'});
expect(res).to.have.status(204);
});
it('it should return an error if the wrong user tries to edit a username', async () => {
await chai.request(app)
.post(`/api/v1/users/${mockUser.id}/username-enable`)
.set(passport.inject({id: '456', roles: ['ADMIN']}));
let res = chai.request(app)
.put('/api/v1/account/username')
.set(passport.inject({id: 'wrongid', roles: []}))
.send({username: 'MojoJojo'});
return expect(res).to.eventually.be.rejected;
});
it('it should return an error when the user tries to edit their username if canEditName is disabled', () => {
let res = chai.request(app)
.put('/api/v1/account/username')
.set(passport.inject({id: mockUser.id, roles: []}))
.send({username: 'MojoJojo'});
return expect(res).to.eventually.be.rejected;
});
});
});
+7 -7
View File
@@ -40,7 +40,7 @@ describe('/api/v1/assets', () => {
for (const role of ['ADMIN', 'MODERATOR']) {
const res = await chai.request(app)
.get('/api/v1/assets')
.set(passport.inject({roles: [role]}));
.set(passport.inject({role}));
const body = res.body;
@@ -57,7 +57,7 @@ describe('/api/v1/assets', () => {
for (const role of ['ADMIN', 'MODERATOR']) {
const res = await chai.request(app)
.get('/api/v1/assets?value=term2')
.set(passport.inject({roles: [role]}));
.set(passport.inject({role}));
const body = res.body;
@@ -79,7 +79,7 @@ describe('/api/v1/assets', () => {
for (const role of ['ADMIN', 'MODERATOR']) {
const res = await chai.request(app)
.get('/api/v1/assets?value=term3')
.set(passport.inject({roles: [role]}));
.set(passport.inject({role}));
const body = res.body;
expect(body).to.have.property('count', 0);
@@ -93,7 +93,7 @@ describe('/api/v1/assets', () => {
for (const role of ['ADMIN', 'MODERATOR']) {
const res = await chai.request(app)
.get('/api/v1/assets?filter=closed')
.set(passport.inject({roles: [role]}));
.set(passport.inject({role}));
const body = res.body;
expect(body).to.have.property('count', 1);
@@ -109,7 +109,7 @@ describe('/api/v1/assets', () => {
for (const role of ['ADMIN', 'MODERATOR']) {
const res = await chai.request(app)
.get('/api/v1/assets?filter=open')
.set(passport.inject({roles: [role]}));
.set(passport.inject({role}));
const body = res.body;
expect(body).to.have.property('count', 1);
@@ -134,7 +134,7 @@ describe('/api/v1/assets', () => {
const res = await chai.request(app)
.put(`/api/v1/assets/${asset.id}/status`)
.set(passport.inject({roles: ['ADMIN']}))
.set(passport.inject({role: 'ADMIN'}))
.send({closedAt: today});
expect(res).to.have.status(204);
@@ -153,7 +153,7 @@ describe('/api/v1/assets', () => {
const promise = chai.request(app)
.put(`/api/v1/assets/${asset.id}/status`)
.set(passport.inject({roles: ['MODERATOR']}))
.set(passport.inject({role: 'MODERATOR'}))
.send({closedAt: today});
await expect(promise).to.eventually.be.rejected;
});
+3 -5
View File
@@ -20,9 +20,7 @@ describe('/api/v1/settings', () => {
for (let role of ['ADMIN', 'MODERATOR']) {
const res = await chai.request(app)
.get('/api/v1/settings')
.set(passport.inject({
roles: [role]
}));
.set(passport.inject({role}));
expect(res).to.have.status(200);
expect(res).to.be.json;
expect(res.body).to.have.property('moderation', 'PRE');
@@ -35,7 +33,7 @@ describe('/api/v1/settings', () => {
it('should update the settings', () => {
return chai.request(app)
.put('/api/v1/settings')
.set(passport.inject({roles: ['ADMIN']}))
.set(passport.inject({role: 'ADMIN'}))
.send({moderation: 'POST'})
.then((res) => {
expect(res).to.have.status(204);
@@ -50,7 +48,7 @@ describe('/api/v1/settings', () => {
it('should require ADMIN role', () => {
const promise = chai.request(app)
.put('/api/v1/settings')
.set(passport.inject({roles: ['MODERATOR']}))
.set(passport.inject({role: 'MODERATOR'}))
.send({moderation: 'POST'});
return expect(promise).to.eventually.be.rejected;
});
+2 -51
View File
@@ -30,7 +30,7 @@ describe('/api/v1/users/:user_id/email/confirm', () => {
return chai.request(app)
.post(`/api/v1/users/${mockUser.id}/email/confirm`)
.set(passport.inject({roles: ['ADMIN']}))
.set(passport.inject({role: 'ADMIN'}))
.then((res) => {
expect(res).to.have.status(204);
expect(mailer.task.tasks).to.have.length(1);
@@ -40,7 +40,7 @@ describe('/api/v1/users/:user_id/email/confirm', () => {
it('should send a 404 on not matching a user', () => {
return chai.request(app)
.post(`/api/v1/users/${mockUser.id}/email/confirm`)
.set(passport.inject({roles: ['ADMIN']}))
.set(passport.inject({role: 'ADMIN'}))
.then((res) => {
expect(res).to.have.status(204);
expect(mailer.task.tasks).to.have.length(1);
@@ -48,52 +48,3 @@ describe('/api/v1/users/:user_id/email/confirm', () => {
});
});
});
describe('/api/v1/users/:user_id/actions', () => {
let mockUser;
beforeEach(() => SettingsService.init(settings).then(() => {
return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana');
})
.then((user) => {
mockUser = user;
}));
describe('#post', () => {
it('it should update actions', () => {
return chai.request(app)
.post(`/api/v1/users/${mockUser.id}/actions`)
.set(passport.inject({id: '456', roles: ['ADMIN']}))
.send({'action_type': 'FLAG', metadata: {reason: 'Bio is too awesome.'}})
.then((res) => {
expect(res).to.have.status(201);
expect(res).to.have.body;
expect(res.body).to.have.property('action_type', 'FLAG');
expect(res.body).to.have.property('item_id', mockUser.id);
});
});
});
});
describe('/api/v1/users/:user_id/username-enable', () => {
let mockUser;
beforeEach(() => SettingsService.init(settings).then(() => {
return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana');
})
.then((user) => {
mockUser = user;
}));
describe('#post', () => {
it('it should enable a user to edit their username', () => {
return chai.request(app)
.post(`/api/v1/users/${mockUser.id}/username-enable`)
.set(passport.inject({id: '456', roles: ['ADMIN']}))
.then((res) => {
expect(res).to.have.status(204);
});
});
});
});
+2 -2
View File
@@ -75,7 +75,7 @@ describe('services.ActionsService', () => {
expect(retrievedAction).has.property('item_id', comment.id);
});
it('fires the callback sucesfully', async () => {
it('fires the callback successfully', async () => {
const srcAction = {
action_type: 'LIKE',
item_type: 'COMMENTS',
@@ -113,7 +113,7 @@ describe('services.ActionsService', () => {
expect(retrievedAction).is.null;
});
it('fires the callback sucesfully', async () => {
it('fires the callback successfully', async () => {
const spy = sinon.spy();
events.once(ACTIONS_DELETE, spy);
@@ -1,27 +1,27 @@
const expect = require('chai').expect;
const Domainlist = require('../../../services/domainlist');
const DomainList = require('../../../services/domain_list');
const SettingsService = require('../../../services/settings');
describe('services.Domainlist', () => {
describe('services.DomainList', () => {
const domainlists = {
const domainLists = {
whitelist: [
'nytimes.com',
'wapo.com'
]
};
let domainlist = new Domainlist();
let domainList = new DomainList();
const settings = {id: '1', moderation: 'PRE', domainlist: {whitelist: ['nytimes.com', 'wapo.com']}};
beforeEach(() => SettingsService.init(settings));
describe('#init', () => {
before(() => domainlist.upsert(domainlists));
before(() => domainList.upsert(domainLists));
it('has entries', () => {
expect(domainlist.lists.whitelist).to.not.be.empty;
expect(domainList.lists.whitelist).to.not.be.empty;
});
});
@@ -92,21 +92,21 @@ describe('services.Domainlist', () => {
['google.Ca:80', 'google.ca'],
['google.Ca:443', 'google.ca'],
].forEach(([domain, hostname]) => {
expect(Domainlist.parseURL(domain), `domain ${domain} should be parsed as ${hostname}`).to.equal(hostname);
expect(DomainList.parseURL(domain), `domain ${domain} should be parsed as ${hostname}`).to.equal(hostname);
});
});
});
describe('#match', () => {
const whiteList = Domainlist.parseList(domainlists['whitelist']);
const whiteList = DomainList.parseList(domainLists['whitelist']);
it('does match on an included domain', () => {
[
'http://wapo.com',
'nytimes.com'
].forEach((domain) => {
expect(domainlist.match(whiteList, domain)).to.be.true;
expect(domainList.match(whiteList, domain)).to.be.true;
});
});
@@ -116,7 +116,7 @@ describe('services.Domainlist', () => {
'www.badsite.com',
'otherexample.com'
].forEach((domain) => {
expect(domainlist.match(whiteList, domain)).to.be.false;
expect(domainList.match(whiteList, domain)).to.be.false;
});
});
});
-22
View File
@@ -1,22 +0,0 @@
describe('services.scraper', () => {
describe('#create', () => {
it('should create a new kue job');
});
describe('#scrape', () => {
it('should scrape complete information');
it('should scrape what it can');
});
describe('#update', () => {
it('should update the database record entries from the meta');
});
describe('#process', () => {
it('should start the processor to scrape assets');
});
describe('#shutdown', () => {
it('should shutdown the job processor');
});
});
+60 -146
View File
@@ -1,6 +1,6 @@
const UsersService = require('../../../services/users');
const SettingsService = require('../../../services/settings');
const MailerService = require('../../../services/mailer');
const mailer = require('../../../services/mailer');
const chai = require('chai');
chai.use(require('chai-as-promised'));
@@ -29,11 +29,11 @@ describe('services.UsersService', () => {
password: '3Coral!3'
}]);
sinon.spy(MailerService, 'send');
sinon.spy(mailer, 'send');
});
afterEach(() => {
MailerService.send.restore();
mailer.send.restore();
});
describe('#findById()', () => {
@@ -151,21 +151,6 @@ describe('services.UsersService', () => {
});
describe('#setStatus', () => {
it('should set the status to active', () => {
return UsersService
.setStatus(mockUsers[0].id, 'ACTIVE')
.then(() => UsersService.findById(mockUsers[0].id))
.then((user) => {
expect(user).to.have.property('status', 'ACTIVE');
})
.then(() => {
expect(MailerService.send).to.not.have.been.called;
});
});
});
describe('#ignoreUser', () => {
it('should add user id to ignoredUsers set', async () => {
const user = mockUsers[0];
@@ -183,7 +168,7 @@ describe('services.UsersService', () => {
it('should not ignore a staff member', async () => {
const user = mockUsers[0];
const usersToIgnore = [mockUsers[1]];
await UsersService.addRoleToUser(usersToIgnore[0].id, 'STAFF');
await UsersService.setRole(usersToIgnore[0].id, 'STAFF');
try {
await UsersService.ignoreUsers(user.id, usersToIgnore.map((u) => u.id));
@@ -194,145 +179,75 @@ describe('services.UsersService', () => {
});
});
describe('#ban', () => {
it('should set the status to banned', () => {
return UsersService
.setStatus(mockUsers[0].id, 'BANNED')
.then(() => UsersService.findById(mockUsers[0].id))
.then((user) => {
expect(user).to.have.property('status', 'BANNED');
})
.then(() => {
expect(MailerService.send).to.have.been.calledWithMatch({
template: 'banned',
to: mockUsers[0].profiles[0].id
});
[
{func: 'changeUsername', okStatus: 'REJECTED', notOKStatus: 'UNSET', newStatus: 'CHANGED'},
{func: 'setUsername', okStatus: 'UNSET', notOKStatus: 'REJECTED', newStatus: 'SET'},
].forEach(({func, okStatus, notOKStatus, newStatus}) => {
describe(`#${func}`, () => {
[
{status: okStatus},
{error: 'EDIT_USERNAME_NOT_AUTHORIZED', status: notOKStatus},
{error: 'EDIT_USERNAME_NOT_AUTHORIZED', status: 'SET'},
{error: 'EDIT_USERNAME_NOT_AUTHORIZED', status: 'APPROVED'},
{error: 'EDIT_USERNAME_NOT_AUTHORIZED', status: 'CHANGED'},
].forEach(({status, error}) => {
it(`${error ? 'should not' : 'should'} let them change the username if they have the status of ${status}`, async () => {
const user = mockUsers[0];
// Set the user to the desired status.
await UsersService.setUsernameStatus(user.id, status);
try {
await UsersService[func](user.id, 'spock');
} catch (err) {
if (error) {
expect(err).have.property('translation_key', error);
} else {
throw err;
}
}
});
});
});
it('should still disable and ban the user if there is no comment', () => {
return UsersService
.setStatus(mockUsers[0].id, 'BANNED')
.then(() => UsersService.findById(mockUsers[0].id))
.then((user) => {
expect(user).to.have.property('status', 'BANNED');
});
});
});
it(`should change the status to ${newStatus} when changed`, async () => {
const user = mockUsers[0];
describe('#unban', () => {
it('should set the status to active', () => {
return UsersService
.setStatus(mockUsers[0].id, 'ACTIVE')
.then(() => UsersService.findById(mockUsers[0].id))
.then((user) => {
expect(user).to.have.property('status', 'ACTIVE');
});
});
});
// Set the user to the desired status.
await UsersService.setUsernameStatus(user.id, okStatus);
describe('#toggleNameEdit', () => {
it('should toggle the canEditName field', () => {
return UsersService
.toggleNameEdit(mockUsers[0].id, true)
.then(() => UsersService.findById(mockUsers[0].id))
.then((user) => {
expect(user).to.have.property('canEditName', true);
});
});
});
const editedUser = await UsersService[func](user.id, 'spock');
describe('#search', () => {
it('should return all the results without a value', async () => {
expect(await UsersService.search()).to.have.length(3);
});
expect(editedUser.status.username.status).to.equal(newStatus);
it('should match the search terms', async () => {
const tests = [
{
search: 'monster',
results: 1,
id: mockUsers[1].id,
},
{
search: 'Stamp',
results: 1,
id: mockUsers[0].id,
},
{
search: 'sockmonster@gmail.com',
results: 1,
id: mockUsers[1].id,
},
{
search: 'marvel',
results: 1,
id: mockUsers[2].id,
},
{
search: 'gmail.com',
results: 3
try {
await UsersService[func](user.id, 'spock');
throw new Error('edit was processed successfully');
} catch (err) {
expect(err).have.property('translation_key', 'EDIT_USERNAME_NOT_AUTHORIZED');
}
];
});
for (const test of tests) {
const users = await UsersService.search(test.search);
it(`${func === 'changeUsername' ? 'should' : 'should not'} refuse changing the username to the same username`, async () => {
const user = mockUsers[0];
expect(users).to.have.length(test.results);
if (test.results === 1) {
expect(users[0]).to.have.property('id', test.id);
// Set the user to the desired status.
await UsersService.setUsernameStatus(user.id, okStatus);
if (func === 'changeUsername') {
try {
await UsersService[func](user.id, user.username);
throw new Error('edit was processed successfully');
} catch (err) {
expect(err).have.property('translation_key', 'SAME_USERNAME_PROVIDED');
}
} else {
await UsersService[func](user.id, user.username);
}
}
});
});
});
describe('#editName', () => {
it('should let the user edit their username if the proper toggle is set', () => {
return UsersService
.toggleNameEdit(mockUsers[0].id, true)
.then(() => UsersService.editName(mockUsers[0].id, 'Jojo'))
.then(() => UsersService.findById(mockUsers[0].id))
.then((user) => {
expect(user).to.have.property('username', 'Jojo');
expect(user).to.have.property('canEditName', false);
});
});
it('should let the user submit the same username if user is not banned (create username)', () => {
return UsersService
.toggleNameEdit(mockUsers[0].id, true)
.then(() => UsersService.editName(mockUsers[0].id, mockUsers[0].username))
.then(() => UsersService.findById(mockUsers[0].id))
.then((user) => {
expect(user).to.have.property('username', mockUsers[0].username);
expect(user).to.have.property('canEditName', false);
});
});
it('should return error when a banned user submits the same username (rejected username)', () => {
return UsersService
.toggleNameEdit(mockUsers[0].id, true)
.then(() => UsersService.setStatus(mockUsers[0].id, 'BANNED'))
.then(() => UsersService.editName(mockUsers[0].id, mockUsers[0].username))
.then(() => UsersService.findById(mockUsers[0].id))
.then(() => {
throw new Error('Error expected');
})
.catch((err) => {
expect(err.status).to.equal(400);
expect(err.translation_key).to.equal('SAME_USERNAME_PROVIDED');
});
});
it('should return an error if canEditName is false', async () => {
return expect(UsersService.editName(mockUsers[0].id, 'Jojo')).to.eventually.be.rejected;
});
it('should return an error if the username is already taken', async () => {
await UsersService.toggleNameEdit(mockUsers[0].id, true);
return expect(UsersService.editName(mockUsers[0].id, 'Marvel')).to.eventually.be.rejected;
});
describe('#isValidUsername', () => {
it('should not allow non-alphanumeric characters in usernames', () => {
return UsersService
.isValidUsername('hi🖕')
@@ -344,5 +259,4 @@ describe('services.UsersService', () => {
});
});
});
});