summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--service/pixelated/resources/account_recovery_resource.py15
-rw-r--r--service/test/unit/resources/test_account_recovery_resource.py11
-rw-r--r--web-ui/src/account_recovery/new_password_form/new_password_form.js13
-rw-r--r--web-ui/src/account_recovery/new_password_form/new_password_form.spec.js18
-rw-r--r--web-ui/src/account_recovery/page.js18
-rw-r--r--web-ui/src/account_recovery/page.spec.js13
-rw-r--r--web-ui/src/account_recovery/user_recovery_code_form/user_recovery_code_form.js10
-rw-r--r--web-ui/src/account_recovery/user_recovery_code_form/user_recovery_code_form.spec.js10
-rw-r--r--web-ui/src/backup_account/backup_email/backup_email.js23
-rw-r--r--web-ui/src/backup_account/backup_email/backup_email.spec.js18
-rw-r--r--web-ui/src/common/util.js21
-rw-r--r--web-ui/src/common/util.spec.js35
12 files changed, 148 insertions, 57 deletions
diff --git a/service/pixelated/resources/account_recovery_resource.py b/service/pixelated/resources/account_recovery_resource.py
index 8cb10fc8..39ebb8d0 100644
--- a/service/pixelated/resources/account_recovery_resource.py
+++ b/service/pixelated/resources/account_recovery_resource.py
@@ -21,6 +21,8 @@ from twisted.python.filepath import FilePath
from pixelated.resources import get_public_static_folder
from twisted.web.http import OK
from twisted.web.template import Element, XMLFile, renderElement
+from twisted.web.server import NOT_DONE_YET
+from twisted.internet import defer
class AccountRecoveryPage(Element):
@@ -44,3 +46,16 @@ class AccountRecoveryResource(BaseResource):
def _render_template(self, request):
site = AccountRecoveryPage()
return renderElement(request, site)
+
+ def render_POST(self, request):
+ def success_response(response):
+ request.setResponseCode(OK)
+ request.finish()
+
+ def error_response(response):
+ request.setResponseCode(INTERNAL_SERVER_ERROR)
+ request.finish()
+
+ d = defer.succeed('Done!')
+ d.addCallbacks(success_response, error_response)
+ return NOT_DONE_YET
diff --git a/service/test/unit/resources/test_account_recovery_resource.py b/service/test/unit/resources/test_account_recovery_resource.py
index d4df7716..cd9acae7 100644
--- a/service/test/unit/resources/test_account_recovery_resource.py
+++ b/service/test/unit/resources/test_account_recovery_resource.py
@@ -42,3 +42,14 @@ class TestAccountRecoveryResource(unittest.TestCase):
d.addCallback(assert_200_when_user_logged_in)
return d
+
+ def test_post_returns_successfully(self):
+ request = DummyRequest(['/account-recovery'])
+ request.method = 'POST'
+ d = self.web.get(request)
+
+ def assert_successful_response(_):
+ self.assertEqual(200, request.responseCode)
+
+ d.addCallback(assert_successful_response)
+ return d
diff --git a/web-ui/src/account_recovery/new_password_form/new_password_form.js b/web-ui/src/account_recovery/new_password_form/new_password_form.js
index f1097b0b..b18bc8ff 100644
--- a/web-ui/src/account_recovery/new_password_form/new_password_form.js
+++ b/web-ui/src/account_recovery/new_password_form/new_password_form.js
@@ -15,17 +15,23 @@
* along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
*/
+import 'isomorphic-fetch';
import React from 'react';
import { translate } from 'react-i18next';
+import { submitForm } from 'src/common/util';
import InputField from 'src/common/input_field/input_field';
import SubmitButton from 'src/common/submit_button/submit_button';
import BackLink from 'src/common/back_link/back_link';
import './new_password_form.scss';
-export const NewPasswordForm = ({ t, previous }) => (
- <form className='account-recovery-form new-password'>
+const submitHandler = code => event => (
+ submitForm(event, '/account-recovery', { userCode: code })
+);
+
+export const NewPasswordForm = ({ t, previous, userCode }) => (
+ <form className='account-recovery-form new-password' onSubmit={submitHandler(userCode)}>
<img
className='account-recovery-progress'
src='/public/images/account-recovery/step_3.svg'
@@ -47,7 +53,8 @@ export const NewPasswordForm = ({ t, previous }) => (
NewPasswordForm.propTypes = {
t: React.PropTypes.func.isRequired,
- previous: React.PropTypes.func.isRequired
+ previous: React.PropTypes.func.isRequired,
+ userCode: React.PropTypes.string.isRequired
};
export default translate('', { wait: true })(NewPasswordForm);
diff --git a/web-ui/src/account_recovery/new_password_form/new_password_form.spec.js b/web-ui/src/account_recovery/new_password_form/new_password_form.spec.js
index d2bd350c..5ffd9720 100644
--- a/web-ui/src/account_recovery/new_password_form/new_password_form.spec.js
+++ b/web-ui/src/account_recovery/new_password_form/new_password_form.spec.js
@@ -1,6 +1,7 @@
import { shallow } from 'enzyme';
import expect from 'expect';
import React from 'react';
+import fetchMock from 'fetch-mock';
import { NewPasswordForm } from './new_password_form';
describe('NewPasswordForm', () => {
@@ -11,7 +12,7 @@ describe('NewPasswordForm', () => {
const mockTranslations = key => key;
mockPrevious = expect.createSpy();
newPasswordForm = shallow(
- <NewPasswordForm t={mockTranslations} previous={mockPrevious} />
+ <NewPasswordForm t={mockTranslations} previous={mockPrevious} userCode='def234' />
);
});
@@ -37,4 +38,19 @@ describe('NewPasswordForm', () => {
newPasswordForm.find('BackLink').simulate('click');
expect(mockPrevious).toHaveBeenCalled();
});
+
+ describe('Submit', () => {
+ beforeEach(() => {
+ fetchMock.post('/account-recovery', 200);
+ newPasswordForm.find('form').simulate('submit', { preventDefault: expect.createSpy() });
+ });
+
+ it('posts to account recovery', () => {
+ expect(fetchMock.called('/account-recovery')).toBe(true, 'POST was not called');
+ });
+
+ it('sends user code as content', () => {
+ expect(fetchMock.lastOptions('/account-recovery').body).toContain('"userCode":"def234"');
+ });
+ });
});
diff --git a/web-ui/src/account_recovery/page.js b/web-ui/src/account_recovery/page.js
index 579f17cc..2d33e2fb 100644
--- a/web-ui/src/account_recovery/page.js
+++ b/web-ui/src/account_recovery/page.js
@@ -32,7 +32,7 @@ export class Page extends React.Component {
constructor(props) {
super(props);
- this.state = { step: 0 };
+ this.state = { step: 0, userCode: '' };
}
nextStep = (event) => {
@@ -44,13 +44,19 @@ export class Page extends React.Component {
this.setState({ step: this.state.step - 1 });
}
- steps = {
- 0: <AdminRecoveryCodeForm next={this.nextStep} />,
- 1: <UserRecoveryCodeForm previous={this.previousStep} next={this.nextStep} />,
- 2: <NewPasswordForm previous={this.previousStep} />
+ saveUserCode = (event) => {
+ this.setState({ userCode: event.target.value });
}
- mainContent = () => this.steps[this.state.step];
+ steps = () => ({
+ 0: <AdminRecoveryCodeForm next={this.nextStep} />,
+ 1: (<UserRecoveryCodeForm
+ previous={this.previousStep} next={this.nextStep} saveCode={this.saveUserCode}
+ />),
+ 2: <NewPasswordForm previous={this.previousStep} userCode={this.state.userCode} />
+ })
+
+ mainContent = () => this.steps()[this.state.step];
render() {
const t = this.props.t;
diff --git a/web-ui/src/account_recovery/page.spec.js b/web-ui/src/account_recovery/page.spec.js
index 68debba0..31a748be 100644
--- a/web-ui/src/account_recovery/page.spec.js
+++ b/web-ui/src/account_recovery/page.spec.js
@@ -12,10 +12,12 @@ import NewPasswordFormWrapper from './new_password_form/new_password_form';
describe('Account Recovery Page', () => {
let page;
+ let pageInstance;
beforeEach(() => {
const mockTranslations = key => key;
page = shallow(<Page t={mockTranslations} />);
+ pageInstance = page.instance();
});
it('renders account recovery page title', () => {
@@ -30,13 +32,12 @@ describe('Account Recovery Page', () => {
expect(page.find(Footer).length).toEqual(1);
});
- context('main content', () => {
- let pageInstance;
-
- beforeEach(() => {
- pageInstance = page.instance();
- });
+ it('saves user code', () => {
+ pageInstance.saveUserCode({ target: { value: '123' } });
+ expect(pageInstance.state.userCode).toEqual('123');
+ });
+ context('main content', () => {
it('renders admin recovery code form as default form', () => {
expect(page.find(AdminRecoveryCodeFormWrapper).length).toEqual(1);
expect(page.find(UserRecoveryCodeFormWrapper).length).toEqual(0);
diff --git a/web-ui/src/account_recovery/user_recovery_code_form/user_recovery_code_form.js b/web-ui/src/account_recovery/user_recovery_code_form/user_recovery_code_form.js
index a4119885..c39c894d 100644
--- a/web-ui/src/account_recovery/user_recovery_code_form/user_recovery_code_form.js
+++ b/web-ui/src/account_recovery/user_recovery_code_form/user_recovery_code_form.js
@@ -24,7 +24,7 @@ import BackLink from 'src/common/back_link/back_link';
import './user_recovery_code_form.scss';
-export const UserRecoveryCodeForm = ({ t, previous, next }) => (
+export const UserRecoveryCodeForm = ({ t, previous, next, saveCode }) => (
<form className='account-recovery-form user-code' onSubmit={next}>
<img
className='account-recovery-progress'
@@ -40,7 +40,10 @@ export const UserRecoveryCodeForm = ({ t, previous, next }) => (
/>
<p>{t('account-recovery.user-form.description')}</p>
</div>
- <InputField name='admin-code' label={t('account-recovery.user-form.input-label')} />
+ <InputField
+ name='user-code' label={t('account-recovery.user-form.input-label')}
+ onChange={saveCode}
+ />
<SubmitButton buttonText={t('account-recovery.button-next')} />
<BackLink text={t('account-recovery.back')} onClick={previous} />
</form>
@@ -49,7 +52,8 @@ export const UserRecoveryCodeForm = ({ t, previous, next }) => (
UserRecoveryCodeForm.propTypes = {
t: React.PropTypes.func.isRequired,
previous: React.PropTypes.func.isRequired,
- next: React.PropTypes.func.isRequired
+ next: React.PropTypes.func.isRequired,
+ saveCode: React.PropTypes.func.isRequired
};
export default translate('', { wait: true })(UserRecoveryCodeForm);
diff --git a/web-ui/src/account_recovery/user_recovery_code_form/user_recovery_code_form.spec.js b/web-ui/src/account_recovery/user_recovery_code_form/user_recovery_code_form.spec.js
index e47f2e6c..386c3a19 100644
--- a/web-ui/src/account_recovery/user_recovery_code_form/user_recovery_code_form.spec.js
+++ b/web-ui/src/account_recovery/user_recovery_code_form/user_recovery_code_form.spec.js
@@ -7,14 +7,17 @@ describe('UserRecoveryCodeForm', () => {
let userRecoveryCodeForm;
let mockNext;
let mockPrevious;
+ let mockSaveCode;
beforeEach(() => {
const mockTranslations = key => key;
mockNext = expect.createSpy();
mockPrevious = expect.createSpy();
+ mockSaveCode = expect.createSpy();
userRecoveryCodeForm = shallow(
<UserRecoveryCodeForm
- t={mockTranslations} next={mockNext} previous={mockPrevious}
+ t={mockTranslations} next={mockNext}
+ previous={mockPrevious} saveCode={mockSaveCode}
/>
);
});
@@ -44,4 +47,9 @@ describe('UserRecoveryCodeForm', () => {
userRecoveryCodeForm.find('BackLink').simulate('click');
expect(mockPrevious).toHaveBeenCalled();
});
+
+ it('saves code on input change', () => {
+ userRecoveryCodeForm.find('InputField').simulate('change', '123');
+ expect(mockSaveCode).toHaveBeenCalledWith('123');
+ });
});
diff --git a/web-ui/src/backup_account/backup_email/backup_email.js b/web-ui/src/backup_account/backup_email/backup_email.js
index 8fa71191..ac64f02e 100644
--- a/web-ui/src/backup_account/backup_email/backup_email.js
+++ b/web-ui/src/backup_account/backup_email/backup_email.js
@@ -19,8 +19,8 @@ import 'isomorphic-fetch';
import React from 'react';
import { translate } from 'react-i18next';
import validator from 'validator';
-import browser from 'helpers/browser';
+import { submitForm } from 'src/common/util';
import SubmitButton from 'src/common/submit_button/submit_button';
import InputField from 'src/common/input_field/input_field';
import BackLink from 'src/common/back_link/back_link';
@@ -45,24 +45,11 @@ export class BackupEmail extends React.Component {
};
submitHandler = (event) => {
- event.preventDefault();
-
- fetch('/backup-account', {
- credentials: 'same-origin',
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- csrftoken: [browser.getCookie('XSRF-TOKEN')],
- backupEmail: this.state.backupEmail
- })
+ submitForm(event, '/backup-account', {
+ backupEmail: this.state.backupEmail
}).then((response) => {
- if (response.ok) {
- this.props.onSubmit('success');
- } else {
- this.props.onSubmit('error');
- }
+ if (response.ok) this.props.onSubmit('success');
+ else this.props.onSubmit('error');
});
};
diff --git a/web-ui/src/backup_account/backup_email/backup_email.spec.js b/web-ui/src/backup_account/backup_email/backup_email.spec.js
index b23aadaa..a34afa06 100644
--- a/web-ui/src/backup_account/backup_email/backup_email.spec.js
+++ b/web-ui/src/backup_account/backup_email/backup_email.spec.js
@@ -3,7 +3,6 @@ import expect from 'expect';
import React from 'react';
import fetchMock from 'fetch-mock';
import { BackupEmail } from 'src/backup_account/backup_email/backup_email';
-import browser from 'helpers/browser';
describe('BackupEmail', () => {
let backupEmail;
@@ -104,7 +103,6 @@ describe('BackupEmail', () => {
context('on success', () => {
beforeEach((done) => {
mockOnSubmit = expect.createSpy().andCall(() => done());
- expect.spyOn(browser, 'getCookie').andReturn('abc123');
fetchMock.post('/backup-account', 204);
backupEmail = shallow(<BackupEmail t={mockTranslations} onSubmit={mockOnSubmit} />);
@@ -117,26 +115,10 @@ describe('BackupEmail', () => {
expect(fetchMock.called('/backup-account')).toBe(true, 'Backup account POST was not called');
});
- it('sends csrftoken as content', () => {
- expect(fetchMock.lastOptions('/backup-account').body).toContain('"csrftoken":["abc123"]');
- });
-
it('sends user email as content', () => {
expect(fetchMock.lastOptions('/backup-account').body).toContain('"backupEmail":"test@test.com"');
});
- it('sends content-type header', () => {
- expect(fetchMock.lastOptions('/backup-account').headers['Content-Type']).toEqual('application/json');
- });
-
- it('sends same origin headers', () => {
- expect(fetchMock.lastOptions('/backup-account').credentials).toEqual('same-origin');
- });
-
- it('prevents default call to refresh page', () => {
- expect(preventDefaultSpy).toHaveBeenCalled();
- });
-
it('calls onSubmit from props with success', () => {
expect(mockOnSubmit).toHaveBeenCalledWith('success');
});
diff --git a/web-ui/src/common/util.js b/web-ui/src/common/util.js
index effb3d9c..c70a8444 100644
--- a/web-ui/src/common/util.js
+++ b/web-ui/src/common/util.js
@@ -1,8 +1,27 @@
+import browser from 'helpers/browser';
+
export const hasQueryParameter = (param) => {
const decodedUri = decodeURIComponent(window.location.search.substring(1));
return !(decodedUri.split('&').indexOf(param) < 0);
};
+export const submitForm = (event, url, body = {}) => {
+ event.preventDefault();
+
+ return fetch(url, {
+ credentials: 'same-origin',
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ csrftoken: [browser.getCookie('XSRF-TOKEN')],
+ ...body
+ })
+ });
+};
+
export default {
- hasQueryParameter
+ hasQueryParameter,
+ submitForm
};
diff --git a/web-ui/src/common/util.spec.js b/web-ui/src/common/util.spec.js
index 805d9dd5..a79859a0 100644
--- a/web-ui/src/common/util.spec.js
+++ b/web-ui/src/common/util.spec.js
@@ -1,4 +1,7 @@
import expect from 'expect';
+import fetchMock from 'fetch-mock';
+
+import browser from 'helpers/browser';
import Util from 'src/common/util';
describe('Utils', () => {
@@ -17,4 +20,36 @@ describe('Utils', () => {
expect(Util.hasQueryParameter('error')).toBe(false);
});
});
+
+ describe('submitForm', () => {
+ const event = {};
+
+ beforeEach(() => {
+ event.preventDefault = expect.createSpy();
+ expect.spyOn(browser, 'getCookie').andReturn('abc123');
+
+ fetchMock.post('/some-url', 200);
+ Util.submitForm(event, '/some-url', { userCode: '123' });
+ });
+
+ it('sends csrftoken as content', () => {
+ expect(fetchMock.lastOptions('/some-url').body).toContain('"csrftoken":["abc123"]');
+ });
+
+ it('sends body as content', () => {
+ expect(fetchMock.lastOptions('/some-url').body).toContain('"userCode":"123"');
+ });
+
+ it('sends content-type header', () => {
+ expect(fetchMock.lastOptions('/some-url').headers['Content-Type']).toEqual('application/json');
+ });
+
+ it('sends same origin headers', () => {
+ expect(fetchMock.lastOptions('/some-url').credentials).toEqual('same-origin');
+ });
+
+ it('prevents default call to refresh page', () => {
+ expect(event.preventDefault).toHaveBeenCalled();
+ });
+ });
});