From e118cf11df6f4f5f0cb8cdf8c0e4930b57aece07 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Mon, 4 Nov 2024 15:52:24 -0500 Subject: [PATCH 1/3] Add error handling for failed reCAPTCHA execute changelog: Internal, reCAPTCHA, Add error handling for failed reCAPTCHA execute --- app/javascript/packages/analytics/index.ts | 2 + .../captcha-submit-button-element.spec.ts | 45 ++++++++++++++++++- .../captcha-submit-button-element.ts | 11 ++++- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/app/javascript/packages/analytics/index.ts b/app/javascript/packages/analytics/index.ts index 1e9261ddae5..22fa2d91308 100644 --- a/app/javascript/packages/analytics/index.ts +++ b/app/javascript/packages/analytics/index.ts @@ -24,6 +24,8 @@ export function trackEvent(event: string, payload?: object) { * Logs an error. * * @param error Error object. + * @param event Error event, if error is caught using an `error` event handler. Including this can + * add additional resolution to the logged error, notably the filename where the error occurred. */ export const trackError = ({ name, message, stack }: Error, event?: ErrorEvent) => trackEvent('Frontend Error', { name, message, stack, filename: event?.filename }); diff --git a/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts b/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts index ca980626684..69fa50a52ce 100644 --- a/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts +++ b/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts @@ -1,12 +1,26 @@ +import quibble from 'quibble'; import type { SinonStub } from 'sinon'; import userEvent from '@testing-library/user-event'; import { screen, waitFor, fireEvent } from '@testing-library/dom'; import { useSandbox, useDefineProperty } from '@18f/identity-test-helpers'; import '@18f/identity-spinner-button/spinner-button-element'; -import './captcha-submit-button-element'; describe('CaptchaSubmitButtonElement', () => { const sandbox = useSandbox(); + const trackError = sandbox.stub(); + + before(async () => { + quibble('@18f/identity-analytics', { trackError }); + await import('./captcha-submit-button-element'); + }); + + afterEach(() => { + trackError.reset(); + }); + + after(() => { + quibble.reset(); + }); context('without ancestor form element', () => { beforeEach(() => { @@ -157,6 +171,35 @@ describe('CaptchaSubmitButtonElement', () => { await waitFor(() => expect(didSubmit).to.be.true()); }); }); + + context('when recaptcha fails to execute', () => { + let error: Error; + + beforeEach(() => { + error = new Error('Invalid site key or not loaded in api.js: badkey'); + ((global as any).grecaptcha.execute as SinonStub).throws(error); + }); + + it('does not prevent default form submission', async () => { + const button = screen.getByRole('button', { name: 'Submit' }); + const form = document.querySelector('form')!; + + sandbox.stub(form, 'submit'); + + await userEvent.click(button); + await expect(form.submit).to.eventually.be.called(); + }); + + it('tracks error', async () => { + const button = screen.getByRole('button', { name: 'Submit' }); + const form = document.querySelector('form')!; + + sandbox.stub(form, 'submit'); + await userEvent.click(button); + + await expect(trackError).to.eventually.be.calledWith(error); + }); + }); }); }); }); diff --git a/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.ts b/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.ts index 3ec45cbb5b0..45f6e33afeb 100644 --- a/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.ts +++ b/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.ts @@ -1,3 +1,5 @@ +import { trackError } from '@18f/identity-analytics'; + class CaptchaSubmitButtonElement extends HTMLElement { form: HTMLFormElement | null; @@ -46,7 +48,14 @@ class CaptchaSubmitButtonElement extends HTMLElement { invokeChallenge() { this.recaptchaClient!.ready(async () => { const { recaptchaSiteKey: siteKey, recaptchaAction: action } = this; - const token = await this.recaptchaClient!.execute(siteKey!, { action }); + + let token; + try { + token = await this.recaptchaClient!.execute(siteKey!, { action }); + } catch (error) { + trackError(error); + } + this.tokenInput.value = token; this.submit(); }); From eab038acfec0745a72bf9493e52266fa07f05148 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Mon, 4 Nov 2024 16:02:20 -0500 Subject: [PATCH 2/3] Improve line breaks for arrange, act, assert --- .../captcha-submit-button/captcha-submit-button-element.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts b/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts index 69fa50a52ce..fbad06ad672 100644 --- a/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts +++ b/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts @@ -193,8 +193,8 @@ describe('CaptchaSubmitButtonElement', () => { it('tracks error', async () => { const button = screen.getByRole('button', { name: 'Submit' }); const form = document.querySelector('form')!; - sandbox.stub(form, 'submit'); + await userEvent.click(button); await expect(trackError).to.eventually.be.calledWith(error); From add00222d1ccd6a86071085944aded71136fdc99 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Mon, 4 Nov 2024 16:02:45 -0500 Subject: [PATCH 3/3] Improve line breaks for arrange, act, assert --- .../captcha-submit-button/captcha-submit-button-element.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts b/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts index fbad06ad672..f0bdece5bb7 100644 --- a/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts +++ b/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts @@ -183,10 +183,10 @@ describe('CaptchaSubmitButtonElement', () => { it('does not prevent default form submission', async () => { const button = screen.getByRole('button', { name: 'Submit' }); const form = document.querySelector('form')!; - sandbox.stub(form, 'submit'); await userEvent.click(button); + await expect(form.submit).to.eventually.be.called(); });