Merge branch 'master' into email-confirm

This commit is contained in:
Wyatt Johnson
2017-01-05 18:45:06 -07:00
41 changed files with 246 additions and 205 deletions
+1
View File
@@ -11,3 +11,4 @@ dump.rdb
.env
gaba.cfg
.idea/
coverage/
+24
View File
@@ -7,6 +7,7 @@ const passport = require('./services/passport');
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
const redis = require('./services/redis');
const csrf = require('csurf');
const app = express();
@@ -73,6 +74,29 @@ app.use(session(session_opts));
app.use(passport.initialize());
app.use(passport.session());
//==============================================================================
// CSRF MIDDLEWARE
//==============================================================================
if (process.env.TEST_MODE === 'unit') {
// Add this fake test token in the event we are in unit test mode, and don't
// include the CSRF protection.
app.locals.csrfToken = 'UNIT_TESTS';
} else {
// Setup route middlewares for CSRF protection.
// Default ignore methods are GET, HEAD, OPTIONS
app.use(csrf({}));
app.use((req, res, next) => {
res.locals.csrfToken = req.csrfToken();
next();
});
}
//==============================================================================
// ROUTES
//==============================================================================
+3 -3
View File
@@ -10,9 +10,9 @@ const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error});
export const checkLogin = () => dispatch => {
dispatch(checkLoginRequest());
coralApi('/auth')
.then(user => {
const isAdmin = !!user.roles.filter(i => i === 'admin').length;
dispatch(checkLoginSuccess(user, isAdmin));
.then(result => {
const isAdmin = !!result.user.roles.filter(i => i === 'admin').length;
dispatch(checkLoginSuccess(result.user, isAdmin));
})
.catch(error => dispatch(checkLoginFailure(error)));
};
+3 -3
View File
@@ -34,9 +34,9 @@ export const fetchModerationQueueComments = () => {
// Create a new comment
export const createComment = (name, body) => {
return dispatch => {
const comment = {body, name};
return coralApi('/comments', {method: 'POST', comment})
return (dispatch) => {
const formData = {body, name};
return coralApi('/comments', {method: 'POST', body: formData})
.then(res => dispatch({type: commentTypes.COMMENT_CREATE_SUCCESS, comment: res}))
.catch(error => dispatch({type: commentTypes.COMMENT_CREATE_FAILED, error}));
};
+3 -2
View File
@@ -41,14 +41,15 @@ export const newPage = () => ({
type: COMMENTERS_NEW_PAGE
});
export const setRole = (id, role) => dispatch => {
export const setRole = (id, role) => (dispatch) => {
return coralApi(`/users/${id}/role`, {method: 'POST', body: {role}})
.then(() => {
return dispatch({type: SET_ROLE, id, role});
});
};
export const setCommenterStatus = (id, status) => dispatch => {
export const setCommenterStatus = (id, status) => (dispatch) => {
return coralApi(`/users/${id}/status`, {method: 'POST', body: {status}})
.then(() => {
return dispatch({type: SET_COMMENTER_STATUS, id, status});
+1 -1
View File
@@ -6,7 +6,7 @@ import * as actions from '../constants/user';
*/
// change status of a user
export const userStatusUpdate = (status, userId, commentId) => {
return dispatch => {
return (dispatch) => {
dispatch({type: actions.UPDATE_STATUS_REQUEST});
return coralApi(`/users/${userId}/status`, {method: 'POST', body: {status: status, comment_id: commentId}})
.then(res => dispatch({type: actions.UPDATE_STATUS_SUCCESS, res}))
@@ -1,45 +1,46 @@
import React from 'react';
import {Dialog} from 'coral-ui';
import Button from 'coral-ui/components/Button';
import styles from './BanUserDialog.css';
import Button from 'coral-ui/components/Button';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
const lang = new I18n(translations);
const BanUserDialog = ({open, handleClose, onClickBanUser, user = {}}) => {
const {userName = '', userId = '', commentId = ''} = user;
return (
<Dialog className={styles.dialog} open={open} onClose={() => handleClose()} onCancel={() => handleClose()} title={lang.t('bandialog.ban_user')}>
<span className={styles.close} onClick={() => handleClose()}>×</span>
<div>
<div className={styles.header}>
<h3>
{lang.t('bandialog.ban_user')}
</h3>
</div>
<div className={styles.separator}>
<h4>
{lang.t('bandialog.are_you_sure', userName)}
</h4>
<i>
{lang.t('bandialog.note')}
</i>
</div>
<div className={styles.buttons}>
<Button cStyle="cancel" className={styles.cancel} onClick={() => handleClose()} full>
{lang.t('bandialog.cancel')}
</Button>
<Button cStyle="black" onClick={() => onClickBanUser(userId, commentId)} full>
{lang.t('bandialog.yes_ban_user')}
</Button>
</div>
const BanUserDialog = ({open, handleClose, onClickBanUser, user = {}}) => (
<Dialog
className={styles.dialog}
id="banuserDialog"
open={open}
onClose={() => handleClose()}
onCancel={() => handleClose()}
title={lang.t('bandialog.ban_user')}>
<span className={styles.close} onClick={handleClose}>×</span>
<div>
<div className={styles.header}>
<h3>
{lang.t('bandialog.ban_user')}
</h3>
</div>
<div className={styles.separator}>
<h4>
{lang.t('bandialog.are_you_sure', user.userName)}
</h4>
<i>
{lang.t('bandialog.note')}
</i>
</div>
<div className={styles.buttons}>
<Button cStyle="cancel" className={styles.cancel} onClick={() => handleClose()} full>
{lang.t('bandialog.cancel')}
</Button>
<Button cStyle="black" onClick={() => onClickBanUser(user.userId, user.commentId)} full>
{lang.t('bandialog.yes_ban_user')}
</Button>
</div>
</div>
</Dialog>
);
};
);
export default BanUserDialog;
@@ -21,12 +21,12 @@ export default class CommentList extends React.Component {
comments: PropTypes.object.isRequired,
users: PropTypes.object.isRequired,
onClickAction: PropTypes.func,
modActions: PropTypes.arrayOf(PropTypes.string),
// list of actions (flags, etc) associated with the comments
modActions: PropTypes.arrayOf(PropTypes.string).isRequired,
loading: PropTypes.bool,
// list of actions (flags, etc) associated with the comments
actions: PropTypes.arrayOf(PropTypes.string),
suspectWords: PropTypes.arrayOf(PropTypes.string)
suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired
}
constructor (props) {
+2
View File
@@ -2,6 +2,8 @@ export const CHECK_LOGIN_REQUEST = 'CHECK_LOGIN_REQUEST';
export const CHECK_LOGIN_SUCCESS = 'CHECK_LOGIN_SUCCESS';
export const CHECK_LOGIN_FAILURE = 'CHECK_LOGIN_FAILURE';
export const CHECK_CSRF_TOKEN = 'CHECK_CSRF_TOKEN';
export const LOGOUT_REQUEST = 'LOGOUT_REQUEST';
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
export const LOGOUT_FAILURE = 'LOGOUT_FAILURE';
@@ -43,7 +43,7 @@ class CommentStream extends React.Component {
render ({comments, users}, {snackbar, snackbarMsg}) {
return (
<div className={styles.container}>
<CommentBox onSubmit={this.onSubmit} />
<CommentBox onSubmit={this.onSubmit}/>
<CommentList isActive hideActive
singleView={false}
commentIds={comments.ids}
@@ -9,6 +9,7 @@ import {
fetchModerationQueueComments
} from 'actions/comments';
import {userStatusUpdate} from 'actions/users';
import {fetchSettings} from 'actions/settings';
import ModerationQueue from './ModerationQueue';
@@ -29,6 +30,7 @@ class ModerationContainer extends React.Component {
componentWillMount() {
this.props.fetchModerationQueueComments();
this.props.fetchSettings();
key('s', () => this.setState({singleView: !this.state.singleView}));
key('shift+/', () => this.setState({modalOpen: true}));
key('esc', () => this.setState({modalOpen: false}));
@@ -80,11 +82,13 @@ class ModerationContainer extends React.Component {
const mapStateToProps = state => ({
comments: state.comments.toJS(),
settings: state.settings.toJS(),
users: state.users.toJS()
});
const mapDispatchToProps = dispatch => {
return {
fetchSettings: () => dispatch(fetchSettings()),
fetchModerationQueueComments: () => dispatch(fetchModerationQueueComments()),
showBanUserDialog: (userId, userName, commentId) => dispatch(showBanUserDialog(userId, userName, commentId)),
hideBanUserDialog: () => dispatch(hideBanUserDialog(false)),
@@ -23,6 +23,7 @@ export default ({onTabClick, ...props}) => (
</div>
<div className={`mdl-tabs__panel is-active ${styles.listContainer}`} id='pending'>
<CommentList
suspectWords={props.settings.settings.wordlist.suspect}
isActive={props.activeTab === 'pending'}
singleView={props.singleView}
commentIds={props.premodIds}
@@ -30,7 +31,7 @@ export default ({onTabClick, ...props}) => (
users={props.users.byId}
onClickAction={props.updateStatus}
onClickShowBanDialog={props.showBanUserDialog}
actions={['reject', 'approve', 'ban']}
modActions={['reject', 'approve', 'ban']}
loading={props.comments.loading}/>
<BanUserDialog
open={props.comments.showBanUserDialog}
@@ -41,25 +42,27 @@ export default ({onTabClick, ...props}) => (
</div>
<div className={`mdl-tabs__panel ${styles.listContainer}`} id='rejected'>
<CommentList
suspectWords={props.settings.settings.wordlist.suspect}
isActive={props.activeTab === 'rejected'}
singleView={props.singleView}
commentIds={props.rejectedIds}
comments={props.comments.byId}
users={props.users.byId}
onClickAction={props.updateStatus}
actions={['approve']}
modActions={['approve']}
loading={props.comments.loading}
/>
</div>
<div className={`mdl-tabs__panel ${styles.listContainer}`} id='flagged'>
<CommentList
suspectWords={props.settings.settings.wordlist.suspect}
isActive={props.activeTab === 'rejected'}
singleView={props.singleView}
commentIds={props.flaggedIds}
comments={props.comments.byId}
users={props.users.byId}
onClickAction={props.updateStatus}
actions={['reject', 'approve']}
modActions={['reject', 'approve']}
loading={props.comments.loading}/>
</div>
<ModerationKeysModal open={props.modalOpen} onClose={props.closeModal} />
+1 -1
View File
@@ -37,7 +37,7 @@ const updateSettings = (state, action) => {
// any nested settings must have a specialized setter
const updateWordlist = (state, action) => {
return state.setIn(['settings', 'wordlist', action.listName], action.wordlist);
return state.setIn(['settings', 'wordlist', action.listName], action.list);
};
const saveComplete = (state, action) => {
@@ -138,7 +138,7 @@ class CommentStream extends Component {
</div>
: <p>{closedMessage}</p>
}
{!loggedIn && <SignInContainer offset={signInOffset} />}
{!loggedIn && <SignInContainer offset={signInOffset}/>}
{
rootItem.comments && rootItem.comments.map((commentId) => {
const comment = comments[commentId];
+8 -7
View File
@@ -23,7 +23,7 @@ const signInRequest = () => ({type: actions.FETCH_SIGNIN_REQUEST});
const signInSuccess = (user, isAdmin) => ({type: actions.FETCH_SIGNIN_SUCCESS, user, isAdmin});
const signInFailure = error => ({type: actions.FETCH_SIGNIN_FAILURE, error});
export const fetchSignIn = (formData) => dispatch => {
export const fetchSignIn = (formData) => (dispatch) => {
dispatch(signInRequest());
coralApi('/auth/local', {method: 'POST', body: formData})
.then(({user}) => {
@@ -72,8 +72,9 @@ const signUpRequest = () => ({type: actions.FETCH_SIGNUP_REQUEST});
const signUpSuccess = user => ({type: actions.FETCH_SIGNUP_SUCCESS, user});
const signUpFailure = error => ({type: actions.FETCH_SIGNUP_FAILURE, error});
export const fetchSignUp = formData => dispatch => {
export const fetchSignUp = formData => (dispatch) => {
dispatch(signUpRequest());
coralApi('/users', {method: 'POST', body: formData})
.then(({user}) => {
dispatch(signUpSuccess(user));
@@ -90,7 +91,7 @@ const forgotPassowordRequest = () => ({type: actions.FETCH_FORGOT_PASSWORD_REQUE
const forgotPassowordSuccess = () => ({type: actions.FETCH_FORGOT_PASSWORD_SUCCESS});
const forgotPassowordFailure = () => ({type: actions.FETCH_FORGOT_PASSWORD_FAILURE});
export const fetchForgotPassword = email => dispatch => {
export const fetchForgotPassword = email => (dispatch) => {
dispatch(forgotPassowordRequest(email));
coralApi('/account/password/reset', {method: 'POST', body: {email}})
.then(() => dispatch(forgotPassowordSuccess()))
@@ -124,13 +125,13 @@ const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error});
export const checkLogin = () => dispatch => {
dispatch(checkLoginRequest());
coralApi('/auth')
.then(user => {
if (!user) {
.then((result) => {
if (!result.user) {
throw new Error('Not logged in');
}
const isAdmin = !!user.roles.filter(i => i === 'admin').length;
dispatch(checkLoginSuccess(user, isAdmin));
const isAdmin = !!result.user.roles.filter(i => i === 'admin').length;
dispatch(checkLoginSuccess(result.user, isAdmin));
})
.catch(error => dispatch(checkLoginFailure(error)));
};
+6 -2
View File
@@ -220,8 +220,12 @@ export function postItem (item, type, id) {
*/
export function postAction (item_id, item_type, action) {
return () => {
return coralApi(`/${item_type}/${item_id}/actions`, {method: 'POST', body: action});
return (dispatch) => {
return coralApi(`/${item_type}/${item_id}/actions`, {method: 'POST', body: action})
.then((json) => {
dispatch(updateItem(action.item_id, action.action_type, action.id, item_type));
return json;
});
};
}
-2
View File
@@ -42,8 +42,6 @@ export const fetchCommentsByUserId = userId => {
dispatch({type: assetActions.MULTIPLE_ASSETS_SUCCESS, assets: assets.map(asset => asset.id)});
})
.catch(error => {
console.error(error.stack);
console.error('FAILURE_COMMENTS_BY_USER', error);
dispatch({type: actions.COMMENTS_BY_USER_FAILURE, error});
});
};
+1
View File
@@ -31,3 +31,4 @@ export const CHECK_LOGIN_REQUEST = 'CHECK_LOGIN_REQUEST';
export const CHECK_LOGIN_SUCCESS = 'CHECK_LOGIN_SUCCESS';
export const CHECK_LOGIN_FAILURE = 'CHECK_LOGIN_FAILURE';
export const CHECK_CSRF_TOKEN = 'CHECK_CSRF_TOKEN';
+15 -1
View File
@@ -2,16 +2,30 @@ export const base = '/api/v1';
const buildOptions = (inputOptions = {}) => {
const csurfDOM = document.head.querySelector('[property=csrf]');
const defaultOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
credentials: 'same-origin'
credentials: 'same-origin',
_csrf: csurfDOM ? csurfDOM.content : false
};
const options = Object.assign({}, defaultOptions, inputOptions);
if (options._csrf) {
switch (options.method.toLowerCase()) {
case 'post':
case 'put':
case 'delete':
options.headers['x-csrf-token'] = options._csrf;
break;
}
}
if (options.method.toLowerCase() !== 'get') {
options.body = JSON.stringify(options.body);
}
+3
View File
@@ -41,6 +41,9 @@ export default function auth (state = initialState, action) {
.set('view', action.view);
case actions.CLEAN_STATE:
return initialState;
case actions.CHECK_CSRF_TOKEN:
return state
.set('_csrf', action._csrf);
case actions.FETCH_SIGNIN_REQUEST:
return state
.set('isLoading', true);
@@ -10,7 +10,11 @@ import CommentHistory from 'coral-plugin-history/CommentHistory';
import SettingsHeader from '../components/SettingsHeader';
import RestrictedContent from 'coral-framework/components/RestrictedContent';
class SignInContainer extends Component {
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
const lang = new I18n(translations);
class SettingsContainer extends Component {
constructor (props) {
super(props);
this.state = {
@@ -59,7 +63,7 @@ class SignInContainer extends Component {
? <CommentHistory
comments={commentsMostRecentFirst}
assets={user.myAssets.map(id => items.assets[id])} />
: <p>Loading comment history...</p>
: <p>{lang.t('user-no-comment')}</p>
}
</TabContent>
<TabContent show={activeTab === 1}>
@@ -83,4 +87,4 @@ const mapDispatchToProps = dispatch => ({
export default connect(
mapStateToProps,
mapDispatchToProps
)(SignInContainer);
)(SettingsContainer);
+8
View File
@@ -0,0 +1,8 @@
{
"en":{
"user-no-comment": "This user has not yet left a comment."
},
"es":{
"user-no-comment": "Aún no ha escrito ningún comentario."
}
}
+3 -2
View File
@@ -9,8 +9,8 @@
"build-watch": "NODE_ENV=development webpack --config webpack.config.dev.js --watch",
"lint": "eslint bin/* .",
"lint-fix": "eslint bin/* . --fix",
"test": "NODE_ENV=test mocha --compilers js:babel-core/register tests/helpers/*.js --require ignore-styles --recursive tests",
"test-watch": "NODE_ENV=test mocha --compilers js:babel-core/register --recursive -w tests",
"test": "TEST_MODE=unit NODE_ENV=test mocha --compilers js:babel-core/register tests/helpers/*.js --require ignore-styles --recursive tests",
"test-watch": "TEST_MODE=unit NODE_ENV=test mocha --compilers js:babel-core/register --recursive -w tests",
"pree2e": "NODE_ENV=test scripts/pree2e.sh",
"e2e": "NODE_ENV=test nightwatch",
"embed-start": "NODE_ENV=development npm run build && ./bin/cli serve --jobs",
@@ -52,6 +52,7 @@
"cli-table": "^0.3.1",
"commander": "^2.9.0",
"connect-redis": "^3.1.0",
"csurf": "^1.9.0",
"debug": "^2.2.0",
"ejs": "^2.5.2",
"env-rewrite": "^1.0.2",
+3 -5
View File
@@ -8,18 +8,16 @@ const router = express.Router();
* This returns the user if they are logged in.
*/
router.get('/', (req, res, next) => {
if (req.user) {
return next();
}
// When there is no user on the request, then just send back a 204 to this
// request. It's not really "an error" if what they asked for isn't available,
// but it could be.
res.status(204).end();
}, (req, res) => {
// Send back the user object.
res.json(req.user.toObject());
res.json({user: req.user.toObject()});
});
/**
@@ -53,7 +51,7 @@ const HandleAuthCallback = (req, res, next) => (err, user) => {
return next(err);
}
// We logged in the user! Let's send back the user data.
// We logged in the user! Let's send back the user data and the CSRF token.
res.json({user});
});
};
+1
View File
@@ -8,6 +8,7 @@ const User = require('../../../models/user');
const Action = require('../../../models/action');
const Asset = require('../../../models/asset');
const Setting = require('../../../models/setting');
const ErrInvalidAssetURL = new Error('asset_url is invalid');
ErrInvalidAssetURL.status = 400;
+2 -1
View File
@@ -52,7 +52,7 @@ router.post('/:user_id/status', (req, res, next) => {
.catch(next);
});
router.post('/', authorization.needed('admin'), (req, res, next) => {
router.post('/', (req, res, next) => {
const {
email,
password,
@@ -105,6 +105,7 @@ router.post('/', authorization.needed('admin'), (req, res, next) => {
});
router.post('/:user_id/actions', authorization.needed(), (req, res, next) => {
const {
action_type,
metadata
-1
View File
@@ -9,7 +9,6 @@
],
"extends": "../.eslintrc.json",
"rules": {
"no-undef": [0],
"mocha/no-exclusive-tests": "warn"
}
}
@@ -15,6 +15,7 @@ describe('itemActions', () => {
beforeEach(() => {
store = mockStore(new Map({}));
fetchMock.restore();
});
describe('getStream', () => {
@@ -103,14 +104,15 @@ describe('itemActions', () => {
});
it('should handle an error', () => {
fetchMock.get('*', 404);
return actions.getItemsArray(ids, host)(store.dispatch)
return actions.getItemsArray(ids)(store.dispatch)
.catch((err) => {
expect(err).to.be.truthy;
});
});
});
describe('postItem', () => {
// NEED TO FIGURE OUT HOW TO TEST WITH CSRF TOKEN IN.
xdescribe('postItem', () => {
const item = {
type: 'comments',
data: {body: 'stuff'}
@@ -118,7 +120,7 @@ describe('itemActions', () => {
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)
return actions.postItem(item.data, item.type, undefined)(store.dispatch, store.getState)
.then((id) => {
expect(fetchMock.calls().matched[0][1]).to.deep.equal(
{
@@ -145,21 +147,21 @@ describe('itemActions', () => {
});
it('should handle an error', () => {
fetchMock.post('*', 404);
return actions.postItem(item)(store.dispatch)
return actions.postItem(item)(store.dispatch, store.getState)
.catch((err) => {
expect(err).to.be.truthy;
});
});
});
describe('postAction', () => {
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)
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'});
@@ -168,7 +170,7 @@ describe('itemActions', () => {
it('should handle an error', () => {
fetchMock.post('*', 404);
return actions.postAction('abc', 'flag', '123')(store.dispatch)
return actions.postAction('abc', 'flag', '123')(store.dispatch, store.getState)
.catch((err) => {
expect(err).to.be.truthy;
});
@@ -185,9 +187,9 @@ describe('itemActions', () => {
});
});
it('should handle an error', () => {
xit('should handle an error', () => {
fetchMock.post('*', 404);
return actions.postAction('abc', 'flag', '123')(store.dispatch)
return actions.postAction('abc', 'flag', '123')(store.dispatch, store.getState)
.catch((err) => {
expect(err).to.be.truthy;
});
+11 -3
View File
@@ -1,4 +1,4 @@
const utils = require('../../utils/e2e-mongoose');
const mongoose = require('../../helpers/mongoose');
const mocks = require('../mocks');
const mockComment = 'This is a test comment.';
@@ -12,7 +12,11 @@ const mockUser = {
module.exports = {
'@tags': ['embed-stream', 'comment', 'premodoff', 'premodon'],
before: () => {
utils.before();
mongoose.waitTillConnect(function(err) {
if (err) {
console.error(err);
}
});
},
'User registers and posts a comment with premod off': client => {
client.perform((client, done) => {
@@ -171,7 +175,11 @@ module.exports = {
});
},
after: client => {
utils.after();
mongoose.disconnect(function(err) {
if (err) {
console.error(err);
}
});
client.end();
}
};
+2
View File
@@ -1,3 +1,5 @@
/* eslint-env browser */
const jsdom = require('jsdom').jsdom;
const fs = require('fs');
const path = require('path');
+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();
};
+4 -39
View File
@@ -1,50 +1,15 @@
const mongoose = require('../services/mongoose');
function waitTillConnect() {
return new Promise((resolve, reject) => {
mongoose.connection.on('open', function(err) {
if (err) {
return reject(err);
}
return resolve();
});
});
}
const mongoose = require('./helpers/mongoose');
before(function(done) {
this.timeout(30000);
waitTillConnect()
.then(() => {
done();
})
.catch((err) => {
done(err);
});
mongoose.waitTillConnect(done);
});
beforeEach(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);
});
mongoose.clearDB(done);
});
after(function(done) {
mongoose.disconnect();
return done();
mongoose.disconnect(done);
});
+13 -13
View File
@@ -13,7 +13,7 @@ describe('/api/v1/auth', () => {
.get('/api/v1/auth')
.then((res) => {
expect(res.status).to.be.equal(204);
expect(res.body).to.be.empty;
expect(res).to.not.have.a.body;
});
});
});
@@ -37,26 +37,27 @@ describe('/api/v1/auth/local', () => {
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');
.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');
});
.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', () => {
@@ -87,6 +88,5 @@ describe('/api/v1/auth/local', () => {
});
});
});
});
});
+11 -18
View File
@@ -88,6 +88,7 @@ describe('/api/v1/comments', () => {
.then(res => {
expect(res).to.have.status(200);
expect(res.body.comments).to.have.length(2);
expect(res.body.comments[0]).to.have.property('author_id', '456');
expect(res.body.comments[1]).to.have.property('author_id', '456');
});
});
@@ -193,8 +194,7 @@ describe('/api/v1/comments', () => {
});
it('should create a comment with a rejected status if it contains a bad word', () => {
return chai.request(app)
.post('/api/v1/comments')
return chai.request(app).post('/api/v1/comments')
.set(passport.inject({roles: []}))
.send({'body': 'bad words are the baddest', 'author_id': '123', 'asset_id': asset_id, 'parent_id': ''})
.then((res) => {
@@ -205,15 +205,13 @@ describe('/api/v1/comments', () => {
});
it('should create a comment with no status and a flag if it contains a suspected word', () => {
return chai.request(app)
.post('/api/v1/comments')
return chai.request(app).post('/api/v1/comments')
.set(passport.inject({roles: []}))
.send({'body': 'suspect words are the most suspicious', 'author_id': '123', 'asset_id': postmod_asset_id, 'parent_id': ''})
.then((res) => {
expect(res).to.have.status(201);
expect(res.body).to.have.property('id');
expect(res.body).to.have.property('status', null);
return Promise.all([
res.body,
Action.findByType('flag', 'comments')
@@ -240,8 +238,7 @@ describe('/api/v1/comments', () => {
.then(() => asset);
})
.then((asset) => {
return chai.request(app)
.post('/api/v1/comments')
return chai.request(app).post('/api/v1/comments')
.set(passport.inject({roles: []}))
.send({'body': 'Something body.', 'author_id': '123', 'asset_id': asset.id, 'parent_id': ''});
})
@@ -262,8 +259,7 @@ describe('/api/v1/comments', () => {
.then(() => asset);
})
.then((asset) => {
return chai.request(app)
.post('/api/v1/comments')
return chai.request(app).post('/api/v1/comments')
.set(passport.inject({roles: []}))
.send({'body': 'This is way way way way way too long.', 'author_id': '123', 'asset_id': asset.id, 'parent_id': ''});
})
@@ -281,8 +277,7 @@ describe('/api/v1/comments', () => {
closedMessage: 'tests said expired!'
})
.then((asset) => {
return chai.request(app)
.post('/api/v1/comments')
return chai.request(app).post('/api/v1/comments')
.set(passport.inject({roles: []}))
.send({'body': 'Something body.', 'author_id': '123', 'asset_id': asset.id, 'parent_id': ''});
})
@@ -302,8 +297,7 @@ describe('/api/v1/comments', () => {
closedMessage: 'tests said expired!'
})
.then((asset) => {
return chai.request(app)
.post('/api/v1/comments')
return chai.request(app).post('/api/v1/comments')
.set(passport.inject({roles: []}))
.send({'body': 'Something body.', 'author_id': '123', 'asset_id': asset.id, 'parent_id': ''});
})
@@ -369,7 +363,6 @@ describe('/api/v1/comments/:comment_id', () => {
expect(res).to.have.status(200);
expect(res).to.have.property('body');
expect(res.body).to.have.property('body', 'comment 10');
});
});
});
@@ -381,7 +374,6 @@ describe('/api/v1/comments/:comment_id', () => {
.set(passport.inject({roles: ['admin']}))
.then((res) => {
expect(res).to.have.status(204);
return Comment.findById('abc');
})
.then((comment) => {
@@ -469,7 +461,7 @@ describe('/api/v1/comments/:comment_id/actions', () => {
});
describe('#post', () => {
it('it should update actions', () => {
it('it should create an action', () => {
return chai.request(app)
.post('/api/v1/comments/abc/actions')
.set(passport.inject({id: '456', roles: ['admin']}))
@@ -477,9 +469,10 @@ describe('/api/v1/comments/:comment_id/actions', () => {
.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': 'Comment is too awesome.'});
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');
});
});
+3 -5
View File
@@ -81,17 +81,15 @@ describe('/api/v1/queue', () => {
});
});
it('should return all the pending comments, users and actions', function(done){
chai.request(app)
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']}))
.end(function(err, res){
expect(err).to.be.null;
.then((res) => {
expect(res).to.have.status(200);
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');
done();
});
});
});
+2 -3
View File
@@ -31,7 +31,7 @@ describe('/api/v1/users/:user_id/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.'}})
.send({'action_type': 'flag', metadata: {reason: 'Bio is too awesome.'}})
.then((res) => {
expect(res).to.have.status(201);
expect(res).to.have.body;
@@ -39,8 +39,7 @@ describe('/api/v1/users/:user_id/actions', () => {
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');
})
.catch(err => console.error(err.message));
});
});
});
});
-36
View File
@@ -1,36 +0,0 @@
const mongoose = require('../../services/mongoose');
// Ensure the NODE_ENV is set to 'test',
// this is helpful when you would like to change behavior when testing.
function clearDB() {
// console.log('Clearing DB', mongoose.connection);
for (let i in mongoose.connection.collections) {
// console.log('Clearing', i);
mongoose.connection.collections[i].remove(function() {});
}
}
module.exports = {
before: () => {
clearDB();
},
beforeEach: () => {
if (mongoose.connection.readyState === 0) {
mongoose.on('open', function() {
if (err) {
throw err;
}
return clearDB();
});
} else {
return clearDB();
}
},
after: () => {
clearDB();
mongoose.disconnect();
}
};
+1
View File
@@ -3,6 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1">
<meta property="csrf" content="<%= csrfToken %>">
<title>Talk - Coral Admin</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://code.getmdl.io/1.2.1/material.indigo-pink.min.css">
+3 -3
View File
@@ -1,13 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta property="csrf" content="<%= csrfToken %>">
<link rel="stylesheet" type="text/css" href="/client/embed/stream/default.css">
<link href="https://fonts.googleapis.com/css?family=Lato|Open+Sans" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<div id="coralStream"></div>
<script src="/client/embed/stream/bundle.js"></script>
</body>
</html>
</html>
+2
View File
@@ -3,6 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1">
<meta property="csrf" content="<%= csrfToken %>">
<title>Password Reset</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://code.getmdl.io/1.2.1/material.indigo-pink.min.css">
@@ -82,6 +83,7 @@
<body>
<div id="root">
<form id="reset-password-form">
<input type="hidden" name="_csrf" value={{csrfToken}}/>
<legend class="legend">Set new password</legend>
<label for="password">
New password