Merge branch 'master' of github.com:coralproject/talk into e2e-env-fix

This commit is contained in:
David Jay
2017-01-25 17:17:04 -05:00
168 changed files with 4549 additions and 3444 deletions
+14
View File
@@ -0,0 +1,14 @@
{
"env": {
"es6": true,
"node": true,
"mocha": true
},
"plugins": [
"mocha"
],
"extends": "../.eslintrc.json",
"rules": {
"mocha/no-exclusive-tests": "warn"
}
}
+23
View File
@@ -0,0 +1,23 @@
{
"env": {
"browser": true,
"es6": true,
"mocha": true
},
"extends": "../.eslintrc.json",
"parserOptions": {
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"jsx": true
},
"sourceType": "module"
},
"parser": "babel-eslint",
"plugins": [
"react"
],
"rules": {
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error"
}
}
+89
View File
@@ -0,0 +1,89 @@
import 'react';
import 'redux';
import {expect} from 'chai';
import fetchMock from 'fetch-mock';
import * as actions from '../../../../client/coral-admin/src/actions/assets';
import {Map} from 'immutable';
import configureStore from 'redux-mock-store';
const mockStore = configureStore();
describe('Asset actions', () => {
let store;
const assets = [
{
url: 'http://test.com',
id: '123',
status: 'closed'
},
{
url: 'http://test.org',
id: '456',
status: 'open'
}
];
beforeEach(() => {
store = mockStore(new Map({}));
fetchMock.restore();
});
describe('FETCH_ASSETS_REQUEST', () => {
it('should fetch a list of assets', () => {
fetchMock.get('*', JSON.stringify({
result: assets,
count: 2
}));
return actions.fetchAssets(2, 20)(store.dispatch)
.then(() => {
expect(store.getActions()[0]).to.have.property('type', 'FETCH_ASSETS_REQUEST');
expect(store.getActions()[1]).to.have.property('type', 'FETCH_ASSETS_SUCCESS');
expect(store.getActions()[1]).to.have.property('count', 2);
expect(store.getActions()[1]).to.have.property('assets').
and.to.deep.equal(assets);
});
});
it('should return an error appropriatly', () => {
fetchMock.get('*', 404);
return actions.fetchAssets(2, 20)(store.dispatch)
.then(() => {
expect(store.getActions()[0]).to.have.property('type', 'FETCH_ASSETS_REQUEST');
expect(store.getActions()[1]).to.have.property('type', 'FETCH_ASSETS_FAILURE');
});
});
});
describe('UPDATE_ASSET_STATE_REQUEST', () => {
it('should update an asset', () => {
fetchMock.put('*', JSON.stringify(assets[0]));
return actions.updateAssetState('123', 'status', 'open')(store.dispatch)
.then(() => {
expect(store.getActions()[0]).to.have.property('type', 'UPDATE_ASSET_STATE_REQUEST');
expect(store.getActions()[1]).to.have.property('type', 'UPDATE_ASSET_STATE_SUCCESS');
});
});
it('should return an error appropriately', () => {
fetchMock.put('*', 404);
return actions.updateAssetState('123', 'status', 'open')(store.dispatch)
.then(() => {
expect(store.getActions()[0]).to.have.property('type', 'UPDATE_ASSET_STATE_REQUEST');
expect(store.getActions()[1]).to.have.property('type', 'UPDATE_ASSET_STATE_FAILURE');
});
});
});
});
@@ -0,0 +1,64 @@
import {Map, fromJS} from 'immutable';
import {expect} from 'chai';
import assetsReducer from '../../../../client/coral-admin/src/reducers/assets';
describe ('assetsReducer', () => {
describe('FETCH_ASSETS_SUCCESS', () => {
it('should replace the existing assets', () => {
const action = {
type: 'FETCH_ASSETS_SUCCESS',
count: 200,
assets: [
{
id: '123',
url: 'http://test.com',
closedAt: 'tomorrow'
},
{
id: '456',
url: 'http://test2.com',
closedAt: 'thursday'
},
]
};
const store = new Map({});
const result = assetsReducer(store, action);
expect(result.getIn(['byId', '123']).toJS()).to.deep.equal({
url: 'http://test.com',
closedAt: 'tomorrow',
id: '123'
});
expect(result.getIn(['ids']).toJS()).to.deep.equal([
'123',
'456'
]);
expect(result.getIn(['count'])).to.equal(200);
});
});
describe('UPDATE_ASSET_STATE_REQUEST', () => {
it('should update the state of a particular asset', () => {
const action = {
type: 'UPDATE_ASSET_STATE_REQUEST',
id: '123',
closedAt: null
};
const store = new fromJS({
byId: {
'123': {
id: '123',
url: 'http://test.com',
closedAt: Date.now()
},
'456': {
id: '456',
url: 'http://test2.com',
closedAt: 'thursday'
}
}
});
const result = assetsReducer(store, action);
expect(result.getIn(['byId', '123', 'closedAt'])).to.equal.null;
});
});
});
@@ -0,0 +1,198 @@
import 'react';
import 'redux';
import {expect} from 'chai';
import fetchMock from 'fetch-mock';
import * as actions from '../../../../client/coral-framework/actions/items';
import {Map} from 'immutable';
import configureStore from 'redux-mock-store';
const mockStore = configureStore();
describe('itemActions', () => {
let store;
beforeEach(() => {
store = mockStore(new Map({}));
fetchMock.restore();
});
describe('getStream', () => {
const assetUrl = 'http://www.test.com';
const response = {
assets: [{
id: '1234', url: assetUrl
}],
comments: [
{body: 'stuff', id: '123'},
{body: 'morestuff', id: '456'}
],
actions: [
{
action_type: 'like',
item_id: '123',
count: 1,
id: 'like_123',
current_user: false
},
{
action_type: 'flag',
item_id: '456',
count: 5,
id: 'flag_456',
current_user: true
}
]
};
it('should get an stream from an asset_url and send the appropriate dispatches', () => {
fetchMock.get('*', JSON.stringify(response));
return actions.getStream(assetUrl)(store.dispatch)
.then((res) => {
expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/stream?asset_url=http%3A%2F%2Fwww.test.com');
expect(res).to.deep.equal(response);
expect(store.getActions()[1]).to.deep.equal({
type: actions.ADD_ITEM,
item: response.comments[0],
item_type: 'comments',
id: '123'
});
expect(store.getActions()[2]).to.deep.equal({
type: actions.ADD_ITEM,
item: response.comments[1],
item_type: 'comments',
id: '456'
});
});
});
it('should handle an error', () => {
fetchMock.get('*', 404);
return actions.getStream(assetUrl)(store.dispatch)
.catch((err) => {
expect(err).to.be.truthy;
});
});
});
// Disabling tests for this function until is is used again.
xdescribe('getItemsArray', () => {
const response = {items: [{type: 'comment', id: '123'}, {type: 'comment', id: '456'}]};
const ids = [1, 2];
it('should get an item from an array of ids and send the appropriate dispatches', () => {
fetchMock.get('*', JSON.stringify(response));
return actions.getItemsArray(ids)(store.dispatch)
.then((res) => {
expect(res).to.deep.equal(response.items);
expect(store.getActions()[0]).to.deep.equal({
type: actions.ADD_ITEM,
item: {
type: 'comment',
id: '123'
},
id: '123'
});
expect(store.getActions()[1]).to.deep.equal({
type: actions.ADD_ITEM,
item: {
type: 'comment', id: '456'
},
id: '456'
});
});
});
it('should handle an error', () => {
fetchMock.get('*', 404);
return actions.getItemsArray(ids)(store.dispatch)
.catch((err) => {
expect(err).to.be.truthy;
});
});
});
// NEED TO FIGURE OUT HOW TO TEST WITH CSRF TOKEN IN.
xdescribe('postItem', () => {
const item = {
type: 'comments',
data: {body: 'stuff'}
};
it ('should post an item, return an id, then dispatch that item to the store', () => {
fetchMock.post('*', {id: '123'});
return actions.postItem(item.data, item.type, undefined)(store.dispatch, store.getState)
.then((id) => {
expect(fetchMock.calls().matched[0][1]).to.deep.equal(
{
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type':'application/json'
},
credentials: 'same-origin',
body: JSON.stringify(item.data)
}
);
expect(id).to.deep.equal({id: '123'});
expect(store.getActions()[0]).to.deep.equal({
type: actions.ADD_ITEM,
item: {
body: 'stuff',
id: '123'
},
item_type: 'comments',
id: '123'
});
});
});
it('should handle an error', () => {
fetchMock.post('*', 404);
return actions.postItem(item)(store.dispatch, store.getState)
.catch((err) => {
expect(err).to.be.truthy;
});
});
});
xdescribe('postAction', () => {
it ('should post an action', () => {
fetchMock.post('*', {id: '456'});
const action = {
action_type: 'flag',
detail: 'Comment smells funny'
};
return actions.postAction('abc', 'comments', action)(store.dispatch, store.getState)
.then(response => {
expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/comments/abc/actions');
expect(response).to.deep.equal({id:'456'});
});
});
it('should handle an error', () => {
fetchMock.post('*', 404);
return actions.postAction('abc', 'flag', '123')(store.dispatch, store.getState)
.catch((err) => {
expect(err).to.be.truthy;
});
});
});
describe('deleteAction', () => {
it ('should remove an action', () => {
fetchMock.delete('*', {});
return actions.deleteAction('abc', 'flag', '123', 'comments')(store.dispatch)
.then(response => {
expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/actions/abc');
expect(response).to.deep.equal({});
});
});
xit('should handle an error', () => {
fetchMock.post('*', 404);
return actions.postAction('abc', 'flag', '123')(store.dispatch, store.getState)
.catch((err) => {
expect(err).to.be.truthy;
});
});
});
});
@@ -0,0 +1,95 @@
import {Map, fromJS} from 'immutable';
import {expect} from 'chai';
import itemsReducer from '../../../../client/coral-framework/reducers/items';
describe ('itemsReducer', () => {
describe('ADD_ITEM', () => {
it('should add an item', () => {
const action = {
type: 'ADD_ITEM',
item: {
body: 'stuff',
id: '123'
},
item_type: 'comments',
id: '123'
};
const store = new Map({});
const result = itemsReducer(store, action);
expect(result.getIn(['comments', '123']).toJS()).to.deep.equal({
body: 'stuff',
id: '123'
});
});
});
describe ('UPDATE_ITEM', () => {
it ('should update an item', () => {
const action = {
type: 'UPDATE_ITEM',
property: 'stuff',
value: 'things',
item_type: 'comments',
id: '123'
};
const store = fromJS({
'comments': {
'123': {
id: '123',
stuff: 'morestuff'
}
}
});
const result = itemsReducer(store, action);
expect(result.getIn(['comments', '123']).toJS()).to.deep.equal({
id: '123',
stuff: 'things'
});
});
});
describe('APPEND_ITEM_ARRAY', () => {
let action;
let store;
beforeEach (() => {
action = {
type: 'APPEND_ITEM_ARRAY',
property: 'stuff',
value: 'things',
id: '123',
item_type: 'comments'
};
store = fromJS({
'comments': {
'123': {
id: '123',
stuff: ['morestuff']
}
}
});
});
it ('should append to an existing array', () => {
const result = itemsReducer(store, action);
expect(result.getIn(['comments', '123']).toJS()).to.deep.equal({
id: '123',
stuff: ['morestuff', 'things']
});
});
it ('should create a new array', () => {
store = fromJS({
'comments': {
'123': {
id: '123'
}
}
});
const result = itemsReducer(store, action);
expect(result.getIn(['comments', '123']).toJS()).to.deep.equal({
id: '123',
stuff: ['things']
});
});
});
});
@@ -0,0 +1,35 @@
import {Map} from 'immutable';
import {expect} from 'chai';
import notificationReducer from '../../../../client/coral-framework/reducers/notification';
import * as actions from '../../../../client/coral-framework/actions/notification';
describe ('notificationsReducer', () => {
describe('ADD_NOTIFICATION', () => {
it('should add a notification', () => {
const action = {
type: actions.ADD_NOTIFICATION,
text: 'Test notification',
notifType: 'test'
};
const store = new Map({});
const result = notificationReducer(store, action);
expect(result.get('text')).to.equal(action.text);
expect(result.get('type')).to.equal(action.notifType);
});
});
describe('CLEAR_NOTIFICATION', () => {
it('should clear a notification', () => {
const action = {
type: actions.CLEAR_NOTIFICATION
};
const store = new Map({
text: 'Test notification',
type: 'test'
});
const result = notificationReducer(store, action);
expect(result.get('text')).to.equal(undefined);
expect(result.get('type')).to.equal(undefined);
});
});
});
@@ -0,0 +1,30 @@
import React from 'react';
import {shallow, mount} from 'enzyme';
import {expect} from 'chai';
import Comment from '../../../client/coral-plugin-history/Comment';
describe('coral-plugin-history/Comment', () => {
let render;
const comment = {body: 'this is a comment', id: '123'};
const asset = {url: 'https://google.com'};
beforeEach(() => {
render = shallow(<Comment asset={asset} comment={comment} link={()=>{}}/>);
});
it('should render the provided comment body', () => {
const wrapper = mount(<Comment asset={asset} comment={comment} link={()=>{}}/>);
expect(wrapper.find('.myCommentBody')).to.have.length(1);
expect(wrapper.find('.myCommentBody').text()).to.equal('this is a comment');
});
it('should render the asset url as a link', () => {
const wrapper = mount(<Comment asset={asset} comment={comment} link={()=>{}}/>);
expect(wrapper.find('.myCommentAnchor')).to.have.length(1);
expect(wrapper.find('.myCommentAnchor').text()).to.equal('https://google.com');
});
it('should render the comment with styles', () => {
expect(render.props().style).to.be.defined;
});
});
@@ -0,0 +1,23 @@
import React from 'react';
import {shallow, mount} from 'enzyme';
import {expect} from 'chai';
import CommentHistory from '../../../client/coral-plugin-history/CommentHistory';
describe('coral-plugin-history/CommentHistory', () => {
let render;
const comments = [{body: 'a comment or something', 'status_history':[{'type':'premod', 'created_at':'2016-12-09T01:40:53.327Z', 'assigned_by':null}, {'created_at':'2016-12-09T22:52:44.888Z', 'type':'accepted', 'assigned_by':'92256159-1164-4f66-9970-c7f23de7e461'}], 'asset_id':'96fddf96-7c83-4008-80ad-50091997d006', 'created_at':'2016-12-09T01:40:53.360Z', 'author_id':'92256159-1164-4f66-9970-c7f23de7e461', 'status':'accepted', '__v':0, 'updated_at':'2016-12-09T22:52:44.893Z', 'id':'3962c2ea-4ec4-42e4-b9bd-c571ff30f56b'}, {'body':'another comment', 'status_history':[{'type':'premod', 'created_at':'2016-12-09T22:53:43.148Z', 'assigned_by':null}], 'asset_id':'96fddf96-7c83-4008-80ad-50091997d006', 'created_at':'2016-12-09T22:53:43.158Z', 'author_id':'92256159-1164-4f66-9970-c7f23de7e461', 'status':'premod', '__v':0, 'updated_at':'2016-12-09T22:53:43.158Z', 'id':'b51e27af-bcfd-4932-91be-e3f01a4802e6'}, {'body':'can I comment?', 'status_history':[{'type':'premod', 'created_at':'2016-12-13T23:23:47.123Z', 'assigned_by':null}, {'created_at':'2016-12-13T23:23:58.487Z', 'type':'accepted', 'assigned_by':'92256159-1164-4f66-9970-c7f23de7e461'}], 'asset_id':'cef81015-1b53-4d70-b9af-6eca680f22fc', 'created_at':'2016-12-13T23:23:47.131Z', 'author_id':'92256159-1164-4f66-9970-c7f23de7e461', 'status':'accepted', '__v':0, 'updated_at':'2016-12-13T23:23:58.493Z', 'id':'dc9d7be1-b911-4dc3-8e1e-400e8b8d110e'}, {'body':'pre-mod comment', 'status_history':[{'type':'premod', 'created_at':'2016-12-08T21:34:56.994Z', 'assigned_by':null}, {'created_at':'2016-12-08T21:38:04.961Z', 'type':'rejected', 'assigned_by':'92256159-1164-4f66-9970-c7f23de7e461'}], 'asset_id':'96fddf96-7c83-4008-80ad-50091997d006', 'created_at':'2016-12-08T21:34:56.997Z', 'author_id':'92256159-1164-4f66-9970-c7f23de7e461', 'status':'rejected', '__v':0, 'updated_at':'2016-12-08T21:38:04.965Z', 'id':'6f02af16-a8f8-4ead-80ea-0d48824eb74d'}, {'body':'a flagged commetn', 'status_history':[{'type':'premod', 'created_at':'2016-12-08T21:38:26.342Z', 'assigned_by':null}, {'created_at':'2016-12-09T23:47:27.009Z', 'type':'accepted', 'assigned_by':'92256159-1164-4f66-9970-c7f23de7e461'}], 'asset_id':'96fddf96-7c83-4008-80ad-50091997d006', 'created_at':'2016-12-08T21:38:26.344Z', 'author_id':'92256159-1164-4f66-9970-c7f23de7e461', 'status':'accepted', '__v':0, 'updated_at':'2016-12-09T23:47:27.018Z', 'id':'784c5f91-36b9-4bda-b4ca-a114cef2c9f0'}, {'body':'a post mod comment', 'status_history':[{'type':'premod', 'created_at':'2016-12-08T22:19:05.870Z', 'assigned_by':null}, {'created_at':'2016-12-09T23:26:41.427Z', 'type':'accepted', 'assigned_by':'92256159-1164-4f66-9970-c7f23de7e461'}], 'asset_id':'96fddf96-7c83-4008-80ad-50091997d006', 'created_at':'2016-12-08T22:19:05.874Z', 'author_id':'92256159-1164-4f66-9970-c7f23de7e461', 'status':'accepted', '__v':0, 'updated_at':'2016-12-09T23:26:41.450Z', 'id':'e8b86039-f850-4e53-bd9d-f8c9186a9637'}, {'body':'an actual post-mod comment here', 'status_history':[], 'asset_id':'96fddf96-7c83-4008-80ad-50091997d006', 'created_at':'2016-12-08T22:20:11.147Z', 'author_id':'92256159-1164-4f66-9970-c7f23de7e461', 'status':null, '__v':0, 'updated_at':'2016-12-08T22:20:11.147Z', 'id':'cff1a318-50c6-431e-9a63-de7a7b7136bf'}];
const assets = [{'settings': null, 'created_at':'2016-12-06T21:36:09.302Z', 'url':'localhost:3000/', 'scraped':null, 'status':'open', 'updated_at':'2016-12-08T02:11:15.943Z', '_id':'58472f499e775a38f23d5da0', 'type':'article', 'closedMessage':null, 'id':'7302e637-f884-47c0-9723-02cc10a18617', 'closedAt':null}, {'settings':null, 'created_at':'2016-12-07T02:25:31.983Z', 'url':'http://localhost:3000/', 'scraped':null, 'status':'open', 'updated_at':'2016-12-13T22:58:36.061Z', '_id':'5847731b9e775a38f23d5da1', 'type':'article', 'closedMessage':null, 'id':'96fddf96-7c83-4008-80ad-50091997d006', 'closedAt':null}, {'settings':null, 'created_at':'2016-12-12T19:04:05.770Z', 'url':'http://localhost:3000/embed/stream', 'scraped':null, 'updated_at':'2016-12-14T20:13:21.934Z', '_id':'584ef4a59e775a38f23d5e86', 'type':'article', 'closedMessage':null, 'id':'cef81015-1b53-4d70-b9af-6eca680f22fc', 'closedAt':null}];
beforeEach(() => {
render = shallow(<CommentHistory comments={comments} assets={assets} link={()=>{}}/>);
});
it('should render Comments as children when given comments and assets', () => {
const wrapper = mount(<CommentHistory comments={comments} assets={assets} link={()=>{}}/>);
expect(wrapper.find('.commentHistory__list').children()).to.have.length(7);
});
it('should render with styles', () => {
expect(render.props().style).to.be.defined;
});
});
+18
View File
@@ -0,0 +1,18 @@
module.exports = {
waitForConditionTimeout: 8000,
baseUrl: 'http://localhost:3000',
users: {
admin: {
email: 'admin@test.com',
pass: 'testtest'
},
moderator: {
email: 'moderator@test.com',
pass: 'testtest'
},
commenter: {
email: 'commenter@test.com',
pass: 'testtest'
}
},
};
+25
View File
@@ -0,0 +1,25 @@
const Comments = require('../../models/comment');
const Users = require('../../models/user');
const Actions = require('../../models/action');
const Assets = require('../../models/asset');
const Settings = require('../../models/setting');
const globals = require('./globals');
/* Create an array of comments */
module.exports.comments = (comments) => Assets.findOrCreateByUrl(globals.baseUrl)
.then((asset) => {
comments = comments.map((comment) => {
comment.asset_id = asset.id;
return comment;
});
return Comments.create(comments);
});
/* Create an array of users */
module.exports.users = (users) => Users.createLocalUsers(users);
/* Create an array of actions */
module.exports.actions = (actions) => Actions.create(actions);
/* Update a setting */
module.exports.settings = (setting) => Settings.init().then(() => Settings.updateSettings(setting));
+33
View File
@@ -0,0 +1,33 @@
const embedStreamCommands = {
url: function () {
return `${this.api.launchUrl}/admin`;
},
ready() {
return this
.waitForElementVisible('body', 2000);
},
approveComment() {
return this
.waitForElementVisible('@commentList')
.waitForElementVisible('@approveButton')
.click('@approveButton');
}
};
module.exports = {
commands: [embedStreamCommands],
elements: {
commentList: {
selector: '#commentList'
},
banButton: {
selector: '#commentList .actions:first-child .ban'
},
rejectButton: {
selector: '#commentList .actions:first-child .reject'
},
approveButton: {
selector: '#commentList .actions:first-child .approve'
}
}
};
+160
View File
@@ -0,0 +1,160 @@
const embedStreamCommands = {
url: function () {
return this
.api.launchUrl;
},
ready() {
return this
.waitForElementVisible('body', 4000)
.waitForElementVisible('iframe#coralStreamIframe')
.api.frame('coralStreamIframe');
},
signUp(user) {
return this
.waitForElementVisible('@signInButton', 2000)
.click('@signInButton')
.waitForElementVisible('@signInDialog')
.waitForElementVisible('@registerButton')
.click('@registerButton')
.setValue('@signInDialogEmail', user.email)
.setValue('@signInDialogPassword', user.pass)
.setValue('@signUpDialogConfirmPassword', user.pass)
.setValue('@signUpDialogDisplayName', user.displayName)
.waitForElementVisible('@signUpButton')
.click('@signUpButton')
.waitForElementVisible('@logInButton')
.click('@logInButton')
.waitForElementVisible('@logoutButton', 5000);
},
login(user) {
return this
.waitForElementVisible('@signInButton', 2000)
.click('@signInButton')
.waitForElementVisible('@signInDialog')
.waitForElementVisible('@signInDialogEmail')
.waitForElementVisible('@signInDialogPassword')
.setValue('@signInDialogEmail', user.email)
.setValue('@signInDialogPassword', user.pass)
.waitForElementVisible('@logInButton')
.click('@logInButton')
.waitForElementVisible('@logoutButton', 5000);
},
logout() {
return this
.waitForElementVisible('@logoutButton')
.click('@logoutButton')
.waitForElementVisible('@signInButton', 2000);
},
postComment(comment = 'Test Comment') {
return this
.waitForElementVisible('@commentBox', 2000)
.setValue('@commentBox', comment)
.click('@postButton');
},
likeComment() {
return this
.waitForElementVisible('@likeButton')
.click('@likeButton');
},
flagComment() {
return this
.waitForElementVisible('@flagButton')
.click('@flagButton');
},
flagUsername() {
return this
.waitForElementVisible('@flagButton')
.click('@flagButton');
},
getPermalink(fn) {
return this
.waitForElementVisible('@permalinkButton')
.click('@permalinkButton')
.waitForElementVisible('@permalinkPopUp')
.getValue('@permalinkInput', result => fn(result.value));
}
};
module.exports = {
commands: [embedStreamCommands],
elements: {
signInButton: {
selector: '#coralSignInButton'
},
signInDialog:{
selector: '#signInDialog'
},
signInDialogEmail: {
selector: '#signInDialog #email'
},
signInDialogPassword: {
selector: '#signInDialog #password'
},
signUpDialogConfirmPassword: {
selector: '#signInDialog #confirmPassword'
},
signUpDialogDisplayName: {
selector: '#signInDialog #displayName'
},
logInButton: {
selector: '#coralLogInButton'
},
signUpButton: {
selector: '#coralSignUpButton'
},
logoutButton: {
selector: '.commentStream #logout'
},
commentBox: {
selector: '.coral-plugin-commentbox-textarea'
},
postButton: {
selector: '#commentBox .coral-plugin-commentbox-button'
},
likeButton: {
selector: '.comment .coral-plugin-likes-container .coral-plugin-likes-button'
},
likeText: {
selector: '.comment .coral-plugin-likes-container .coral-plugin-likes-button .coral-plugin-likes-button-text'
},
likesCount: {
selector: '.comment .coral-plugin-likes-container .coral-plugin-likes-button .coral-plugin-likes-like-count'
},
flagButton: {
selector: '.comment .coral-plugin-flags-container .coral-plugin-flags-button'
},
flagPopUp: {
selector: '.comment .coral-plugin-flags-popup'
},
flagCommentOption: {
selector: '.comment .coral-plugin-flags-popup .coral-plugin-flags-popup-radio-label[for="comments"]'
},
flagUsernameOption: {
selector: '.comment .coral-plugin-flags-popup .coral-plugin-flags-popup-radio-label[for="user"]'
},
flagOtherOption: {
selector: '.comment .coral-plugin-flags-popup .coral-plugin-flags-popup-radio-label[for="other"]'
},
flagHeaderMessage: {
selector: '.comment .coral-plugin-flags-popup .coral-plugin-flags-popup-header'
},
flagButtonText: {
selector: '.comment .coral-plugin-flags-button-text'
},
flagDoneButton: {
selector: '.comment .coral-plugin-flags-popup .coral-plugin-flags-popup-button'
},
permalinkButton: {
selector: '.comment .coral-plugin-permalinks-button'
},
permalinkPopUp: {
selector: '.comment .coral-plugin-permalinks-popover.active'
},
permalinkInput: {
selector: '.comment .coral-plugin-permalinks-popover.active input'
},
registerButton: {
selector: '#signInDialog #coralRegister'
}
}
};
+10
View File
@@ -0,0 +1,10 @@
module.exports = {
'@tags': ['app'],
'Base url and Hostname': browser => {
const {baseUrl} = browser.globals;
browser
.url(baseUrl)
.assert.title('Coral Talk')
.waitForElementPresent('body', 1000);
}
};
+44
View File
@@ -0,0 +1,44 @@
module.exports = {
'@tags': ['embedStream'],
before: client => {
const embedStreamPage = client.page.embedStreamPage();
embedStreamPage
.navigate()
.ready();
},
'Login as commenter': client => {
const embedStreamPage = client.page.embedStreamPage();
const {users} = client.globals;
embedStreamPage
.login(users.commenter);
},
'Add test comment': client => {
const embedStreamPage = client.page.embedStreamPage();
embedStreamPage
.postComment('Test Comment');
},
'Logout': client => {
const embedStreamPage = client.page.embedStreamPage();
embedStreamPage
.logout();
},
'Login as admin': client => {
const embedStreamPage = client.page.embedStreamPage();
const {users} = client.globals;
embedStreamPage
.login(users.admin);
},
'Approve test comment': client => {
const adminPage = client.page.adminPage();
adminPage
.navigate()
.ready();
adminPage
.approveComment();
},
after: client => {
client.end();
}
};
+23
View File
@@ -0,0 +1,23 @@
module.exports = {
'@tags': ['login', 'ADMIN'],
before(client) {
const embedStreamPage = client.page.embedStreamPage();
const {launchUrl} = client;
client
.url(launchUrl);
embedStreamPage
.ready();
},
'Admin logs in': client => {
const {users} = client.globals;
const embedStreamPage = client.page.embedStreamPage();
embedStreamPage
.login(users.admin);
},
after: client => {
client.end();
}
};
@@ -0,0 +1,34 @@
module.exports = {
'@tags': ['flag', 'comments', 'commenter'],
before: client => {
const embedStreamPage = client.page.embedStreamPage();
const {users} = client.globals;
embedStreamPage
.navigate()
.ready();
embedStreamPage
.login(users.commenter);
},
'Commenter flags a comment': client => {
const embedStreamPage = client.page.embedStreamPage();
embedStreamPage
.flagComment()
.waitForElementVisible('@flagPopUp')
.waitForElementVisible('@flagCommentOption')
.click('@flagCommentOption')
.waitForElementVisible('@flagDoneButton')
.click('@flagDoneButton')
.waitForElementVisible('@flagOtherOption')
.click('@flagOtherOption')
.waitForElementVisible('@flagDoneButton')
.click('@flagDoneButton')
.click('@flagDoneButton')
.expect.element('@flagButtonText').text.to.equal('Reported');
},
after: client => {
client.end();
}
};
@@ -0,0 +1,33 @@
module.exports = {
'@tags': ['flag', 'commenter'],
before: client => {
const embedStreamPage = client.page.embedStreamPage();
const {users} = client.globals;
embedStreamPage
.navigate()
.ready();
embedStreamPage
.login(users.commenter);
},
'Commenter flags a username': client => {
const embedStreamPage = client.page.embedStreamPage();
embedStreamPage
.flagUsername()
.waitForElementVisible('@flagPopUp')
.waitForElementVisible('@flagUsernameOption')
.click('@flagUsernameOption')
.waitForElementVisible('@flagDoneButton')
.click('@flagDoneButton')
.waitForElementVisible('@flagOtherOption')
.click('@flagOtherOption')
.waitForElementVisible('@flagDoneButton')
.click('@flagDoneButton')
.click('@flagDoneButton');
},
after: client => {
client.end();
}
};
@@ -0,0 +1,26 @@
module.exports = {
'@tags': ['like', 'comments', 'commenter'],
before: client => {
const embedStreamPage = client.page.embedStreamPage();
const {users} = client.globals;
embedStreamPage
.navigate()
.ready();
embedStreamPage
.login(users.commenter);
},
'Commenter likes a comment': client => {
const embedStreamPage = client.page.embedStreamPage();
embedStreamPage
.likeComment()
.waitForElementVisible('@likesCount', 2000)
.expect.element('@likeText').text.to.equal('Liked');
},
after: client => {
client.end();
}
};
+20
View File
@@ -0,0 +1,20 @@
module.exports = {
'@tags': ['login', 'commenter'],
before: client => {
const embedStreamPage = client.page.embedStreamPage();
embedStreamPage
.navigate()
.ready();
},
'Commenter logs in': client => {
const {users} = client.globals;
const embedStreamPage = client.page.embedStreamPage();
embedStreamPage
.login(users.commenter);
},
after: client => {
client.end();
}
};
+34
View File
@@ -0,0 +1,34 @@
let permalink = '';
module.exports = {
'@tags': ['permalink', 'commenter'],
before: client => {
const embedStreamPage = client.page.embedStreamPage();
const {users} = client.globals;
embedStreamPage
.navigate()
.ready();
embedStreamPage
.login(users.commenter);
},
'Commenter gets the permalink of a comment': client => {
const embedStreamPage = client.page.embedStreamPage();
embedStreamPage
.getPermalink(value => {
permalink = value;
});
},
'Commenter navigates to the permalink': client => {
const embedStreamPage = client.page.embedStreamPage();
embedStreamPage
.navigate(permalink);
client.assert.urlContains(permalink);
},
after: client => {
client.end();
}
};
+38
View File
@@ -0,0 +1,38 @@
module.exports = {
'@tags': ['write', 'commenter'],
before: client => {
const embedStreamPage = client.page.embedStreamPage();
const {users} = client.globals;
embedStreamPage
.navigate()
.ready();
embedStreamPage
.login(users.commenter);
},
'Commenter posts a comment': client => {
const embedStreamPage = client.page.embedStreamPage();
embedStreamPage
.postComment('I read the comments');
},
after: client => {
const adminPage = client.page.adminPage();
const embedStreamPage = client.page.embedStreamPage();
const {users} = client.globals;
embedStreamPage
.logout()
.login(users.admin);
adminPage
.navigate()
.ready();
adminPage
.approveComment();
client.end();
}
};
+185
View File
@@ -0,0 +1,185 @@
const mongoose = require('../../helpers/mongoose');
const mocks = require('../mocks');
const mockComment = 'This is a test comment.';
const mockReply = 'This is a test reply';
const mockUser = {
email: `${new Date().getTime()}@test.com`,
name: 'testuser',
pw: 'testtest'
};
module.exports = {
'@tags': ['embed-stream', 'comment', 'premodoff', 'premodon'],
before: () => {
mongoose.waitTillConnect(function(err) {
if (err) {
console.error(err);
}
});
},
'User registers and posts a comment with premod off': client => {
client.perform((client, done) => {
mocks.settings({moderation: 'POST'})
.then(() => {
// Load Page
client.resizeWindow(1200, 800)
.url(client.globals.baseUrl)
.frame('coralStreamIframe')
// Register and Log In
.waitForElementVisible('#commentBox', 1000)
.waitForElementVisible('#coralSignInButton', 2000)
.click('#coralSignInButton')
.waitForElementVisible('#coralRegister', 1000)
.click('#coralRegister')
.waitForElementVisible('#email', 1000)
.setValue('#email', mockUser.email)
.setValue('#displayName', mockUser.name)
.setValue('#password', mockUser.pw)
.setValue('#confirmPassword', mockUser.pw)
.click('#coralSignUpButton')
.waitForElementVisible('#coralLogInButton', 10000)
.click('#coralLogInButton')
.waitForElementVisible('.coral-plugin-commentbox-button', 4000)
// Post a comment
.setValue('.coral-plugin-commentbox-textarea', mockComment)
.click('.coral-plugin-commentbox-button')
.waitForElementVisible('.comment', 1000)
// Verify that it appears
.assert.containsText('.comment', mockComment);
done();
})
.catch((err) => {
console.log(err);
done();
});
});
},
'User posts a comment with premod on': client => {
client.perform((client, done) => {
mocks.settings({moderation: 'PRE'})
.then(() => {
// Load Page
client.url(client.globals.baseUrl)
.frame('coralStreamIframe');
// Post a comment
client.waitForElementVisible('.coral-plugin-commentbox-button', 2000)
.setValue('.coral-plugin-commentbox-textarea', mockComment)
.click('.coral-plugin-commentbox-button')
.waitForElementVisible('#coral-notif', 1000)
// Verify that it appears
.assert.containsText('#coral-notif', 'moderation team');
done();
})
.catch((err) => {
console.log(err);
done();
});
});
},
'User replies to a comment with premod off': client => {
client.perform((client, done) => {
mocks.settings({moderation: 'POST'})
.then(() => {
// Load Page
client.resizeWindow(1200, 800)
.url(client.globals.baseUrl)
.frame('coralStreamIframe');
// Post a comment
client.waitForElementVisible('.coral-plugin-commentbox-button', 2000)
.setValue('.coral-plugin-commentbox-textarea', mockComment)
.click('.coral-plugin-commentbox-button')
// Post a reply
.waitForElementVisible('.coral-plugin-replies-reply-button', 5000)
.click('.coral-plugin-replies-reply-button')
.waitForElementVisible('#replyText')
.setValue('#replyText', mockReply)
.click('.coral-plugin-replies-textarea button')
.waitForElementVisible('.reply', 2000)
// Verify that it appears
.assert.containsText('.reply', mockReply);
done();
})
.catch((err) => {
console.log(err);
done();
});
});
},
'User replies to a comment with premod on': client => {
client.perform((client, done) => {
mocks.settings({moderation: 'PRE'})
// Add a mock user
.then(() => {
return mocks.users([{
displayName: 'Baby Blue',
email: 'whale@tale.sea',
password: 'krill'
}]);
})
// Add a mock preapproved comment by that user
.then((user) => {
return mocks.comments([{
body: 'Whales are not fish.',
status: 'accepted',
author_id: user.id
}]);
})
.then(() => {
// Load Page
client.resizeWindow(1200, 800)
.url(client.globals.baseUrl)
.frame('coralStreamIframe');
// Post a reply
client.waitForElementVisible('.coral-plugin-replies-reply-button', 5000)
.click('.coral-plugin-replies-reply-button')
.waitForElementVisible('#replyText')
.setValue('#replyText', mockReply)
.click('.coral-plugin-replies-textarea button')
.waitForElementVisible('#coral-notif', 1000)
// Verify that it appears
.assert.containsText('#coral-notif', 'moderation team');
done();
})
.catch((err) => {
console.log(err);
done();
});
});
},
'Total comment count premod on': client => {
client.perform((client, done) => {
client.url(client.globals.baseUrl)
.frame('coralStreamIframe');
// Verify that comment count is correct
client.waitForElementVisible('.coral-plugin-comment-count-text', 2000)
.assert.containsText('.coral-plugin-comment-count-text', '1 Comment');
done();
});
},
after: client => {
mongoose.disconnect(function(err) {
if (err) {
console.error(err);
}
});
client.end();
}
};
+23
View File
@@ -0,0 +1,23 @@
module.exports = {
'@tags': ['login', 'MODERATOR'],
before: client => {
const embedStreamPage = client.page.embedStreamPage();
const {launchUrl} = client;
client
.url(launchUrl);
embedStreamPage
.ready();
},
'Moderator logs in': client => {
const {users} = client.globals;
const embedStreamPage = client.page.embedStreamPage();
embedStreamPage
.login(users.moderator);
},
after: client => {
client.end();
}
};
+20
View File
@@ -0,0 +1,20 @@
module.exports = {
'@tags': ['flag', 'comments', 'visitor'],
before: client => {
const embedStreamPage = client.page.embedStreamPage();
embedStreamPage
.navigate()
.ready();
},
'Visitor tries to flag a comment': client => {
const embedStreamPage = client.page.embedStreamPage();
embedStreamPage
.flagComment()
.waitForElementVisible('@signInDialog', 2000);
},
after: client => {
client.end();
}
};
+20
View File
@@ -0,0 +1,20 @@
module.exports = {
'@tags': ['like', 'comments', 'visitor'],
before: client => {
const embedStreamPage = client.page.embedStreamPage();
embedStreamPage
.navigate()
.ready();
},
'Visitor tries to like a comment': client => {
const embedStreamPage = client.page.embedStreamPage();
embedStreamPage
.likeComment()
.waitForElementVisible('@signInDialog', 2000);
},
after: client => {
client.end();
}
};
+23
View File
@@ -0,0 +1,23 @@
module.exports = {
'@tags': ['signup', 'visitor'],
before: client => {
const embedStreamPage = client.page.embedStreamPage();
embedStreamPage
.navigate()
.ready();
},
'Visitor signs up': client => {
const embedStreamPage = client.page.embedStreamPage();
embedStreamPage
.signUp({
email: `visitor_${Date.now()}@test.com`,
displayName: 'visitor',
pass: 'testtest'
});
},
after: client => {
client.end();
}
};
+51
View File
@@ -0,0 +1,51 @@
/* eslint-env browser */
const jsdom = require('jsdom').jsdom;
const fs = require('fs');
const path = require('path');
// Storage Mock
function storageMock() {
const storage = {};
return {
setItem: function(key, value) {
storage[key] = value || '';
},
getItem: function(key) {
return storage[key] || null;
},
removeItem: function(key) {
delete storage[key];
},
get length() {
return Object.keys(storage).length;
},
key: function(i) {
const keys = Object.keys(storage);
return keys[i] || null;
}
};
}
global.document = jsdom(fs.readFileSync(path.resolve(__dirname, 'index.test.html')));
global.window = document.defaultView;
// these lines are required for react-mdl
global.window.CustomEvent = undefined;
require('react-mdl/extra/material');
global.Element = global.window.Element;
global.navigator = {
userAgent: 'node.js'
};
global.documentRef = document;
global.localStorage = {};
global.sessionStorage = storageMock();
global.XMLHttpRequest = storageMock();
global.Headers = function(headers) {
return headers;
};
File diff suppressed because one or more lines are too long
+38
View File
@@ -0,0 +1,38 @@
const mongoose = require('../../services/mongoose');
module.exports = {};
module.exports.waitTillConnect = function(done) {
mongoose.connection.on('open', function(err) {
if (err) {
return done(err);
}
return done();
});
};
module.exports.clearDB = function(done) {
Promise.all(Object.keys(mongoose.connection.collections).map((collection) => {
return new Promise((resolve, reject) => {
mongoose.connection.collections[collection].remove(function(err) {
if (err) {
return reject(err);
}
return resolve();
});
});
}))
.then(() => {
done();
})
.catch((err) => {
done(err);
});
};
module.exports.disconnect = function(done) {
mongoose.disconnect();
return done();
};
+7
View File
@@ -0,0 +1,7 @@
const kue = require('../services/kue');
beforeEach(() => {
// Empty the test tasks before finishing.
kue.TestQueue.splice(0, kue.TestQueue.length);
});
+7
View File
@@ -0,0 +1,7 @@
test/helpers/*.js
test
--compilers js:babel-core/register
--require ignore-styles
--recursive
--colors
--sort
+15
View File
@@ -0,0 +1,15 @@
const mongoose = require('./helpers/mongoose');
before(function(done) {
this.timeout(30000);
mongoose.waitTillConnect(done);
});
beforeEach(function(done) {
mongoose.clearDB(done);
});
after(function(done) {
mongoose.disconnect(done);
});
+25
View File
@@ -0,0 +1,25 @@
const authorization = require('../middleware/authorization');
// Add the passport middleware here before it's setup.
authorization.middleware.push((req, res, next) => {
req.user = JSON.parse(new Buffer(req.get('X-Mock-Authorization'), 'base64').toString('ascii'));
next();
});
const MockStrategy = {
/**
* Injects the new user into the request header for the mock middleware to
* interpret.
* @param {Object} user the user to inject
* @return {Object} the headers to add to the request
*/
inject(user) {
return {
'X-Mock-Authorization': new Buffer(JSON.stringify(user)).toString('base64')
};
}
};
module.exports = MockStrategy;
+148
View File
@@ -0,0 +1,148 @@
const passport = require('../../../passport');
const app = require('../../../../app');
const chai = require('chai');
const expect = chai.expect;
// Setup chai.
chai.should();
chai.use(require('chai-http'));
const AssetModel = require('../../../../models/asset');
const AssetsService = require('../../../../services/assets');
describe('/api/v1/assets', () => {
beforeEach(() => {
return AssetModel.create([
{
url: 'https://coralproject.net/news/asset1',
title: 'Asset 1',
description: 'term1',
closedAt: Date.now()
},
{
url: 'https://coralproject.net/news/asset2',
title: 'Asset 2',
description: 'term2',
closedAt: null
}
]);
});
describe('#get', () => {
it('should return all assets without a search query', () => {
return chai.request(app)
.get('/api/v1/assets')
.set(passport.inject({roles: ['ADMIN']}))
.then((res) => {
const body = res.body;
expect(body).to.have.property('count', 2);
expect(body).to.have.property('result');
const assets = body.result;
expect(assets).to.have.length(2);
});
});
it('should return assets that we search for', () => {
return chai.request(app)
.get('/api/v1/assets?search=term2')
.set(passport.inject({roles: ['ADMIN']}))
.then((res) => {
const body = res.body;
expect(body).to.have.property('count', 1);
expect(body).to.have.property('result');
const assets = body.result;
expect(assets).to.have.length(1);
const asset = assets[0];
expect(asset).to.have.property('url', 'https://coralproject.net/news/asset2');
expect(asset).to.have.property('title', 'Asset 2');
});
});
it('should not return assets that we do not search for', () => {
return chai.request(app)
.get('/api/v1/assets?search=term3')
.set(passport.inject({roles: ['ADMIN']}))
.then((res) => {
const body = res.body;
expect(body).to.have.property('count', 0);
expect(body).to.have.property('result');
expect(body.result).to.be.empty;
});
});
it('should return only closed assets', () => {
return chai.request(app)
.get('/api/v1/assets?filter=closed')
.set(passport.inject({roles: ['ADMIN']}))
.then((res) => {
const body = res.body;
expect(body).to.have.property('count', 1);
expect(body).to.have.property('result');
const assets = body.result;
expect(assets[0]).to.have.property('title', 'Asset 1');
});
});
it('should return only opened assets', () => {
return chai.request(app)
.get('/api/v1/assets?filter=open')
.set(passport.inject({roles: ['ADMIN']}))
.then((res) => {
const body = res.body;
expect(body).to.have.property('count', 1);
expect(body).to.have.property('result');
const assets = body.result;
expect(assets[0]).to.have.property('title', 'Asset 2');
});
});
});
describe('#put', () => {
it('should close the asset', function() {
const today = Date.now();
return AssetsService.findOrCreateByUrl('http://test.com')
.then((asset) => {
expect(asset).to.have.property('isClosed', null);
expect(asset).to.have.property('closedAt', null);
return chai.request(app)
.put(`/api/v1/assets/${asset.id}/status`)
.set(passport.inject({roles: ['ADMIN']}))
.send({closedAt: today});
})
.then((res) => {
expect(res).to.have.status(204);
return AssetsService.findByUrl('http://test.com');
})
.then((asset) => {
expect(asset).to.have.property('isClosed', true);
expect(asset).to.have.property('closedAt').and.to.not.equal(null);
});
});
});
});
+96
View File
@@ -0,0 +1,96 @@
const app = require('../../../../app');
const chai = require('chai');
const expect = chai.expect;
chai.use(require('chai-http'));
const UsersService = require('../../../../services/users');
describe('/api/v1/auth', () => {
describe('#get', () => {
it('should return nothing when no user is logged in', () => {
return chai.request(app)
.get('/api/v1/auth')
.then((res) => {
expect(res.status).to.be.equal(204);
expect(res).to.not.have.a.body;
});
});
});
});
const SettingsService = require('../../../../services/settings');
describe('/api/v1/auth/local', () => {
let mockUser;
beforeEach(() => {
const settings = {requireEmailConfirmation: false, wordlist: {banned: ['bad'], suspect: ['naughty']}};
return SettingsService.init(settings).then(() => {
return UsersService.createLocalUser('maria@gmail.com', 'password!', 'Maria')
.then((user) => {
mockUser = user;
});
});
});
describe('email confirmation disabled', () => {
describe('#post', () => {
it('should send back the user on a successful login', () => {
return chai.request(app)
.post('/api/v1/auth/local')
.send({email: 'maria@gmail.com', password: 'password!'})
.then((res2) => {
expect(res2).to.have.status(200);
expect(res2).to.be.json;
expect(res2.body).to.have.property('user');
expect(res2.body.user).to.have.property('displayName', 'maria');
});
});
it('should not send back the user on a unsuccessful login', () => {
return chai.request(app)
.post('/api/v1/auth/local')
.send({email: 'maria@gmail.com', password: 'password!3'})
.catch((err) => {
expect(err).to.not.be.null;
expect(err.response).to.have.status(401);
expect(err.response.body).to.have.property('message', 'not authorized');
});
});
});
});
describe('email confirmation enabled', () => {
beforeEach(() => SettingsService.init({requireEmailConfirmation: true}));
describe('#post', () => {
it('should not allow a login from a user that is not confirmed', () => {
return chai.request(app)
.post('/api/v1/auth/local')
.send({email: 'maria@gmail.com', password: 'password!'})
.catch((err) => {
err.response.should.have.status(401);
return UsersService.createEmailConfirmToken(mockUser.id, mockUser.profiles[0].id);
})
.then(UsersService.verifyEmailConfirmation)
.then(() => {
return chai.request(app)
.post('/api/v1/auth/local')
.send({email: 'maria@gmail.com', password: 'password!'});
})
.then((res) => {
expect(res).to.have.status(200);
expect(res).to.be.json;
expect(res.body).to.have.property('user');
expect(res.body.user).to.have.property('displayName', 'maria');
});
});
});
});
});
+345
View File
@@ -0,0 +1,345 @@
const passport = require('../../../passport');
const app = require('../../../../app');
const chai = require('chai');
const expect = chai.expect;
// Setup chai.
chai.should();
chai.use(require('chai-http'));
const CommentModel = require('../../../../models/comment');
const ActionModel = require('../../../../models/action');
const CommentsService = require('../../../../services/comments');
const UsersService = require('../../../../services/users');
const SettingsService = require('../../../../services/settings');
const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['bad words'], suspect: ['suspect words']}};
describe('/api/v1/comments', () => {
// Ensure that the settings are always available.
beforeEach(() => SettingsService.init(settings));
describe('#get', () => {
const comments = [{
body: 'comment 10',
asset_id: 'asset',
author_id: '123'
}, {
body: 'comment 20',
asset_id: 'asset',
author_id: '456'
}, {
body: 'comment 20',
asset_id: 'asset',
author_id: '456',
status: 'REJECTED',
status_history: [{
type: 'REJECTED'
}]
}, {
body: 'comment 30',
asset_id: '456',
status: 'ACCEPTED',
status_history: [{
type: 'ACCEPTED'
}]
}];
const users = [{
displayName: 'Ana',
email: 'ana@gmail.com',
password: '123456789'
}, {
displayName: 'Maria',
email: 'maria@gmail.com',
password: '123456789'
}];
const actions = [{
action_type: 'FLAG',
item_id: 'abc',
item_type: 'COMMENTS'
}, {
action_type: 'LIKE',
item_id: 'hij',
item_type: 'COMMENTS'
}];
beforeEach(() => {
return Promise.all([
CommentModel.create(comments).then((newComments) => {
newComments.forEach((comment, i) => {
comments[i].id = comment.id;
});
actions[0].item_id = comments[0].id;
actions[1].item_id = comments[1].id;
return ActionModel.create(actions);
}),
UsersService.createLocalUsers(users)
]);
});
it('should return only the owners published comments if the user is not an admin', () => {
return chai.request(app)
.get('/api/v1/comments?user_id=456')
.set(passport.inject({id: '456', roles: []}))
.then(res => {
expect(res).to.have.status(200);
expect(res.body.comments).to.have.length(1);
expect(res.body.comments[0]).to.have.property('author_id', '456');
});
});
it('should fail if a non-admin requests comments not owned by them', () => {
return chai.request(app)
.get('/api/v1/comments?user_id=456')
.set(passport.inject({id: '123', roles: []}))
.then((res) => {
expect(res).to.be.empty;
})
.catch((err) => {
expect(err).to.have.status(401);
});
});
it('should return all the comments', () => {
return chai.request(app)
.get('/api/v1/comments')
.set(passport.inject({roles: ['ADMIN']}))
.then((res) => {
expect(res).to.have.status(200);
});
});
it('should return all the rejected comments', () => {
return chai.request(app)
.get('/api/v1/comments?status=REJECTED')
.set(passport.inject({roles: ['ADMIN']}))
.then((res) => {
expect(res).to.have.status(200);
expect(res.body).to.have.property('comments');
expect(res.body.comments).to.have.length(1);
expect(res.body.comments[0]).to.have.property('id', comments[2].id);
});
});
it('should return all the approved comments', () => {
return chai.request(app)
.get('/api/v1/comments?status=ACCEPTED')
.set(passport.inject({roles: ['ADMIN']}))
.then((res) => {
expect(res).to.have.status(200);
expect(res.body.comments).to.have.length(1);
expect(res.body.comments[0]).to.have.property('id', comments[3].id);
});
});
it('should return all the new comments', () => {
return chai.request(app)
.get('/api/v1/comments?status=NEW')
.set(passport.inject({roles: ['ADMIN']}))
.then((res) => {
expect(res).to.have.status(200);
expect(res.body.comments).to.have.length(2);
});
});
it('should return all the flagged comments', () => {
return chai.request(app)
.get('/api/v1/comments?action_type=FLAG')
.set(passport.inject({roles: ['ADMIN']}))
.then((res) => {
expect(res).to.have.status(200);
expect(res.body.comments).to.have.length(1);
expect(res.body.comments[0]).to.have.property('id', comments[0].id);
});
});
});
});
describe('/api/v1/comments/:comment_id', () => {
const comments = [{
id: 'abc',
body: 'comment 10',
asset_id: 'asset',
author_id: '123'
}, {
id: 'def',
body: 'comment 20',
asset_id: 'asset',
author_id: '456'
}, {
id: 'hij',
body: 'comment 30',
asset_id: '456'
}];
const users = [{
displayName: 'Ana',
email: 'ana@gmail.com',
password: '123456789'
}, {
displayName: 'Maria',
email: 'maria@gmail.com',
password: '123456789'
}];
const actions = [{
action_type: 'FLAG',
item_id: 'abc',
item_type: 'COMMENTS'
}, {
action_type: 'LIKE',
item_id: 'hij',
item_type: 'COMMENTS'
}];
beforeEach(() => {
return SettingsService.init(settings).then(() => {
return Promise.all([
CommentModel.create(comments),
UsersService.createLocalUsers(users),
ActionModel.create(actions)
]);
});
});
describe('#get', () => {
it('should return the right comment for the comment_id', () => {
return chai.request(app)
.get('/api/v1/comments/abc')
.set(passport.inject({roles: ['ADMIN']}))
.then((res) => {
expect(res).to.have.status(200);
expect(res).to.have.property('body');
expect(res.body).to.have.property('body', 'comment 10');
});
});
});
describe('#delete', () => {
it('it should remove comment', () => {
return chai.request(app)
.delete('/api/v1/comments/abc')
.set(passport.inject({roles: ['ADMIN']}))
.then((res) => {
expect(res).to.have.status(204);
return CommentsService.findById('abc');
})
.then((comment) => {
expect(comment).to.be.null;
});
});
});
describe('#put', () => {
it('it should update status', function() {
return chai.request(app)
.put('/api/v1/comments/abc/status')
.set(passport.inject({roles: ['ADMIN']}))
.send({status: 'accepted'})
.then((res) => {
expect(res).to.have.status(204);
expect(res.body).to.be.empty;
});
});
it('it should not allow a non-ADMIN to update status', () => {
return chai.request(app)
.put('/api/v1/comments/abc/status')
.set(passport.inject({roles: []}))
.send({status: 'accepted'})
.then((res) => {
expect(res).to.be.empty;
})
.catch((err) => {
expect(err).to.have.property('status', 401);
});
});
});
});
describe('/api/v1/comments/:comment_id/actions', () => {
const comments = [{
id: 'abc',
body: 'comment 10',
asset_id: 'asset',
author_id: '123',
status_history: []
}, {
id: 'def',
body: 'comment 20',
asset_id: 'asset',
author_id: '456',
status: 'REJECTED',
status_history: [{
type: 'REJECTED'
}]
}, {
id: 'hij',
body: 'comment 30',
asset_id: '456',
status: 'ACCEPTED',
status_history: [{
type: 'ACCEPTED'
}]
}];
const users = [{
displayName: 'Ana',
email: 'ana@gmail.com',
password: '123456789'
}, {
displayName: 'Maria',
email: 'maria@gmail.com',
password: '123456789'
}];
const actions = [{
action_type: 'FLAG',
item_type: 'COMMENTS',
item_id: 'abc'
}, {
action_type: 'LIKE',
item_type: 'COMMENTS',
item_id: 'hij'
}];
beforeEach(() => {
return SettingsService.init(settings).then(() => {
return Promise.all([
CommentModel.create(comments),
UsersService.createLocalUsers(users),
ActionModel.create(actions)
]);
});
});
describe('#post', () => {
it('it should create an action', () => {
return chai.request(app)
.post('/api/v1/comments/abc/actions')
.set(passport.inject({id: '456', roles: ['ADMIN']}))
.send({'action_type': 'flag', 'metadata': {'reason': 'Comment 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('metadata');
expect(res.body.metadata).to.deep.equal({'reason': 'Comment is too awesome.'});
expect(res.body).to.have.property('item_id', 'abc');
});
});
});
});
+101
View File
@@ -0,0 +1,101 @@
const passport = require('../../../passport');
const app = require('../../../../app');
const chai = require('chai');
const expect = chai.expect;
// Setup chai.
chai.should();
chai.use(require('chai-http'));
const Comment = require('../../../../models/comment');
const Action = require('../../../../models/action');
const UsersService = require('../../../../services/users');
const SettingsService = require('../../../../services/settings');
const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['banned'], suspect: ['suspect']}};
describe('/api/v1/queue', () => {
const comments = [{
id: 'abc',
body: 'comment 10',
asset_id: 'asset',
author_id: '123',
status: 'REJECTED',
status_history: [{
type: 'REJECTED'
}]
}, {
id: 'def',
body: 'comment 20',
asset_id: 'asset',
author_id: '456',
status: 'PREMOD',
status_history: [{
type: 'PREMOD'
}]
}, {
id: 'hij',
body: 'comment 30',
asset_id: '456',
status: 'ACCEPTED',
status_history: [{
type: 'ACCEPTED'
}]
}];
const users = [{
displayName: 'Ana',
email: 'ana@gmail.com',
password: '123456789'
}, {
displayName: 'Maria',
email: 'maria@gmail.com',
password: '123456789'
}];
const actions = [{
action_type: 'FLAG',
item_id: 'abc',
item_type: 'COMMENTS'
}, {
action_type: 'LIKE',
item_id: 'hij',
item_type: 'COMMENTS'
}];
beforeEach(() => {
return SettingsService.init(settings).then(() => {
return UsersService.createLocalUsers(users)
.then((u) => {
comments[0].author_id = u[0].id;
comments[1].author_id = u[1].id;
comments[2].author_id = u[1].id;
return Comment.create(comments);
})
.then((c) => {
actions[0].item_id = c[0].id;
actions[1].item_id = c[1].id;
return Promise.all([
Action.create(actions),
SettingsService.init(settings)
]);
});
});
});
it('should return all the pending comments, users and actions', () => {
return chai.request(app)
.get('/api/v1/queue/comments/pending')
.set(passport.inject({roles: ['ADMIN']}))
.then((res) => {
expect(res).to.have.status(200);
expect(res.body.comments).to.have.length(1);
expect(res.body.comments[0]).to.have.property('body');
expect(res.body.users[0]).to.have.property('displayName');
expect(res.body.actions[0]).to.have.property('action_type');
});
});
});
+51
View File
@@ -0,0 +1,51 @@
const passport = require('../../../passport');
const app = require('../../../../app');
const chai = require('chai');
const expect = chai.expect;
chai.should();
chai.use(require('chai-http'));
const SettingsService = require('../../../../services/settings');
const defaults = {id: '1', moderation: 'PRE'};
describe('/api/v1/settings', () => {
beforeEach(() => SettingsService.init(defaults));
describe('#get', () => {
it('should return a settings object', () => {
return chai.request(app)
.get('/api/v1/settings')
.set(passport.inject({
roles: ['ADMIN']
}))
.then((res) => {
expect(res).to.have.status(200);
expect(res).to.be.json;
expect(res.body).to.have.property('moderation', 'PRE');
});
});
});
describe('#put', () => {
it('should update the settings', () => {
return chai.request(app)
.put('/api/v1/settings')
.set(passport.inject({roles: ['ADMIN']}))
.send({moderation: 'POST'})
.then((res) => {
expect(res).to.have.status(204);
return SettingsService.retrieve();
})
.then((settings) => {
expect(settings).to.have.property('moderation', 'POST');
});
});
});
});
+87
View File
@@ -0,0 +1,87 @@
const passport = require('../../../passport');
const app = require('../../../../app');
const mailer = require('../../../../services/mailer');
const chai = require('chai');
const expect = chai.expect;
const SettingsService = require('../../../../services/settings');
const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['bad words'], suspect: ['suspect words']}};
// Setup chai.
chai.should();
chai.use(require('chai-http'));
const UsersService = require('../../../../services/users');
describe('/api/v1/users/:user_id/email/confirm', () => {
let mockUser;
beforeEach(() => SettingsService.init(settings).then(() => {
return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana');
})
.then((user) => {
mockUser = user;
}));
describe('#post', () => {
it('should send an email when we hit the endpoint', () => {
expect(mailer.task.tasks).to.have.length(0);
return chai.request(app)
.post(`/api/v1/users/${mockUser.id}/email/confirm`)
.set(passport.inject({roles: ['ADMIN']}))
.then((res) => {
expect(res).to.have.status(204);
expect(mailer.task.tasks).to.have.length(1);
});
});
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']}))
.then((res) => {
expect(res).to.have.status(204);
expect(mailer.task.tasks).to.have.length(1);
});
});
});
});
describe('/api/v1/users/:user_id/actions', () => {
const users = [{
displayName: 'Ana',
email: 'ana@gmail.com',
password: '123456789'
}, {
displayName: 'Maria',
email: 'maria@gmail.com',
password: '123456789'
}];
beforeEach(() => {
return SettingsService.init(settings).then(() => {
return UsersService.createLocalUsers(users);
});
});
describe('#post', () => {
it('it should update actions', () => {
return chai.request(app)
.post('/api/v1/users/abc/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('metadata')
.and.to.deep.equal({'reason': 'Bio is too awesome.'});
expect(res.body).to.have.property('item_id', 'abc');
});
});
});
});
+107
View File
@@ -0,0 +1,107 @@
const ActionModel = require('../../models/action');
const ActionsService = require('../../services/actions');
const expect = require('chai').expect;
describe('services.ActionsService', () => {
let mockActions = [];
beforeEach(() => ActionModel.create([
{
action_type: 'FLAG',
item_id: '123',
item_type: 'COMMENTS',
user_id: 'flagginguserid'
},
{
action_type: 'FLAG',
item_id: '456',
item_type: 'COMMENTS'
},
{
action_type: 'FLAG',
item_id: '123',
item_type: 'COMMENTS'
},
{
action_type: 'LIKE',
item_id: '123',
item_type: 'COMMENTS'
}
]).then((actions) => {
mockActions = actions;
}));
describe('#findById()', () => {
it('should find an action by id', () => {
return ActionsService.findById(mockActions[0].id).then((result) => {
expect(result).to.not.be.null;
expect(result).to.have.property('action_type', 'FLAG');
});
});
});
describe('#findByItemIdArray()', () => {
it('should find an array of actions from an array of item_ids', () => {
return ActionsService.findByItemIdArray(['123', '456']).then((result) => {
expect(result).to.have.length(4);
});
});
});
describe('#getActionSummaries()', () => {
it('should return properly formatted summaries from an array of item_ids', () => {
return ActionsService
.getActionSummaries(['123', '789'])
.then((summaries) => {
expect(summaries).to.have.length(2);
expect(summaries).to.deep.include({
action_type: 'LIKE',
count: 1,
item_id: '123',
item_type: 'COMMENTS',
current_user: null
});
expect(summaries).to.deep.include({
action_type: 'FLAG',
count: 2,
item_id: '123',
item_type: 'COMMENTS',
current_user: null
});
});
});
it('should include a current user when one is passed', () => {
return ActionsService
.getActionSummaries(['123'], 'flagginguserid')
.then((summaries) => {
expect(summaries).to.have.length(2);
let summary = summaries.find((s) => s.item_id === '123' && s.action_type === 'FLAG');
expect(summary).to.not.be.undefined;
expect(summary.current_user).to.not.be.null;
expect(summary.current_user).to.have.property('item_id', '123');
expect(summary.current_user).to.have.property('item_type', 'COMMENTS');
expect(summary.current_user).to.have.property('user_id', 'flagginguserid');
expect(summary.current_user).to.have.property('action_type', 'FLAG');
});
});
it('should not include a current user when one is passed for a user that doesn\'t have an action', () => {
return ActionsService
.getActionSummaries(['123'], 'flagginguserid2')
.then((summaries) => {
expect(summaries).to.have.length(2);
summaries.forEach((summary) => {
expect(summary).to.not.be.undefined;
expect(summary).to.have.property('current_user', null);
});
});
});
});
});
+107
View File
@@ -0,0 +1,107 @@
const AssetModel = require('../../models/asset');
const AssetsService = require('../../services/assets');
const chai = require('chai');
const expect = chai.expect;
// Use the chai should.
chai.should();
describe('services.AssetsService', () => {
beforeEach(() => {
const defaults = {url:'http://test.com'};
return AssetModel.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true});
});
describe('#findById', ()=> {
it('should find an asset by the id', () => {
return AssetsService.findById(1)
.then((asset) => {
expect(asset).to.have.property('url')
.and.to.equal('http://test.com');
});
});
});
describe('#findByUrl', ()=> {
beforeEach(() => AssetsService.findOrCreateByUrl('http://test.com'));
it('should find an asset by a url', () => {
return AssetsService
.findByUrl('http://test.com')
.then((asset) => {
expect(asset).to.have.property('url', 'http://test.com');
});
});
it('should return null when a url does not exist', () => {
return AssetsService
.findByUrl('http://new.test.com')
.then((asset) => {
expect(asset).to.be.null;
});
});
});
describe('#findOrCreateByUrl', ()=> {
it('should find an asset by a url', () => {
return AssetsService
.findOrCreateByUrl('http://test.com')
.then((asset) => {
expect(asset).to.have.property('url')
.and.to.equal('http://test.com');
});
});
it('should return a new asset when the url does not exist', () => {
return AssetsService
.findOrCreateByUrl('http://new.test.com')
.then((asset) => {
expect(asset).to.have.property('id')
.and.to.not.equal(1);
});
});
});
describe('#overrideSettings', () => {
it('should update the settings', () => {
return AssetsService
.findOrCreateByUrl('https://override.test.com/asset')
.then((asset) => {
expect(asset).to.have.property('settings');
expect(asset.settings).to.be.null;
return AssetsService.overrideSettings(asset.id, {moderation: 'PRE'});
})
.then(() => {
return AssetsService.findOrCreateByUrl('https://override.test.com/asset');
})
.then((asset) => {
expect(asset).to.have.property('settings');
expect(asset.settings).is.an('object');
expect(asset.settings).to.have.property('moderation', 'PRE');
});
});
});
describe('#findOrCreateByUrl', ()=> {
it('should find an asset by a url', () => {
return AssetsService
.findOrCreateByUrl('http://test.com')
.then((asset) => {
expect(asset).to.have.property('url')
.and.to.equal('http://test.com');
});
});
it('should return a new asset when the url does not exist', () => {
return AssetsService
.findOrCreateByUrl('http://new.test.com')
.then((asset) => {
expect(asset).to.have.property('id')
.and.to.not.equal(1);
});
});
});
});
+259
View File
@@ -0,0 +1,259 @@
const CommentModel = require('../../models/comment');
const ActionModel = require('../../models/action');
const ActionsService = require('../../services/actions');
const UsersService = require('../../services/users');
const SettingsService = require('../../services/settings');
const CommentsService = require('../../services/comments');
const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['bad words'], suspect: ['suspect words']}};
const expect = require('chai').expect;
describe('services.CommentsService', () => {
const comments = [{
body: 'comment 10',
asset_id: '123',
status_history: [],
parent_id: '',
author_id: '123',
id: '1'
}, {
body: 'comment 20',
asset_id: '123',
status_history: [{
type: 'ACCEPTED'
}],
status: 'ACCEPTED',
parent_id: '',
author_id: '123',
id: '2'
}, {
body: 'comment 30',
asset_id: '456',
status_history: [],
parent_id: '',
author_id: '456',
id: '3'
}, {
body: 'comment 40',
asset_id: '123',
status_history: [{
type: 'REJECTED'
}],
status: 'REJECTED',
parent_id: '',
author_id: '456',
id: '4'
}, {
body: 'comment 50',
asset_id: '1234',
status_history: [{
type: 'PREMOD'
}],
status: 'PREMOD',
parent_id: '',
author_id: '456',
id: '5'
}, {
body: 'comment 60',
asset_id: '1234',
status_history: [{
type: 'PREMOD'
}],
status: 'PREMOD',
parent_id: '',
author_id: '456',
id: '6'
}];
const users = [{
email: 'stampi@gmail.com',
displayName: 'Stampi',
password: '1Coral!!'
}, {
email: 'sockmonster@gmail.com',
displayName: 'Sockmonster',
password: '2Coral!!'
}];
const actions = [{
action_type: 'FLAG',
item_id: '3',
item_type: 'COMMENTS',
user_id: '123'
}, {
action_type: 'LIKE',
item_id: '1',
item_type: 'COMMENTS',
user_id: '456'
}];
beforeEach(() => {
return SettingsService.init(settings).then(() => {
return Promise.all([
CommentModel.create(comments),
UsersService.createLocalUsers(users),
ActionModel.create(actions)
]);
});
});
describe('#publicCreate()', () => {
it('creates a new comment', () => {
return CommentsService
.publicCreate({
body: 'This is a comment!',
status: 'ACCEPTED'
}).then((c) => {
expect(c).to.not.be.null;
expect(c.id).to.not.be.null;
expect(c.id).to.be.uuid;
expect(c.status).to.be.equal('ACCEPTED');
});
});
it('creates many new comments', () => {
return CommentsService
.publicCreate([{
body: 'This is a comment!',
status: 'ACCEPTED'
}, {
body: 'This is another comment!'
}, {
body: 'This is a rejected comment!',
status: 'REJECTED'
}]).then(([c1, c2, c3]) => {
expect(c1).to.not.be.null;
expect(c1.id).to.be.uuid;
expect(c1.status).to.be.equal('ACCEPTED');
expect(c2).to.not.be.null;
expect(c2.id).to.be.uuid;
expect(c2.status).to.be.null;
expect(c3).to.not.be.null;
expect(c3.id).to.be.uuid;
expect(c3.status).to.be.equal('REJECTED');
});
});
});
describe('#findById()', () => {
it('should find a comment by id', () => {
return CommentsService
.findById('1')
.then((result) => {
expect(result).to.not.be.null;
expect(result).to.have.property('body', 'comment 10');
});
});
});
describe('#findByAssetId()', () => {
it('should find an array of all comments by asset id', () => {
return CommentsService
.findByAssetId('123')
.then((result) => {
expect(result).to.have.length(3);
result.sort((a, b) => {
if (a.body < b.body) {return -1;}
else {return 1;}
});
expect(result[0]).to.have.property('body', 'comment 10');
expect(result[1]).to.have.property('body', 'comment 20');
expect(result[2]).to.have.property('body', 'comment 40');
});
});
});
describe('#moderationQueue()', () => {
it('should find an array of new comments to moderate when pre-moderation', () => {
return CommentsService
.moderationQueue('PREMOD')
.then((result) => {
expect(result).to.not.be.null;
expect(result).to.have.lengthOf(2);
});
});
});
describe('#removeAction', () => {
it('should remove an action', () => {
return CommentsService
.removeAction('3', '123', 'flag')
.then(() => {
return ActionsService.findByItemIdArray(['123']);
})
.then((actions) => {
expect(actions.length).to.equal(0);
});
});
});
describe('#findByUserId', () => {
it('should return all comments if admin', () => {
return CommentsService
.findByUserId('456', true)
.then(comments => {
expect(comments).to.have.length(4);
});
});
it('should not return premod and rejected comments if not admin', () => {
return CommentsService
.findByUserId('456')
.then(comments => {
expect(comments).to.have.length(1);
});
});
});
describe('#changeStatus', () => {
it('should change the status of a comment from no status', () => {
let comment_id = comments[0].id;
return CommentsService.findById(comment_id)
.then((c) => {
expect(c.status).to.be.null;
return CommentsService.pushStatus(comment_id, 'REJECTED', '123');
})
.then(() => CommentsService.findById(comment_id))
.then((c) => {
expect(c).to.have.property('status');
expect(c.status).to.equal('REJECTED');
expect(c.status_history).to.have.length(1);
expect(c.status_history[0]).to.have.property('type', 'REJECTED');
expect(c.status_history[0]).to.have.property('assigned_by', '123');
});
});
it('should change the status of a comment from accepted', () => {
return CommentsService.pushStatus(comments[1].id, 'REJECTED', '123')
.then(() => CommentsService.findById(comments[1].id))
.then((c) => {
expect(c).to.have.property('status_history');
expect(c).to.have.property('status');
expect(c.status).to.equal('REJECTED');
expect(c.status_history).to.have.length(2);
expect(c.status_history[0]).to.have.property('type', 'ACCEPTED');
expect(c.status_history[0]).to.have.property('assigned_by', null);
expect(c.status_history[1]).to.have.property('type', 'REJECTED');
expect(c.status_history[1]).to.have.property('assigned_by', '123');
});
});
});
});
+22
View File
@@ -0,0 +1,22 @@
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');
});
});
+56
View File
@@ -0,0 +1,56 @@
const SettingsService = require('../../services/settings');
const expect = require('chai').expect;
describe('services.SettingsService', () => {
beforeEach(() => SettingsService.init({moderation: 'PRE', wordlist: ['donut']}));
describe('#retrieve()', () => {
it('should have a moderation field defined', () => {
return SettingsService.retrieve().then(settings => {
expect(settings).to.have.property('moderation').and.to.equal('PRE');
});
});
it('should have two infoBox fields defined', () => {
return SettingsService.retrieve().then(settings => {
expect(settings).to.have.property('infoBoxEnable').and.to.equal(false);
expect(settings).to.have.property('infoBoxContent').and.to.equal('');
});
});
});
describe('#update()', () => {
it('should update the settings with a passed object', () => {
const mockSettings = {moderation: 'POST', infoBoxEnable: true, infoBoxContent: 'yeah'};
return SettingsService.update(mockSettings).then(updatedSettings => {
expect(updatedSettings).to.be.an('object');
expect(updatedSettings).to.have.property('moderation').and.to.equal('POST');
expect(updatedSettings).to.have.property('infoBoxEnable', true);
expect(updatedSettings).to.have.property('infoBoxContent', 'yeah');
});
});
});
describe('#get', () => {
it('should return the moderation settings', () => {
return SettingsService.retrieve().then(({moderation}) => {
expect(moderation).not.to.be.null;
});
});
});
describe('#merge', () => {
it('should merge a settings object and its overrides', () => {
return SettingsService
.retrieve()
.then((settings) => {
let ovrSett = {moderation: 'POST'};
settings.merge(ovrSett);
expect(settings).to.have.property('moderation', 'POST');
});
});
});
});
+211
View File
@@ -0,0 +1,211 @@
const UsersService = require('../../services/users');
const SettingsService = require('../../services/settings');
const expect = require('chai').expect;
describe('services.UsersService', () => {
let mockUsers;
beforeEach(() => {
const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['bad words'], suspect: ['suspect words']}};
return SettingsService.init(settings).then(() => {
return UsersService.createLocalUsers([{
email: 'stampi@gmail.com',
displayName: 'Stampi',
password: '1Coral!-'
}, {
email: 'sockmonster@gmail.com',
displayName: 'Sockmonster',
password: '2Coral!2'
}, {
email: 'marvel@gmail.com',
displayName: 'Marvel',
password: '3Coral!3'
}]).then((users) => {
mockUsers = users;
});
});
});
describe('#findById()', () => {
it('should find a user by id', () => {
return UsersService
.findById(mockUsers[0].id)
.then((user) => {
expect(user).to.have.property('displayName', 'stampi');
});
});
});
describe('#findByIdArray()', () => {
it('should find an array of users from an array of ids', () => {
const ids = mockUsers.map((user) => user.id);
return UsersService.findByIdArray(ids).then((result) => {
expect(result).to.have.length(3);
});
});
});
describe('#findPublicByIdArray()', () => {
it('should find an array of users from an array of ids', () => {
const ids = mockUsers.map((user) => user.id);
return UsersService.findPublicByIdArray(ids).then((result) => {
expect(result).to.have.length(3);
const sorted = result.sort((a, b) => {
if(a.displayName < b.displayName) {return -1;}
if(a.displayName > b.displayName) {return 1;}
return 0;
});
expect(sorted[0]).to.have.property('displayName', 'marvel');
});
});
});
describe('#findLocalUser', () => {
it('should find a user when we give the right credentials', () => {
return UsersService
.findLocalUser(mockUsers[0].profiles[0].id, '1Coral!-')
.then((user) => {
expect(user).to.have.property('displayName')
.and.to.equal(mockUsers[0].displayName.toLowerCase());
});
});
it('should not find the user when we give the wrong credentials', () => {
return UsersService
.findLocalUser(mockUsers[0].profiles[0].id, '1Coral!-<nope>')
.then((user) => {
expect(user).to.equal(false);
});
});
});
describe('#createLocalUser', () => {
it('should not create a user with duplicate display name', () => {
return UsersService.createLocalUsers([{
email: 'otrostampi@gmail.com',
displayName: 'StampiTheSecond',
password: '1Coralito!'
}])
.then((user) => {
expect(user).to.be.null;
})
.catch((error) => {
expect(error).to.not.be.null;
});
});
});
describe('#createEmailConfirmToken', () => {
it('should create a token for a valid user', () => {
return UsersService
.createEmailConfirmToken(mockUsers[0].id, mockUsers[0].profiles[0].id)
.then((token) => {
expect(token).to.not.be.null;
});
});
it('should not create a token for a user already verified', () => {
return UsersService
.createEmailConfirmToken(mockUsers[0].id, mockUsers[0].profiles[0].id)
.then((token) => {
expect(token).to.not.be.null;
return UsersService.verifyEmailConfirmation(token);
})
.then(() => {
return UsersService.createEmailConfirmToken(mockUsers[0].id, mockUsers[0].profiles[0].id);
})
.catch((err) => {
expect(err).to.have.property('message', 'email address already confirmed');
});
});
});
describe('#verifyEmailConfirmation', () => {
it('should correctly validate a valid token', () => {
return UsersService
.createEmailConfirmToken(mockUsers[0].id, mockUsers[0].profiles[0].id)
.then((token) => {
expect(token).to.not.be.null;
return UsersService.verifyEmailConfirmation(token);
});
});
it('should correctly reject an invalid token', () => {
return UsersService
.verifyEmailConfirmation('cats')
.catch((err) => {
expect(err).to.not.be.null;
});
});
it('should update the user model when verification is complete', () => {
return UsersService
.createEmailConfirmToken(mockUsers[0].id, mockUsers[0].profiles[0].id)
.then((token) => {
expect(token).to.not.be.null;
return UsersService.verifyEmailConfirmation(token);
})
.then(() => {
return UsersService.findById(mockUsers[0].id);
})
.then((user) => {
expect(user.profiles[0]).to.have.property('metadata');
expect(user.profiles[0].metadata).to.have.property('confirmed_at');
expect(user.profiles[0].metadata.confirmed_at).to.not.be.null;
});
});
});
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');
});
});
});
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');
});
});
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');
});
});
});
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');
});
});
});
});
+96
View File
@@ -0,0 +1,96 @@
const expect = require('chai').expect;
const Errors = require('../../errors');
const Wordlist = require('../../services/wordlist');
const SettingsService = require('../../services/settings');
describe('services.Wordlist', () => {
const wordlists = {
banned: [
'cookies',
'how to do bad things',
'how to do really bad things'
],
suspect: [
'do bad things'
]
};
let wordlist = new Wordlist();
const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['bad words'], suspect: ['suspect words']}};
beforeEach(() => SettingsService.init(settings));
describe('#init', () => {
before(() => wordlist.upsert(wordlists));
it('has entries', () => {
expect(wordlist.lists.banned).to.not.be.empty;
expect(wordlist.lists.suspect).to.not.be.empty;
});
});
describe('#match', () => {
const bannedList = Wordlist.parseList(wordlists.banned);
it('does match on a bad word', () => {
[
'how to do really bad things',
'what is cookies',
'cookies',
'COOKIES.',
'how to do bad things',
'How To do bad things!'
].forEach((word) => {
expect(wordlist.match(bannedList, word)).to.be.true;
});
});
it('does not match on a good word', () => {
[
'how to',
'cookie',
'how to be a great person?',
'how to not do really bad things?'
].forEach((word) => {
expect(wordlist.match(bannedList, word)).to.be.false;
});
});
});
describe('#filter', () => {
before(() => wordlist.upsert(wordlists));
it('matches on bodies containing bad words', () => {
let errors = wordlist.filter({
content: 'how to do really bad things?'
}, 'content');
expect(errors).to.have.property('banned', Errors.ErrContainsProfanity);
});
it('does not match on bodies not containing bad words', () => {
let errors = wordlist.filter({
content: 'how to not do really bad things?'
}, 'content');
expect(errors).to.not.have.property('banned');
});
it('does not match on bodies not containing the bad word field', () => {
let errors = wordlist.filter({
author: 'how to do really bad things?',
content: 'how to be a great person?'
}, 'content');
expect(errors).to.not.have.property('banned');
});
});
});