From 5ea9115a959c1497507f84be8d6fa0a214378c19 Mon Sep 17 00:00:00 2001 From: Mike Stone Date: Fri, 16 Sep 2016 09:55:46 -0400 Subject: [PATCH] Forgot password submit (#168) * Authentication middleware * API client refactor * Configure API client to make forgot_password requests * Successfully submit forgot password form * Display server errors for unknown email address --- .../ForgotPasswordForm/ForgotPasswordForm.jsx | 10 +- frontend/kolide/base.js | 62 ++++++++++ frontend/kolide/endpoints.js | 1 + frontend/kolide/index.js | 58 ++-------- frontend/kolide/index.tests.js | 45 +++++++- .../ForgotPasswordPage/ForgotPasswordPage.jsx | 77 ++++++++++++- .../ForgotPasswordPage.tests.jsx | 21 ++++ frontend/pages/ForgotPasswordPage/styles.js | 28 +++++ frontend/redux/middlewares/auth.js | 16 +++ frontend/redux/nodes/auth/actions.js | 2 +- frontend/redux/nodes/auth/reducer.tests.js | 75 +++++++++++- .../components/ForgotPasswordPage/actions.js | 46 ++++++++ .../components/ForgotPasswordPage/reducer.js | 45 ++++++++ .../ForgotPasswordPage/reducer.tests.js | 108 ++++++++++++++++++ frontend/redux/nodes/components/reducer.js | 7 ++ frontend/redux/reducers.js | 2 + frontend/redux/store.js | 9 +- frontend/styles/color.js | 3 + frontend/test/mocks.js | 37 ++++-- 19 files changed, 577 insertions(+), 75 deletions(-) create mode 100644 frontend/kolide/base.js create mode 100644 frontend/pages/ForgotPasswordPage/ForgotPasswordPage.tests.jsx create mode 100644 frontend/redux/middlewares/auth.js create mode 100644 frontend/redux/nodes/components/ForgotPasswordPage/actions.js create mode 100644 frontend/redux/nodes/components/ForgotPasswordPage/reducer.js create mode 100644 frontend/redux/nodes/components/ForgotPasswordPage/reducer.tests.js create mode 100644 frontend/redux/nodes/components/reducer.js diff --git a/frontend/components/forms/ForgotPasswordForm/ForgotPasswordForm.jsx b/frontend/components/forms/ForgotPasswordForm/ForgotPasswordForm.jsx index 529f4886fa..13b5aa4044 100644 --- a/frontend/components/forms/ForgotPasswordForm/ForgotPasswordForm.jsx +++ b/frontend/components/forms/ForgotPasswordForm/ForgotPasswordForm.jsx @@ -7,6 +7,8 @@ import validEmail from '../validators/valid_email'; class ForgotPasswordForm extends Component { static propTypes = { + clearErrors: PropTypes.func, + error: PropTypes.string, onSubmit: PropTypes.func, }; @@ -21,6 +23,7 @@ class ForgotPasswordForm extends Component { } onInputFieldChange = (evt) => { + const { clearErrors, error: serverError } = this.props; const { value } = evt.target; this.setState({ @@ -30,6 +33,8 @@ class ForgotPasswordForm extends Component { }, }); + if (serverError) clearErrors(); + return false; } @@ -61,7 +66,8 @@ class ForgotPasswordForm extends Component { } render () { - const { error, formData: { email } } = this.state; + const { error: serverError } = this.props; + const { error: clientError, formData: { email } } = this.state; const { formStyles, inputStyles, submitButtonStyles } = componentStyles; const { onFormSubmit, onInputFieldChange } = this; const disabled = !email; @@ -69,7 +75,7 @@ class ForgotPasswordForm extends Component { return (
{ + return response.json() + .then(jsonResponse => { + if (response.ok) { + return jsonResponse; + } + + const error = new Error(response.statusText); + error.response = jsonResponse; + error.message = jsonResponse; + error.error = jsonResponse.error; + + throw error; + }); + }); + } +} + +export default Base; + diff --git a/frontend/kolide/endpoints.js b/frontend/kolide/endpoints.js index d0c21b3f81..31b0621d6f 100644 --- a/frontend/kolide/endpoints.js +++ b/frontend/kolide/endpoints.js @@ -1,3 +1,4 @@ export default { + FORGOT_PASSWORD: '/v1/kolide/forgot_password', LOGIN: '/v1/kolide/login', }; diff --git a/frontend/kolide/index.js b/frontend/kolide/index.js index 54f57ee6c6..bd2b072b50 100644 --- a/frontend/kolide/index.js +++ b/frontend/kolide/index.js @@ -1,61 +1,21 @@ import fetch from 'isomorphic-fetch'; -import config from '../config'; +import Base from './base'; import endpoints from './endpoints'; import local from '../utilities/local'; -class Kolide { - constructor () { - this.baseURL = this.setBaseURL(); - } - - setBaseURL () { - const { - settings: { env }, - environments: { development }, - } = config; - - if (env === development) { - return 'http://localhost:8080/api'; - } - - throw new Error(`API base URL is not configured for environment: ${env}`); - } - - setBearerToken (bearerToken) { - this.bearerToken = bearerToken; - } - +class Kolide extends Base { loginUser ({ username, password }) { const { LOGIN } = endpoints; - const endpoint = this.baseURL + LOGIN; + const loginEndpoint = this.baseURL + LOGIN; - return fetch(endpoint, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ username, password }), - }) - .then(response => { - return response.json() - .then(user => { - if (response.ok) { - const { token } = user; + return this.post(loginEndpoint, JSON.stringify({ username, password })); + } - local.setItem('auth_token', token); - this.setBearerToken(token); + forgotPassword ({ email }) { + const { FORGOT_PASSWORD } = endpoints; + const forgotPasswordEndpoint = this.baseURL + FORGOT_PASSWORD; - return user; - } - - const error = new Error(response.statusText); - error.response = response; - error.message = user.error; - - throw error; - }); - }); + return this.post(forgotPasswordEndpoint, JSON.stringify({ email })); } } diff --git a/frontend/kolide/index.tests.js b/frontend/kolide/index.tests.js index f3b77356b5..5b41ef58af 100644 --- a/frontend/kolide/index.tests.js +++ b/frontend/kolide/index.tests.js @@ -1,6 +1,13 @@ import expect from 'expect'; import Kolide from './index'; -import { validLoginRequest } from '../test/mocks'; +import mocks from '../test/mocks'; + +const { + invalidPasswordResetRequest, + validLoginRequest, + validPasswordResetRequest, + validUser, +} = mocks; describe('Kolide - API client', () => { describe('defaults', () => { @@ -10,19 +17,49 @@ describe('Kolide - API client', () => { }); describe('#loginUser', () => { - it('sets the bearer token', (done) => { + it('calls the appropriate endpoint with the correct parameters', (done) => { const request = validLoginRequest(); Kolide.loginUser({ username: 'admin', password: 'secret', }) - .then(() => { + .then((user) => { + expect(user).toEqual(validUser); expect(request.isDone()).toEqual(true); - expect(Kolide.bearerToken).toEqual('auth_token'); done(); }) .catch(done); }); }); + + describe('#forgotPassword', () => { + it('calls the appropriate endpoint with the correct parameters when successful', (done) => { + const request = validPasswordResetRequest(); + const email = 'hi@thegnar.co'; + + Kolide.forgotPassword({ email }) + .then(() => { + expect(request.isDone()).toEqual(true); + done(); + }) + .catch(done); + }); + + it('return errors correctly for unsuccessful requests', (done) => { + const error = 'Something went wrong'; + const request = invalidPasswordResetRequest(error); + const email = 'hi@thegnar.co'; + + Kolide.forgotPassword({ email }) + .then(done) + .catch(errorResponse => { + const { response } = errorResponse; + + expect(response).toEqual({ error }); + expect(request.isDone()).toEqual(true); + done(); + }); + }); + }); }); diff --git a/frontend/pages/ForgotPasswordPage/ForgotPasswordPage.jsx b/frontend/pages/ForgotPasswordPage/ForgotPasswordPage.jsx index 2884df1d03..b8572eb4e5 100644 --- a/frontend/pages/ForgotPasswordPage/ForgotPasswordPage.jsx +++ b/frontend/pages/ForgotPasswordPage/ForgotPasswordPage.jsx @@ -1,10 +1,73 @@ -import React, { Component } from 'react'; +import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { noop } from 'lodash'; import componentStyles from './styles'; +import { + clearForgotPasswordErrors, + forgotPasswordAction, +} from '../../redux/nodes/components/ForgotPasswordPage/actions'; import ForgotPasswordForm from '../../components/forms/ForgotPasswordForm'; +import Icon from '../../components/icons/Icon'; + +export class ForgotPasswordPage extends Component { + static propTypes = { + dispatch: PropTypes.func, + email: PropTypes.string, + error: PropTypes.string, + }; + + static defaultProps = { + dispatch: noop, + }; -class ForgotPasswordPage extends Component { onSubmit = (formData) => { - console.log('ForgotPasswordPage formData', formData); + const { dispatch } = this.props; + + return dispatch(forgotPasswordAction(formData)); + } + + clearErrors = () => { + const { dispatch } = this.props; + + return dispatch(clearForgotPasswordErrors); + } + + renderContent = () => { + const { clearErrors } = this; + const { email, error } = this.props; + const { + emailSentButtonWrapperStyles, + emailSentIconStyles, + emailSentTextStyles, + emailSentTextWrapperStyles, + emailTextStyles, + } = componentStyles; + + if (email) { + return ( +
+
+

+ An email was sent to + {email}. + Click the link on the email to proceed with the password reset process. +

+
+
+ + EMAIL SENT +
+
+ ); + } + + return ( + + ); } render () { @@ -24,11 +87,15 @@ class ForgotPasswordPage extends Component {

Forgot Password

If you’ve forgotten your password enter your email below and we will email you a link so that you can reset your password.

- + {this.renderContent()}
); } } -export default ForgotPasswordPage; +const mapStateToProps = (state) => { + return state.components.ForgotPasswordPage; +}; + +export default connect(mapStateToProps)(ForgotPasswordPage); diff --git a/frontend/pages/ForgotPasswordPage/ForgotPasswordPage.tests.jsx b/frontend/pages/ForgotPasswordPage/ForgotPasswordPage.tests.jsx new file mode 100644 index 0000000000..76775a8310 --- /dev/null +++ b/frontend/pages/ForgotPasswordPage/ForgotPasswordPage.tests.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import expect from 'expect'; +import { mount } from 'enzyme'; +import { ForgotPasswordPage } from './ForgotPasswordPage'; + +describe('ForgotPasswordPage - component', () => { + it('renders the ForgotPasswordForm when there is no email prop', () => { + const page = mount(); + + expect(page.find('ForgotPasswordForm').length).toEqual(1); + }); + + it('renders the email sent text when the email prop is present', () => { + const email = 'hi@thegnar.co'; + const page = mount(); + + expect(page.find('ForgotPasswordForm').length).toEqual(0); + expect(page.text()).toInclude(`An email was sent to ${email}.`); + }); +}); + diff --git a/frontend/pages/ForgotPasswordPage/styles.js b/frontend/pages/ForgotPasswordPage/styles.js index e728d91623..68be19b02b 100644 --- a/frontend/pages/ForgotPasswordPage/styles.js +++ b/frontend/pages/ForgotPasswordPage/styles.js @@ -9,6 +9,34 @@ export default { justifyContent: 'center', flexDirection: 'column', }, + emailSentButtonWrapperStyles: { + backgroundColor: color.successLight, + borderRadius: border.radius.base, + color: color.white, + padding: padding.base, + position: 'relative', + textAlign: 'center', + textTransform: 'uppercase', + }, + emailSentIconStyles: { + height: '35px', + left: '18px', + position: 'absolute', + top: '14px', + width: '35px', + }, + emailSentTextStyles: { + fontSize: font.medium, + }, + emailSentTextWrapperStyles: { + padding: padding.base, + backgroundColor: color.accentLight, + borderRadius: border.radius.base, + marginBottom: padding.base, + }, + emailTextStyles: { + color: color.link, + }, forgotPasswordStyles: { alignItems: 'center', backgroundColor: color.white, diff --git a/frontend/redux/middlewares/auth.js b/frontend/redux/middlewares/auth.js new file mode 100644 index 0000000000..b184002e0e --- /dev/null +++ b/frontend/redux/middlewares/auth.js @@ -0,0 +1,16 @@ +/* eslint-disable no-unused-vars */ +import kolide from '../../kolide'; +import { LOGIN_SUCCESS } from '../nodes/auth/actions'; +import local from '../../utilities/local'; + +const authMiddleware = store => next => action => { + if (action.type === LOGIN_SUCCESS) { + const { token } = action.payload.data; + local.setItem('auth_token', token); + kolide.setBearerToken(token); + } + + return next(action); +}; + +export default authMiddleware; diff --git a/frontend/redux/nodes/auth/actions.js b/frontend/redux/nodes/auth/actions.js index 999b795ad9..7088ded9a4 100644 --- a/frontend/redux/nodes/auth/actions.js +++ b/frontend/redux/nodes/auth/actions.js @@ -27,7 +27,7 @@ export const loginUser = (formData) => { return (dispatch) => { return new Promise((resolve, reject) => { dispatch(loginRequest); - Kolide.loginUser(formData) + return Kolide.loginUser(formData) .then(user => { dispatch(loginSuccess(user)); return resolve(user); diff --git a/frontend/redux/nodes/auth/reducer.tests.js b/frontend/redux/nodes/auth/reducer.tests.js index 6f3ba3cfcc..2681c79b07 100644 --- a/frontend/redux/nodes/auth/reducer.tests.js +++ b/frontend/redux/nodes/auth/reducer.tests.js @@ -1,6 +1,12 @@ +import configureStore from 'redux-mock-store'; import expect from 'expect'; +import thunk from 'redux-thunk'; +import authMiddleware from '../../middlewares/auth'; +import kolide from '../../../kolide'; +import local from '../../../utilities/local'; +import { loginRequest, LOGIN_REQUEST, LOGIN_SUCCESS, loginUser } from './actions'; import reducer, { initialState } from './reducer'; -import { loginRequest } from './actions'; +import { validLoginRequest, validUser } from '../../../test/mocks'; describe('Auth - reducer', () => { it('sets the initial state', () => { @@ -17,4 +23,71 @@ describe('Auth - reducer', () => { loading: true, }); }); + + context('loginUser action', () => { + const formData = { + username: 'username', + password: 'p@ssw0rd', + }; + const middlewares = [thunk, authMiddleware]; + const mockStore = configureStore(middlewares); + const store = mockStore({}); + + + it('calls the api login endpoint', (done) => { + const loginRequestMock = validLoginRequest(); + store.dispatch(loginUser(formData)) + .then(user => { + expect(loginRequestMock.isDone()).toEqual(true); + expect(local.getItem('auth_token')).toEqual(user.token); + done(); + }) + .catch(done); + }); + + it('returns the authenticated user', (done) => { + validLoginRequest(); + + store.dispatch(loginUser(formData)) + .then(user => { + expect(user).toEqual(validUser); + done(); + }) + .catch(done); + }); + + it('sets the users auth token in local storage', (done) => { + validLoginRequest(); + + store.dispatch(loginUser(formData)) + .then(user => { + expect(local.getItem('auth_token')).toEqual(user.token); + done(); + }) + .catch(done); + }); + + it('sets the api client bearerToken', (done) => { + validLoginRequest(); + + store.dispatch(loginUser(formData)) + .then(user => { + expect(kolide.bearerToken).toEqual(user.token); + done(); + }) + .catch(done); + }); + + it('dispatches LOGIN_REQUEST and LOGIN_SUCCESS actions', (done) => { + validLoginRequest(); + + store.dispatch(loginUser(formData)) + .then(() => { + const actionTypes = store.getActions().map(a => a.type); + expect(actionTypes).toInclude(LOGIN_REQUEST, LOGIN_SUCCESS); + done(); + }) + .catch(done); + }); + }); }); diff --git a/frontend/redux/nodes/components/ForgotPasswordPage/actions.js b/frontend/redux/nodes/components/ForgotPasswordPage/actions.js new file mode 100644 index 0000000000..35e5d4c6cd --- /dev/null +++ b/frontend/redux/nodes/components/ForgotPasswordPage/actions.js @@ -0,0 +1,46 @@ +import Kolide from '../../../../kolide'; + +export const CLEAR_FORGOT_PASSWORD_ERRORS = 'CLEAR_FORGOT_PASSWORD_ERRORS'; +export const FORGOT_PASSWORD_REQUEST = 'FORGOT_PASSWORD_REQUEST'; +export const FORGOT_PASSWORD_SUCCESS = 'FORGOT_PASSWORD_SUCCESS'; +export const FORGOT_PASSWORD_ERROR = 'FORGOT_PASSWORD_ERROR'; + +export const clearForgotPasswordErrors = { type: CLEAR_FORGOT_PASSWORD_ERRORS }; +export const forgotPasswordRequestAction = { type: FORGOT_PASSWORD_REQUEST }; +export const forgotPasswordSuccessAction = (email) => { + return { + type: FORGOT_PASSWORD_SUCCESS, + payload: { + data: { + email, + }, + }, + }; +}; +export const forgotPasswordErrorAction = (error) => { + return { + type: FORGOT_PASSWORD_ERROR, + payload: { + error, + }, + }; +}; + +// formData should be { email: } +export const forgotPasswordAction = (formData) => { + return (dispatch) => { + dispatch(forgotPasswordRequestAction); + return Kolide.forgotPassword(formData) + .then(() => { + const { email } = formData; + + return dispatch(forgotPasswordSuccessAction(email)); + }) + .catch(response => { + const { error } = response; + + dispatch(forgotPasswordErrorAction(error)); + throw response; + }); + }; +}; diff --git a/frontend/redux/nodes/components/ForgotPasswordPage/reducer.js b/frontend/redux/nodes/components/ForgotPasswordPage/reducer.js new file mode 100644 index 0000000000..0a6fec4b84 --- /dev/null +++ b/frontend/redux/nodes/components/ForgotPasswordPage/reducer.js @@ -0,0 +1,45 @@ +import { + CLEAR_FORGOT_PASSWORD_ERRORS, + FORGOT_PASSWORD_ERROR, + FORGOT_PASSWORD_REQUEST, + FORGOT_PASSWORD_SUCCESS, +} from './actions'; + +export const initialState = { + email: null, + error: null, + loading: false, +}; + +const reducer = (state = initialState, { type, payload }) => { + switch (type) { + case CLEAR_FORGOT_PASSWORD_ERRORS: + return { + ...state, + error: null, + }; + case FORGOT_PASSWORD_REQUEST: + return { + ...state, + loading: true, + }; + case FORGOT_PASSWORD_SUCCESS: + return { + ...state, + email: payload.data.email, + error: null, + loading: false, + }; + case FORGOT_PASSWORD_ERROR: + return { + ...state, + email: null, + error: payload.error, + loading: false, + }; + default: + return state; + } +}; + +export default reducer; diff --git a/frontend/redux/nodes/components/ForgotPasswordPage/reducer.tests.js b/frontend/redux/nodes/components/ForgotPasswordPage/reducer.tests.js new file mode 100644 index 0000000000..4bda184918 --- /dev/null +++ b/frontend/redux/nodes/components/ForgotPasswordPage/reducer.tests.js @@ -0,0 +1,108 @@ +import configureStore from 'redux-mock-store'; +import expect from 'expect'; +import thunk from 'redux-thunk'; +import { + clearForgotPasswordErrors, + forgotPasswordAction, + forgotPasswordRequestAction, + forgotPasswordSuccessAction, + forgotPasswordErrorAction, +} from './actions'; +import { invalidPasswordResetRequest, validPasswordResetRequest } from '../../../../test/mocks'; +import reducer, { initialState } from './reducer'; + +describe('ForgotPasswordPage - reducer', () => { + describe('initial state', () => { + it('sets the initial state', () => { + expect(reducer(undefined, { type: 'FAKE-ACTION' })).toEqual(initialState); + }); + }); + + describe('clearForgotPasswordErrors', () => { + it('changes the loading state to true', () => { + const errorState = { + ...initialState, + error: 'Something went wrong', + }; + + expect(reducer(errorState, clearForgotPasswordErrors)).toEqual({ + ...errorState, + error: null, + }); + }); + }); + + describe('forgotPasswordRequestAction', () => { + it('changes the loading state to true', () => { + expect(reducer(initialState, forgotPasswordRequestAction)).toEqual({ + ...initialState, + loading: true, + }); + }); + }); + + describe('forgotPasswordSuccessAction', () => { + it('changes the loading state to false and emailSent to true', () => { + const email = 'hi@thegnar.co'; + + expect(reducer(initialState, forgotPasswordSuccessAction(email))).toEqual({ + ...initialState, + email, + loading: false, + }); + }); + }); + + describe('forgotPasswordErrorAction', () => { + it('changes the loading state to false and sets the error state', () => { + const error = 'There was an error with your request'; + + expect(reducer(initialState, forgotPasswordErrorAction(error))).toEqual({ + ...initialState, + error, + loading: false, + }); + }); + }); + + describe('forgotPasswordAction', () => { + const middlewares = [thunk]; + const mockStore = configureStore(middlewares); + + it('dispatches the appropriate actions when successful', (done) => { + const formData = { email: 'hi@thegnar.co' }; + const request = validPasswordResetRequest(); + const store = mockStore({}); + + store.dispatch(forgotPasswordAction(formData)) + .then(() => { + const actions = store.getActions(); + + expect(actions).toInclude(forgotPasswordRequestAction); + expect(actions).toInclude(forgotPasswordSuccessAction(formData.email)); + expect(request.isDone()).toEqual(true); + done(); + }) + .catch(done); + }); + + it('dispatches the appropriate actions when unsuccessful', (done) => { + const formData = { email: 'hi@thegnar.co' }; + const error = 'Something went wrong'; + const invalidRequest = invalidPasswordResetRequest(error); + const store = mockStore({}); + + store.dispatch(forgotPasswordAction(formData)) + .then(done) + .catch(errorResponse => { + const actions = store.getActions(); + const { response } = errorResponse; + + expect(response).toEqual({ error }); + expect(actions).toInclude(forgotPasswordErrorAction(error)); + expect(invalidRequest.isDone()).toEqual(true); + done(); + }); + }); + }); +}); diff --git a/frontend/redux/nodes/components/reducer.js b/frontend/redux/nodes/components/reducer.js new file mode 100644 index 0000000000..17ef20fe1e --- /dev/null +++ b/frontend/redux/nodes/components/reducer.js @@ -0,0 +1,7 @@ +import { combineReducers } from 'redux'; +import ForgotPasswordPage from './ForgotPasswordPage/reducer'; + +export default combineReducers({ + ForgotPasswordPage, +}); + diff --git a/frontend/redux/reducers.js b/frontend/redux/reducers.js index 18f4d9e33d..4499e48e3d 100644 --- a/frontend/redux/reducers.js +++ b/frontend/redux/reducers.js @@ -2,9 +2,11 @@ import { combineReducers } from 'redux'; import { routerReducer } from 'react-router-redux'; import app from './nodes/app/reducer'; import auth from './nodes/auth/reducer'; +import components from './nodes/components/reducer'; export default combineReducers({ app, auth, + components, routing: routerReducer, }); diff --git a/frontend/redux/store.js b/frontend/redux/store.js index a23af5da3a..bcf5ac1004 100644 --- a/frontend/redux/store.js +++ b/frontend/redux/store.js @@ -2,18 +2,21 @@ import { createStore, applyMiddleware, compose } from 'redux'; import thunkMiddleware from 'redux-thunk'; import { browserHistory } from 'react-router'; import { routerMiddleware } from 'react-router-redux'; +import authMiddleware from './middlewares/auth'; import reducers from './reducers'; const initialState = {}; -const middleware = [ + +const appliedMiddleware = applyMiddleware( thunkMiddleware, routerMiddleware(browserHistory), -]; -const appliedMiddleware = applyMiddleware(...middleware); + authMiddleware, +); const store = createStore( reducers, initialState, compose(appliedMiddleware), ); + export default store; diff --git a/frontend/styles/color.js b/frontend/styles/color.js index 517e2aa4a0..2de3ed7b19 100644 --- a/frontend/styles/color.js +++ b/frontend/styles/color.js @@ -1,16 +1,19 @@ const grey = '#66696f'; export default { + accentLight: '#EAEDFB', brightPurple: '#AE6DDF', darkGrey: '#202532', green: '#4FD061', grey, lightGrey: '#B4B4B4', + link: '#4A90E2', logoPurple: '#9651CA', mediumGrey: '#6F737F', primary: grey, purple: '#c38dec', purpleGrey: '#858495', red: '#FF5850', + successLight: '#94E39F', white: '#FFF', }; diff --git a/frontend/test/mocks.js b/frontend/test/mocks.js index 74d748068c..3f69843aa1 100644 --- a/frontend/test/mocks.js +++ b/frontend/test/mocks.js @@ -1,20 +1,37 @@ import nock from 'nock'; +export const validUser = { + token: 'auth_token', + id: 1, + username: 'admin', + email: 'admin@kolide.co', + name: '', + admin: true, + enabled: true, + needs_password_reset: false, +}; + export const validLoginRequest = () => { return nock('http://localhost:8080') .post('/api/v1/kolide/login') - .reply(200, { - token: 'auth_token', - id: 1, - username: 'admin', - email: 'admin@kolide.co', - name: '', - admin: true, - enabled: true, - needs_password_reset: false, - }); + .reply(200, validUser); +}; + +export const validPasswordResetRequest = () => { + return nock('http://localhost:8080') + .post('/api/v1/kolide/forgot_password') + .reply(200, validUser); +}; + +export const invalidPasswordResetRequest = (error) => { + return nock('http://localhost:8080') + .post('/api/v1/kolide/forgot_password') + .reply(422, { error }); }; export default { + invalidPasswordResetRequest, validLoginRequest, + validPasswordResetRequest, + validUser, };