Add more descriptive errors for IDV submission

This commit is contained in:
Bianca Severino
2020-12-14 14:29:21 -05:00
parent 402dbae44f
commit 600a2b8fe2
4 changed files with 94 additions and 26 deletions

View File

@@ -576,6 +576,21 @@ const messages = defineMessages({
defaultMessage: 'Submit',
description: 'Button to confirm all information is correct and submit.',
},
'id.verification.submission.alert.error.face': {
id: 'id.verification.submission.alert.error.face',
defaultMessage: 'A photo of your face is required. Please retake your portrait photo.',
description: 'Error message displayed when the user\'s portrait photo is missing.',
},
'id.verification.submission.alert.error.id': {
id: 'id.verification.submission.alert.error.id',
defaultMessage: 'A photo of your ID card is required. Please retake your ID photo.',
description: 'Error message displayed when the user\'s ID photo is missing.',
},
'id.verification.submission.alert.error.name': {
id: 'id.verification.submission.alert.error.name',
defaultMessage: 'A valid account name is required. Please update your account name to match the name on your ID.',
description: 'Error message displayed when the user\'s account name is missing.',
},
'id.verification.review.error': {
id: 'id.verification.review.error',
defaultMessage: 'edX Support Page',

View File

@@ -84,6 +84,9 @@ export async function submitIdVerification(verificationData) {
await getAuthenticatedHttpClient().post(url, urlEncodedPostData, requestConfig);
return { success: true, message: null };
} catch (e) {
return { success: false, message: String(e) }; // TODO: is String(e) right?
return {
success: false,
status: e.customAttributes.httpErrorStatus,
message: String(e) };
}
}

View File

@@ -25,7 +25,7 @@ function SummaryPanel(props) {
} = useContext(IdVerificationContext);
const nameToBeUsed = idPhotoName || nameOnAccount || '';
const [isSubmitting, setIsSubmitting] = useState(false);
const [submissionError, setSubmissionError] = useState(false);
const [submissionError, setSubmissionError] = useState(null);
function SubmitButton() {
async function handleClick() {
@@ -43,7 +43,7 @@ function SummaryPanel(props) {
} else {
stopUserMedia();
setIsSubmitting(false);
setSubmissionError(true);
setSubmissionError(result);
}
}
return (
@@ -59,6 +59,29 @@ function SummaryPanel(props) {
);
}
function getError() {
if (submissionError.status === 400) {
if (submissionError.message.includes('face_image')) {
return props.intl.formatMessage(messages['id.verification.submission.alert.error.face']);
} else if (submissionError.message.includes('Photo ID image')) {
return props.intl.formatMessage(messages['id.verification.submission.alert.error.id']);
} else if (submissionError.message.includes('Name')) {
return props.intl.formatMessage(messages['id.verification.submission.alert.error.name']);
}
}
return (
<FormattedMessage
id="idv.submission.alert.error"
defaultMessage={`
We encountered a technical error while trying to submit ID verification.
This might be a temporary issue, so please try again in a few minutes.
If the problem persists, please go to {support_link} for help.
`}
values={{ support_link: <Alert.Link href="https://support.edx.org/hc/en-us">{props.intl.formatMessage(messages['id.verification.review.error'])}</Alert.Link> }}
/>
);
}
return (
<BasePanel
name={panelSlug}
@@ -69,18 +92,9 @@ function SummaryPanel(props) {
variant="danger"
data-testid="submission-error"
dismissible
onClose={() => setSubmissionError(false)}
onClose={() => setSubmissionError(null)}
>
<FormattedMessage
id="idv.submission.alert.error"
defaultMessage={`
We encountered a technical error while trying to submit ID verification.
This might be a temporary issue, so please try again in a few minutes.
If the problem persists,
please go to {support_link} for help.
`}
values={{ support_link: <Alert.Link href="https://support.edx.org/hc/en-us">{props.intl.formatMessage(messages['id.verification.review.error'])}</Alert.Link> }}
/>
{getError()}
</Alert>}
<p>
{props.intl.formatMessage(messages['id.verification.review.description'])}

View File

@@ -33,7 +33,7 @@ describe('SummaryPanel', () => {
stopUserMedia: jest.fn(),
};
beforeEach(async () => {
const getPanel = async () => {
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
@@ -43,13 +43,14 @@ describe('SummaryPanel', () => {
</IntlProvider>
</Router>
)));
});
};
afterEach(() => {
cleanup();
});
it('routes back to TakePortraitPhotoPanel', async () => {
await getPanel();
const button = await screen.findByTestId('portrait-retake');
fireEvent.click(button);
expect(history.location.pathname).toEqual('/take-portrait-photo');
@@ -57,6 +58,7 @@ describe('SummaryPanel', () => {
});
it('routes back to TakeIdPhotoPanel', async () => {
await getPanel();
const button = await screen.findByTestId('id-retake');
fireEvent.click(button);
expect(history.location.pathname).toEqual('/take-id-photo');
@@ -64,6 +66,7 @@ describe('SummaryPanel', () => {
});
it('allows user to upload ID photo', async () => {
await getPanel();
const collapsible = await screen.getAllByRole('button', { 'aria-expanded': false })[0];
fireEvent.click(collapsible);
const uploadButton = await screen.getByTestId('fileUpload');
@@ -71,6 +74,7 @@ describe('SummaryPanel', () => {
});
it('submits', async () => {
await getPanel();
const button = await screen.findByTestId('submit-button');
fireEvent.click(button);
expect(dataService.submitIdVerification).toHaveBeenCalled();
@@ -78,21 +82,53 @@ describe('SummaryPanel', () => {
});
it('shows error when cannot submit', async () => {
await cleanup();
dataService.submitIdVerification = jest.fn().mockReturnValue({ success: false });
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlSummaryPanel {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
await getPanel();
const button = await screen.findByTestId('submit-button');
await act(async () => fireEvent.click(button));
expect(dataService.submitIdVerification).toHaveBeenCalled();
const error = await screen.getByTestId('submission-error');
expect(error).toBeDefined();
});
it('displays correct error for missing portrait photo', async () => {
dataService.submitIdVerification = jest.fn().mockReturnValue({
success: false,
status: 400,
message: 'Missing required parameter face_image',
});
await getPanel();
const button = await screen.findByTestId('submit-button');
await act(async () => fireEvent.click(button));
const error = await screen.getByTestId('submission-error');
expect(error).toHaveTextContent('A photo of your face is required. Please retake your portrait photo.');
});
it('displays correct error for missing id photo', async () => {
dataService.submitIdVerification = jest.fn().mockReturnValue({
success: false,
status: 400,
message: 'Photo ID image is required if the user does not have an initial verification attempt.',
});
await getPanel();
const button = await screen.findByTestId('submit-button');
await act(async () => fireEvent.click(button));
const error = await screen.getByTestId('submission-error');
expect(error).toHaveTextContent('A photo of your ID card is required. Please retake your ID photo.');
});
it('displays correct error for missing account name', async () => {
dataService.submitIdVerification = jest.fn().mockReturnValue({
success: false,
status: 400,
message: 'Name must be at least 1 character long.',
});
await getPanel();
const button = await screen.findByTestId('submit-button');
await act(async () => fireEvent.click(button));
const error = await screen.getByTestId('submission-error');
expect(error).toHaveTextContent(
'A valid account name is required. Please update your account name to match the name on your ID.'
);
});
});