mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2025-02-17 00:21:20 -05:00
Merge pull request #3485 from benjiwheeler/join-flow-cache-ignores-api-failures
username step and email step caches ignore api failures
This commit is contained in:
commit
77fca39bbf
5 changed files with 227 additions and 21 deletions
|
@ -83,7 +83,10 @@ class EmailStep extends React.Component {
|
||||||
// email is not in our cache
|
// email is not in our cache
|
||||||
return validate.validateEmailRemotely(email).then(
|
return validate.validateEmailRemotely(email).then(
|
||||||
remoteResult => {
|
remoteResult => {
|
||||||
|
// cache result, if it successfully heard back from server
|
||||||
|
if (remoteResult.requestSucceeded) {
|
||||||
this.emailRemoteCache[email] = remoteResult;
|
this.emailRemoteCache[email] = remoteResult;
|
||||||
|
}
|
||||||
return remoteResult;
|
return remoteResult;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -56,7 +56,10 @@ class UsernameStep extends React.Component {
|
||||||
// username is not in our cache
|
// username is not in our cache
|
||||||
return validate.validateUsernameRemotely(username).then(
|
return validate.validateUsernameRemotely(username).then(
|
||||||
remoteResult => {
|
remoteResult => {
|
||||||
|
// cache result, if it successfully heard back from server
|
||||||
|
if (remoteResult.requestSucceeded) {
|
||||||
this.usernameRemoteCache[username] = remoteResult;
|
this.usernameRemoteCache[username] = remoteResult;
|
||||||
|
}
|
||||||
return remoteResult;
|
return remoteResult;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -21,21 +21,21 @@ module.exports.validateUsernameRemotely = username => (
|
||||||
uri: `/accounts/checkusername/${username}/`
|
uri: `/accounts/checkusername/${username}/`
|
||||||
}, (err, body, res) => {
|
}, (err, body, res) => {
|
||||||
if (err || res.statusCode !== 200) {
|
if (err || res.statusCode !== 200) {
|
||||||
resolve({valid: false, errMsgId: 'general.error'});
|
resolve({requestSucceeded: false, valid: false, errMsgId: 'general.error'});
|
||||||
}
|
}
|
||||||
switch (body.msg) {
|
switch (body.msg) {
|
||||||
case 'valid username':
|
case 'valid username':
|
||||||
resolve({valid: true});
|
resolve({requestSucceeded: true, valid: true});
|
||||||
break;
|
break;
|
||||||
case 'username exists':
|
case 'username exists':
|
||||||
resolve({valid: false, errMsgId: 'registration.validationUsernameExists'});
|
resolve({requestSucceeded: true, valid: false, errMsgId: 'registration.validationUsernameExists'});
|
||||||
break;
|
break;
|
||||||
case 'bad username': // i.e., vulgar
|
case 'bad username': // i.e., vulgar
|
||||||
resolve({valid: false, errMsgId: 'registration.validationUsernameNotAllowed'});
|
resolve({requestSucceeded: true, valid: false, errMsgId: 'registration.validationUsernameNotAllowed'});
|
||||||
break;
|
break;
|
||||||
case 'invalid username':
|
case 'invalid username':
|
||||||
default:
|
default:
|
||||||
resolve({valid: false, errMsgId: 'registration.validationUsernameNotAllowed'});
|
resolve({requestSucceeded: true, valid: false, errMsgId: 'registration.validationUsernameNotAllowed'});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -86,16 +86,16 @@ module.exports.validateEmailRemotely = email => (
|
||||||
uri: '/accounts/check_email/'
|
uri: '/accounts/check_email/'
|
||||||
}, (err, body, res) => {
|
}, (err, body, res) => {
|
||||||
if (err || res.statusCode !== 200 || !body || body.length < 1 || !body[0].msg) {
|
if (err || res.statusCode !== 200 || !body || body.length < 1 || !body[0].msg) {
|
||||||
resolve({valid: false, errMsgId: 'general.apiError'});
|
resolve({requestSucceeded: false, valid: false, errMsgId: 'general.apiError'});
|
||||||
}
|
}
|
||||||
switch (body[0].msg) {
|
switch (body[0].msg) {
|
||||||
case 'valid email':
|
case 'valid email':
|
||||||
resolve({valid: true});
|
resolve({requestSucceeded: true, valid: true});
|
||||||
break;
|
break;
|
||||||
case 'Scratch is not allowed to send email to this address.': // e.g., bad TLD or block-listed
|
case 'Scratch is not allowed to send email to this address.': // e.g., bad TLD or block-listed
|
||||||
case 'Enter a valid email address.':
|
case 'Enter a valid email address.':
|
||||||
default:
|
default:
|
||||||
resolve({valid: false, errMsgId: 'registration.validationEmailInvalid'});
|
resolve({requestSucceeded: true, valid: false, errMsgId: 'registration.validationEmailInvalid'});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,9 +4,24 @@ const JoinFlowStep = require('../../../src/components/join-flow/join-flow-step.j
|
||||||
const FormikInput = require('../../../src/components/formik-forms/formik-input.jsx');
|
const FormikInput = require('../../../src/components/formik-forms/formik-input.jsx');
|
||||||
const FormikCheckbox = require('../../../src/components/formik-forms/formik-checkbox.jsx');
|
const FormikCheckbox = require('../../../src/components/formik-forms/formik-checkbox.jsx');
|
||||||
|
|
||||||
const mockedValidateEmailRemotely = jest.fn(() => (
|
const requestSuccessResponse = {
|
||||||
|
requestSucceeded: true,
|
||||||
|
valid: false,
|
||||||
|
errMsgId: 'registration.validationEmailInvalid'
|
||||||
|
};
|
||||||
|
const requestFailureResponse = {
|
||||||
|
requestSucceeded: false,
|
||||||
|
valid: false,
|
||||||
|
errMsgId: 'general.error'
|
||||||
|
};
|
||||||
|
// mockedValidateEmailRemotely will return a promise resolving with remoteRequestResponse.
|
||||||
|
// Using remoteRequestResponse, rather than using requestSuccessResponse directly,
|
||||||
|
// lets us change where remoteRequestResponse points later, without actually changing
|
||||||
|
// mockedValidateEmailRemotely.
|
||||||
|
let remoteRequestResponse = requestSuccessResponse;
|
||||||
|
let mockedValidateEmailRemotely = jest.fn(() => (
|
||||||
/* eslint-disable no-undef */
|
/* eslint-disable no-undef */
|
||||||
Promise.resolve({valid: false, errMsgId: 'registration.validationEmailInvalid'})
|
Promise.resolve(remoteRequestResponse)
|
||||||
/* eslint-enable no-undef */
|
/* eslint-enable no-undef */
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -179,6 +194,13 @@ describe('EmailStep test', () => {
|
||||||
const val = emailStepWrapper.instance().validateEmail();
|
const val = emailStepWrapper.instance().validateEmail();
|
||||||
expect(val).toBe('general.required');
|
expect(val).toBe('general.required');
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validateEmailRemotelyWithCache test with successful requests', () => {
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
test('validateEmailRemotelyWithCache calls validate.validateEmailRemotely', done => {
|
test('validateEmailRemotelyWithCache calls validate.validateEmailRemotely', done => {
|
||||||
const intlWrapper = shallowWithIntl(
|
const intlWrapper = shallowWithIntl(
|
||||||
|
@ -242,3 +264,79 @@ describe('EmailStep test', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('validateEmailRemotelyWithCache test with failing requests', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// needs to be wrapped inside beforeEach, because if not, it gets run as this
|
||||||
|
// test file is loaded, and goes into effect before any of the earlier tests run!
|
||||||
|
remoteRequestResponse = requestFailureResponse;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('validateEmailRemotelyWithCache calls validate.validateEmailRemotely', done => {
|
||||||
|
const wrapper = shallowWithIntl(
|
||||||
|
<EmailStep />);
|
||||||
|
const instance = wrapper.dive().instance();
|
||||||
|
|
||||||
|
instance.validateEmailRemotelyWithCache('some-email@some-domain.com')
|
||||||
|
.then(response => {
|
||||||
|
expect(mockedValidateEmailRemotely).toHaveBeenCalled();
|
||||||
|
expect(response.valid).toBe(false);
|
||||||
|
expect(response.errMsgId).toBe('general.error');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('validateEmailRemotelyWithCache, called twice with different data, makes two remote requests', done => {
|
||||||
|
const wrapper = shallowWithIntl(
|
||||||
|
<EmailStep />
|
||||||
|
);
|
||||||
|
const instance = wrapper.dive().instance();
|
||||||
|
|
||||||
|
instance.validateEmailRemotelyWithCache('some-email@some-domain.com')
|
||||||
|
.then(response => {
|
||||||
|
expect(mockedValidateEmailRemotely).toHaveBeenCalledTimes(1);
|
||||||
|
expect(response.valid).toBe(false);
|
||||||
|
expect(response.errMsgId).toBe('general.error');
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// make the same request a second time
|
||||||
|
instance.validateEmailRemotelyWithCache('different-email@some-domain.org')
|
||||||
|
.then(response => {
|
||||||
|
expect(mockedValidateEmailRemotely).toHaveBeenCalledTimes(2);
|
||||||
|
expect(response.valid).toBe(false);
|
||||||
|
expect(response.errMsgId).toBe('general.error');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('validateEmailRemotelyWithCache, called 2x w/same data, makes 2 requests, since 1st not stored', done => {
|
||||||
|
const wrapper = shallowWithIntl(
|
||||||
|
<EmailStep />
|
||||||
|
);
|
||||||
|
const instance = wrapper.dive().instance();
|
||||||
|
|
||||||
|
instance.validateEmailRemotelyWithCache('some-email@some-domain.com')
|
||||||
|
.then(response => {
|
||||||
|
expect(mockedValidateEmailRemotely).toHaveBeenCalledTimes(1);
|
||||||
|
expect(response.valid).toBe(false);
|
||||||
|
expect(response.errMsgId).toBe('general.error');
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// make the same request a second time
|
||||||
|
instance.validateEmailRemotelyWithCache('some-email@some-domain.com')
|
||||||
|
.then(response => {
|
||||||
|
expect(mockedValidateEmailRemotely).toHaveBeenCalledTimes(2);
|
||||||
|
expect(response.valid).toBe(false);
|
||||||
|
expect(response.errMsgId).toBe('general.error');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -1,9 +1,24 @@
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const {shallowWithIntl} = require('../../helpers/intl-helpers.jsx');
|
const {shallowWithIntl} = require('../../helpers/intl-helpers.jsx');
|
||||||
|
|
||||||
const mockedValidateUsernameRemotely = jest.fn(() => (
|
const requestSuccessResponse = {
|
||||||
|
requestSucceeded: true,
|
||||||
|
valid: false,
|
||||||
|
errMsgId: 'registration.validationUsernameNotAllowed'
|
||||||
|
};
|
||||||
|
const requestFailureResponse = {
|
||||||
|
requestSucceeded: false,
|
||||||
|
valid: false,
|
||||||
|
errMsgId: 'general.error'
|
||||||
|
};
|
||||||
|
// mockedValidateUsernameRemotely will return a promise resolving with remoteRequestResponse.
|
||||||
|
// Using remoteRequestResponse, rather than using requestSuccessResponse directly,
|
||||||
|
// lets us change where remoteRequestResponse points later, without actually changing
|
||||||
|
// mockedValidateUsernameRemotely.
|
||||||
|
let remoteRequestResponse = requestSuccessResponse;
|
||||||
|
let mockedValidateUsernameRemotely = jest.fn(() => (
|
||||||
/* eslint-disable no-undef */
|
/* eslint-disable no-undef */
|
||||||
Promise.resolve({valid: false, errMsgId: 'registration.validationUsernameNotAllowed'})
|
Promise.resolve(remoteRequestResponse)
|
||||||
/* eslint-enable no-undef */
|
/* eslint-enable no-undef */
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -17,11 +32,7 @@ jest.mock('../../../src/lib/validate.js', () => (
|
||||||
// must come after validation mocks, so validate.js will be mocked before it is required
|
// must come after validation mocks, so validate.js will be mocked before it is required
|
||||||
const UsernameStep = require('../../../src/components/join-flow/username-step.jsx');
|
const UsernameStep = require('../../../src/components/join-flow/username-step.jsx');
|
||||||
|
|
||||||
describe('UsernameStep test', () => {
|
describe('UsernameStep tests', () => {
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('send correct props to formik', () => {
|
test('send correct props to formik', () => {
|
||||||
const wrapper = shallowWithIntl(<UsernameStep />);
|
const wrapper = shallowWithIntl(<UsernameStep />);
|
||||||
|
@ -54,14 +65,22 @@ describe('UsernameStep test', () => {
|
||||||
expect(mockedOnNextStep).toHaveBeenCalledWith(formData);
|
expect(mockedOnNextStep).toHaveBeenCalledWith(formData);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validateUsernameRemotelyWithCache test with successful requests', () => {
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
test('validateUsernameRemotelyWithCache calls validate.validateUsernameRemotely', done => {
|
test('validateUsernameRemotelyWithCache calls validate.validateUsernameRemotely', done => {
|
||||||
const wrapper = shallowWithIntl(
|
const wrapper = shallowWithIntl(<UsernameStep />);
|
||||||
<UsernameStep />);
|
|
||||||
const instance = wrapper.dive().instance();
|
const instance = wrapper.dive().instance();
|
||||||
|
|
||||||
instance.validateUsernameRemotelyWithCache('newUniqueUsername55')
|
instance.validateUsernameRemotelyWithCache('newUniqueUsername55')
|
||||||
.then(response => {
|
.then(response => {
|
||||||
expect(mockedValidateUsernameRemotely).toHaveBeenCalled();
|
expect(mockedValidateUsernameRemotely).toHaveBeenCalled();
|
||||||
|
expect(response.requestSucceeded).toBe(true);
|
||||||
expect(response.valid).toBe(false);
|
expect(response.valid).toBe(false);
|
||||||
expect(response.errMsgId).toBe('registration.validationUsernameNotAllowed');
|
expect(response.errMsgId).toBe('registration.validationUsernameNotAllowed');
|
||||||
done();
|
done();
|
||||||
|
@ -77,6 +96,7 @@ describe('UsernameStep test', () => {
|
||||||
instance.validateUsernameRemotelyWithCache('newUniqueUsername55')
|
instance.validateUsernameRemotelyWithCache('newUniqueUsername55')
|
||||||
.then(response => {
|
.then(response => {
|
||||||
expect(mockedValidateUsernameRemotely).toHaveBeenCalledTimes(1);
|
expect(mockedValidateUsernameRemotely).toHaveBeenCalledTimes(1);
|
||||||
|
expect(response.requestSucceeded).toBe(true);
|
||||||
expect(response.valid).toBe(false);
|
expect(response.valid).toBe(false);
|
||||||
expect(response.errMsgId).toBe('registration.validationUsernameNotAllowed');
|
expect(response.errMsgId).toBe('registration.validationUsernameNotAllowed');
|
||||||
})
|
})
|
||||||
|
@ -85,6 +105,7 @@ describe('UsernameStep test', () => {
|
||||||
instance.validateUsernameRemotelyWithCache('secondDifferent66')
|
instance.validateUsernameRemotelyWithCache('secondDifferent66')
|
||||||
.then(response => {
|
.then(response => {
|
||||||
expect(mockedValidateUsernameRemotely).toHaveBeenCalledTimes(2);
|
expect(mockedValidateUsernameRemotely).toHaveBeenCalledTimes(2);
|
||||||
|
expect(response.requestSucceeded).toBe(true);
|
||||||
expect(response.valid).toBe(false);
|
expect(response.valid).toBe(false);
|
||||||
expect(response.errMsgId).toBe('registration.validationUsernameNotAllowed');
|
expect(response.errMsgId).toBe('registration.validationUsernameNotAllowed');
|
||||||
done();
|
done();
|
||||||
|
@ -101,6 +122,7 @@ describe('UsernameStep test', () => {
|
||||||
instance.validateUsernameRemotelyWithCache('newUniqueUsername55')
|
instance.validateUsernameRemotelyWithCache('newUniqueUsername55')
|
||||||
.then(response => {
|
.then(response => {
|
||||||
expect(mockedValidateUsernameRemotely).toHaveBeenCalledTimes(1);
|
expect(mockedValidateUsernameRemotely).toHaveBeenCalledTimes(1);
|
||||||
|
expect(response.requestSucceeded).toBe(true);
|
||||||
expect(response.valid).toBe(false);
|
expect(response.valid).toBe(false);
|
||||||
expect(response.errMsgId).toBe('registration.validationUsernameNotAllowed');
|
expect(response.errMsgId).toBe('registration.validationUsernameNotAllowed');
|
||||||
})
|
})
|
||||||
|
@ -109,6 +131,7 @@ describe('UsernameStep test', () => {
|
||||||
instance.validateUsernameRemotelyWithCache('newUniqueUsername55')
|
instance.validateUsernameRemotelyWithCache('newUniqueUsername55')
|
||||||
.then(response => {
|
.then(response => {
|
||||||
expect(mockedValidateUsernameRemotely).toHaveBeenCalledTimes(1);
|
expect(mockedValidateUsernameRemotely).toHaveBeenCalledTimes(1);
|
||||||
|
expect(response.requestSucceeded).toBe(true);
|
||||||
expect(response.valid).toBe(false);
|
expect(response.valid).toBe(false);
|
||||||
expect(response.errMsgId).toBe('registration.validationUsernameNotAllowed');
|
expect(response.errMsgId).toBe('registration.validationUsernameNotAllowed');
|
||||||
done();
|
done();
|
||||||
|
@ -116,3 +139,82 @@ describe('UsernameStep test', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('validateUsernameRemotelyWithCache test with failing requests', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// needs to be wrapped inside beforeEach, because if not, it gets run as this
|
||||||
|
// test file is loaded, and goes into effect before any of the earlier tests run!
|
||||||
|
remoteRequestResponse = requestFailureResponse;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('validateUsernameRemotelyWithCache calls validate.validateUsernameRemotely', done => {
|
||||||
|
const wrapper = shallowWithIntl(<UsernameStep />);
|
||||||
|
const instance = wrapper.dive().instance();
|
||||||
|
|
||||||
|
instance.validateUsernameRemotelyWithCache('newUniqueUsername55')
|
||||||
|
.then(response => {
|
||||||
|
expect(mockedValidateUsernameRemotely).toHaveBeenCalled();
|
||||||
|
expect(response.requestSucceeded).toBe(false);
|
||||||
|
expect(response.valid).toBe(false);
|
||||||
|
expect(response.errMsgId).toBe('general.error');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('validateUsernameRemotelyWithCache, called twice with different data, makes two remote requests', done => {
|
||||||
|
const wrapper = shallowWithIntl(
|
||||||
|
<UsernameStep />
|
||||||
|
);
|
||||||
|
const instance = wrapper.dive().instance();
|
||||||
|
|
||||||
|
instance.validateUsernameRemotelyWithCache('newUniqueUsername55')
|
||||||
|
.then(response => {
|
||||||
|
expect(mockedValidateUsernameRemotely).toHaveBeenCalledTimes(1);
|
||||||
|
expect(response.requestSucceeded).toBe(false);
|
||||||
|
expect(response.valid).toBe(false);
|
||||||
|
expect(response.errMsgId).toBe('general.error');
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// make the same request a second time
|
||||||
|
instance.validateUsernameRemotelyWithCache('secondDifferent66')
|
||||||
|
.then(response => {
|
||||||
|
expect(mockedValidateUsernameRemotely).toHaveBeenCalledTimes(2);
|
||||||
|
expect(response.requestSucceeded).toBe(false);
|
||||||
|
expect(response.valid).toBe(false);
|
||||||
|
expect(response.errMsgId).toBe('general.error');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('validateUsernameRemotelyWithCache, called 2x w/same data, makes 2 requests, since 1st not stored', done => {
|
||||||
|
const wrapper = shallowWithIntl(
|
||||||
|
<UsernameStep />
|
||||||
|
);
|
||||||
|
const instance = wrapper.dive().instance();
|
||||||
|
|
||||||
|
instance.validateUsernameRemotelyWithCache('newUniqueUsername55')
|
||||||
|
.then(response => {
|
||||||
|
expect(mockedValidateUsernameRemotely).toHaveBeenCalledTimes(1);
|
||||||
|
expect(response.requestSucceeded).toBe(false);
|
||||||
|
expect(response.valid).toBe(false);
|
||||||
|
expect(response.errMsgId).toBe('general.error');
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// make the same request a second time
|
||||||
|
instance.validateUsernameRemotelyWithCache('newUniqueUsername55')
|
||||||
|
.then(response => {
|
||||||
|
expect(mockedValidateUsernameRemotely).toHaveBeenCalledTimes(2);
|
||||||
|
expect(response.requestSucceeded).toBe(false);
|
||||||
|
expect(response.valid).toBe(false);
|
||||||
|
expect(response.errMsgId).toBe('general.error');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue