From 41e4e61baafb7b9a307cfba1b161614d33be8e95 Mon Sep 17 00:00:00 2001 From: Bill Randall <william.randall@cru.org> Date: Sat, 30 Nov 2024 21:48:55 -0500 Subject: [PATCH 1/7] Temporarily disable recaptcha since we went over quota --- .../components/Recaptcha/Recaptcha.test.tsx | 20 ++--- src/common/components/Recaptcha/Recaptcha.tsx | 83 ++++++++++--------- 2 files changed, 52 insertions(+), 51 deletions(-) diff --git a/src/common/components/Recaptcha/Recaptcha.test.tsx b/src/common/components/Recaptcha/Recaptcha.test.tsx index cb7e1994a..614988c42 100644 --- a/src/common/components/Recaptcha/Recaptcha.test.tsx +++ b/src/common/components/Recaptcha/Recaptcha.test.tsx @@ -45,7 +45,7 @@ describe('Recaptcha component', () => { expect(recaptchaEnabledButton.innerHTML).toEqual('Label') }) - it('should disable the button until ready', async () => { + xit('should disable the button until ready', async () => { global.window.grecaptcha = undefined const { getByRole } = render(buildRecaptcha()) @@ -56,7 +56,7 @@ describe('Recaptcha component', () => { await waitFor(() => expect((recaptchaEnabledButton as HTMLButtonElement).disabled).toEqual(false)) }) - it('should successfully pass the recaptcha', async () => { + xit('should successfully pass the recaptcha', async () => { //@ts-ignore global.fetch = jest.fn(() => { return Promise.resolve({ @@ -77,7 +77,7 @@ describe('Recaptcha component', () => { }) }) - it('should successfully pass the recaptcha on branded checkout', async () => { + xit('should successfully pass the recaptcha on branded checkout', async () => { //@ts-ignore global.fetch = jest.fn(() => { return Promise.resolve({ @@ -98,7 +98,7 @@ describe('Recaptcha component', () => { }) }) - it('should log a warning due to low score', async () => { + xit('should log a warning due to low score', async () => { //@ts-ignore global.fetch = jest.fn(() => { return Promise.resolve({ @@ -119,7 +119,7 @@ describe('Recaptcha component', () => { }) }) - it('should fail the recaptcha call', async () => { + xit('should fail the recaptcha call', async () => { //@ts-ignore global.fetch = jest.fn(() => { return Promise.resolve({ @@ -140,7 +140,7 @@ describe('Recaptcha component', () => { }) }) - it('should call the fail function when not a valid action', async () => { + xit('should call the fail function when not a valid action', async () => { //@ts-ignore global.fetch = jest.fn(() => { return Promise.resolve({ @@ -161,7 +161,7 @@ describe('Recaptcha component', () => { }) }) - it('should skip the recaptcha call', async () => { + xit('should skip the recaptcha call', async () => { //@ts-ignore global.window.grecaptcha = { ready: mockRecaptchaReady } @@ -178,7 +178,7 @@ describe('Recaptcha component', () => { }) }) - it('should not block the gift if something went wrong with recaptcha', async () => { + xit('should not block the gift if something went wrong with recaptcha', async () => { //@ts-ignore global.fetch = jest.fn(() => { return Promise.reject('Failed') @@ -198,7 +198,7 @@ describe('Recaptcha component', () => { }) }) - it('should not block the gift if something went wrong with recaptcha JSON', async () => { + xit('should not block the gift if something went wrong with recaptcha JSON', async () => { //@ts-ignore global.fetch = jest.fn(() => { return Promise.resolve({ @@ -220,7 +220,7 @@ describe('Recaptcha component', () => { }) }) - it('should not block gifts if something weird happens', async () => { + xit('should not block gifts if something weird happens', async () => { //@ts-ignore global.fetch = jest.fn(() => { return Promise.resolve({ diff --git a/src/common/components/Recaptcha/Recaptcha.tsx b/src/common/components/Recaptcha/Recaptcha.tsx index 49f7163e3..22760f64a 100644 --- a/src/common/components/Recaptcha/Recaptcha.tsx +++ b/src/common/components/Recaptcha/Recaptcha.tsx @@ -71,49 +71,50 @@ export const Recaptcha = ({ }, [grecaptcha, buttonId]) const handleReCaptchaVerify = useCallback(async () => { - if (!ready) { - $log.info('Execute recaptcha not yet available') - return - } + // if (!ready) { + // $log.info('Execute recaptcha not yet available') + // return + // } - grecaptcha.ready(async () => { - try { - const token = await grecaptcha.execute(recaptchaKey, { action: action }) - const serverResponse = await fetch(`${apiUrl}/recaptcha/verify`, { - method: 'POST', - body: JSON.stringify({ token: token }), - headers: { 'Content-Type': 'application/json' } - }) - const data = await serverResponse.json() + // grecaptcha.ready(async () => { + // try { + // const token = await grecaptcha.execute(recaptchaKey, { action: action }) + // const serverResponse = await fetch(`${apiUrl}/recaptcha/verify`, { + // method: 'POST', + // body: JSON.stringify({ token: token }), + // headers: { 'Content-Type': 'application/json' } + // }) + // const data = await serverResponse.json() - if (data?.success === true && isValidAction(data?.action)) { - if (data.score < 0.5) { - $log.warn(`Captcha score was below the threshold: ${data.score}`) - onFailure(componentInstance) - return - } - onSuccess(componentInstance) - return - } - if (data?.success === false && isValidAction(data?.action)) { - $log.warn('Recaptcha call was unsuccessful, continuing anyway') - onSuccess(componentInstance) - return - } - if (!data) { - $log.warn('Data was missing!') - onSuccess(componentInstance) - return - } - if (!isValidAction(data?.action)) { - $log.warn(`Invalid action: ${data?.action}`) - onFailure(componentInstance) - } - } catch (error) { - $log.error(`Failed to verify recaptcha, continuing on: ${error}`) - onSuccess(componentInstance) - } - }) + // if (data?.success === true && isValidAction(data?.action)) { + // if (data.score < 0.5) { + // $log.warn(`Captcha score was below the threshold: ${data.score}`) + // onFailure(componentInstance) + // return + // } + // onSuccess(componentInstance) + // return + // } + // if (data?.success === false && isValidAction(data?.action)) { + // $log.warn('Recaptcha call was unsuccessful, continuing anyway') + // onSuccess(componentInstance) + // return + // } + // if (!data) { + // $log.warn('Data was missing!') + // onSuccess(componentInstance) + // return + // } + // if (!isValidAction(data?.action)) { + // $log.warn(`Invalid action: ${data?.action}`) + // onFailure(componentInstance) + // } + // } catch (error) { + // $log.error(`Failed to verify recaptcha, continuing on: ${error}`) + // onSuccess(componentInstance) + // } + // }) + onSuccess(componentInstance) }, [grecaptcha, buttonId, ready]) return ( From 18e3bdc26069ff6a05512434b1ce857ad58b9c56 Mon Sep 17 00:00:00 2001 From: Bill Randall <william.randall@cru.org> Date: Mon, 2 Dec 2024 10:21:07 -0500 Subject: [PATCH 2/7] Revert "Temporarily disable recaptcha since we went over quota" This reverts commit 41e4e61baafb7b9a307cfba1b161614d33be8e95. --- .../components/Recaptcha/Recaptcha.test.tsx | 20 ++--- src/common/components/Recaptcha/Recaptcha.tsx | 83 +++++++++---------- 2 files changed, 51 insertions(+), 52 deletions(-) diff --git a/src/common/components/Recaptcha/Recaptcha.test.tsx b/src/common/components/Recaptcha/Recaptcha.test.tsx index 614988c42..cb7e1994a 100644 --- a/src/common/components/Recaptcha/Recaptcha.test.tsx +++ b/src/common/components/Recaptcha/Recaptcha.test.tsx @@ -45,7 +45,7 @@ describe('Recaptcha component', () => { expect(recaptchaEnabledButton.innerHTML).toEqual('Label') }) - xit('should disable the button until ready', async () => { + it('should disable the button until ready', async () => { global.window.grecaptcha = undefined const { getByRole } = render(buildRecaptcha()) @@ -56,7 +56,7 @@ describe('Recaptcha component', () => { await waitFor(() => expect((recaptchaEnabledButton as HTMLButtonElement).disabled).toEqual(false)) }) - xit('should successfully pass the recaptcha', async () => { + it('should successfully pass the recaptcha', async () => { //@ts-ignore global.fetch = jest.fn(() => { return Promise.resolve({ @@ -77,7 +77,7 @@ describe('Recaptcha component', () => { }) }) - xit('should successfully pass the recaptcha on branded checkout', async () => { + it('should successfully pass the recaptcha on branded checkout', async () => { //@ts-ignore global.fetch = jest.fn(() => { return Promise.resolve({ @@ -98,7 +98,7 @@ describe('Recaptcha component', () => { }) }) - xit('should log a warning due to low score', async () => { + it('should log a warning due to low score', async () => { //@ts-ignore global.fetch = jest.fn(() => { return Promise.resolve({ @@ -119,7 +119,7 @@ describe('Recaptcha component', () => { }) }) - xit('should fail the recaptcha call', async () => { + it('should fail the recaptcha call', async () => { //@ts-ignore global.fetch = jest.fn(() => { return Promise.resolve({ @@ -140,7 +140,7 @@ describe('Recaptcha component', () => { }) }) - xit('should call the fail function when not a valid action', async () => { + it('should call the fail function when not a valid action', async () => { //@ts-ignore global.fetch = jest.fn(() => { return Promise.resolve({ @@ -161,7 +161,7 @@ describe('Recaptcha component', () => { }) }) - xit('should skip the recaptcha call', async () => { + it('should skip the recaptcha call', async () => { //@ts-ignore global.window.grecaptcha = { ready: mockRecaptchaReady } @@ -178,7 +178,7 @@ describe('Recaptcha component', () => { }) }) - xit('should not block the gift if something went wrong with recaptcha', async () => { + it('should not block the gift if something went wrong with recaptcha', async () => { //@ts-ignore global.fetch = jest.fn(() => { return Promise.reject('Failed') @@ -198,7 +198,7 @@ describe('Recaptcha component', () => { }) }) - xit('should not block the gift if something went wrong with recaptcha JSON', async () => { + it('should not block the gift if something went wrong with recaptcha JSON', async () => { //@ts-ignore global.fetch = jest.fn(() => { return Promise.resolve({ @@ -220,7 +220,7 @@ describe('Recaptcha component', () => { }) }) - xit('should not block gifts if something weird happens', async () => { + it('should not block gifts if something weird happens', async () => { //@ts-ignore global.fetch = jest.fn(() => { return Promise.resolve({ diff --git a/src/common/components/Recaptcha/Recaptcha.tsx b/src/common/components/Recaptcha/Recaptcha.tsx index 22760f64a..49f7163e3 100644 --- a/src/common/components/Recaptcha/Recaptcha.tsx +++ b/src/common/components/Recaptcha/Recaptcha.tsx @@ -71,50 +71,49 @@ export const Recaptcha = ({ }, [grecaptcha, buttonId]) const handleReCaptchaVerify = useCallback(async () => { - // if (!ready) { - // $log.info('Execute recaptcha not yet available') - // return - // } + if (!ready) { + $log.info('Execute recaptcha not yet available') + return + } - // grecaptcha.ready(async () => { - // try { - // const token = await grecaptcha.execute(recaptchaKey, { action: action }) - // const serverResponse = await fetch(`${apiUrl}/recaptcha/verify`, { - // method: 'POST', - // body: JSON.stringify({ token: token }), - // headers: { 'Content-Type': 'application/json' } - // }) - // const data = await serverResponse.json() + grecaptcha.ready(async () => { + try { + const token = await grecaptcha.execute(recaptchaKey, { action: action }) + const serverResponse = await fetch(`${apiUrl}/recaptcha/verify`, { + method: 'POST', + body: JSON.stringify({ token: token }), + headers: { 'Content-Type': 'application/json' } + }) + const data = await serverResponse.json() - // if (data?.success === true && isValidAction(data?.action)) { - // if (data.score < 0.5) { - // $log.warn(`Captcha score was below the threshold: ${data.score}`) - // onFailure(componentInstance) - // return - // } - // onSuccess(componentInstance) - // return - // } - // if (data?.success === false && isValidAction(data?.action)) { - // $log.warn('Recaptcha call was unsuccessful, continuing anyway') - // onSuccess(componentInstance) - // return - // } - // if (!data) { - // $log.warn('Data was missing!') - // onSuccess(componentInstance) - // return - // } - // if (!isValidAction(data?.action)) { - // $log.warn(`Invalid action: ${data?.action}`) - // onFailure(componentInstance) - // } - // } catch (error) { - // $log.error(`Failed to verify recaptcha, continuing on: ${error}`) - // onSuccess(componentInstance) - // } - // }) - onSuccess(componentInstance) + if (data?.success === true && isValidAction(data?.action)) { + if (data.score < 0.5) { + $log.warn(`Captcha score was below the threshold: ${data.score}`) + onFailure(componentInstance) + return + } + onSuccess(componentInstance) + return + } + if (data?.success === false && isValidAction(data?.action)) { + $log.warn('Recaptcha call was unsuccessful, continuing anyway') + onSuccess(componentInstance) + return + } + if (!data) { + $log.warn('Data was missing!') + onSuccess(componentInstance) + return + } + if (!isValidAction(data?.action)) { + $log.warn(`Invalid action: ${data?.action}`) + onFailure(componentInstance) + } + } catch (error) { + $log.error(`Failed to verify recaptcha, continuing on: ${error}`) + onSuccess(componentInstance) + } + }) }, [grecaptcha, buttonId, ready]) return ( From f4169581b02ac051cc7e4b035007b0c1b9fcf511 Mon Sep 17 00:00:00 2001 From: Bill Randall <william.randall@cru.org> Date: Mon, 2 Dec 2024 10:35:23 -0500 Subject: [PATCH 3/7] Add a couple of checks that would have prevented the sev1 on Saturday. If the response is not in the format we are expecting, we will succeed but log a warning --- .../components/Recaptcha/Recaptcha.test.tsx | 52 +++++++++++++++++-- src/common/components/Recaptcha/Recaptcha.tsx | 11 ++-- 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/src/common/components/Recaptcha/Recaptcha.test.tsx b/src/common/components/Recaptcha/Recaptcha.test.tsx index cb7e1994a..f77a2e3dc 100644 --- a/src/common/components/Recaptcha/Recaptcha.test.tsx +++ b/src/common/components/Recaptcha/Recaptcha.test.tsx @@ -144,7 +144,7 @@ describe('Recaptcha component', () => { //@ts-ignore global.fetch = jest.fn(() => { return Promise.resolve({ - json: () => Promise.resolve({ success: true, action: 'read' }) + json: () => Promise.resolve({ success: true, action: 'read', score: 0.9 }) }) }) @@ -220,11 +220,11 @@ describe('Recaptcha component', () => { }) }) - it('should not block gifts if something weird happens', async () => { + it('should not block gifts if data is empty', async () => { //@ts-ignore global.fetch = jest.fn(() => { return Promise.resolve({ - json: () => Promise.resolve() + json: () => Promise.resolve({}) }) }) @@ -238,7 +238,51 @@ describe('Recaptcha component', () => { await waitFor(() => { expect(onSuccess).toHaveBeenCalledTimes(1) expect(onFailure).not.toHaveBeenCalled() - expect($log.warn).toHaveBeenCalledWith('Data was missing!') + expect($log.warn).toHaveBeenCalledWith('Recaptcha returned an unusual response:', {}) + }) + }) + + it('should not block gifts if action is undefined', async () => { + //@ts-ignore + global.fetch = jest.fn(() => { + return Promise.resolve({ + json: () => Promise.resolve({ success: true, score: 0.9 }) + }) + }) + + onSuccess.mockImplementation(() => console.log('success after weird')) + + const { getByRole } = render( + buildRecaptcha() + ) + + await userEvent.click(getByRole('button')) + await waitFor(() => { + expect(onSuccess).toHaveBeenCalledTimes(1) + expect(onFailure).not.toHaveBeenCalled() + expect($log.warn).toHaveBeenCalledWith('Recaptcha returned an unusual response:', { success: true, score: 0.9 }) + }) + }) + + it('should not block gifts if score is undefined', async () => { + //@ts-ignore + global.fetch = jest.fn(() => { + return Promise.resolve({ + json: () => Promise.resolve({ success: true, action: 'submit_gift' }) + }) + }) + + onSuccess.mockImplementation(() => console.log('success after weird')) + + const { getByRole } = render( + buildRecaptcha() + ) + + await userEvent.click(getByRole('button')) + await waitFor(() => { + expect(onSuccess).toHaveBeenCalledTimes(1) + expect(onFailure).not.toHaveBeenCalled() + expect($log.warn).toHaveBeenCalledWith('Recaptcha returned an unusual response:', { success: true, action: 'submit_gift' }) }) }) diff --git a/src/common/components/Recaptcha/Recaptcha.tsx b/src/common/components/Recaptcha/Recaptcha.tsx index 49f7163e3..ddf7bf21e 100644 --- a/src/common/components/Recaptcha/Recaptcha.tsx +++ b/src/common/components/Recaptcha/Recaptcha.tsx @@ -86,6 +86,12 @@ export const Recaptcha = ({ }) const data = await serverResponse.json() + if (!data || !data.score || !data.action) { + $log.warn('Recaptcha returned an unusual response:', data) + onSuccess(componentInstance) + return + } + if (data?.success === true && isValidAction(data?.action)) { if (data.score < 0.5) { $log.warn(`Captcha score was below the threshold: ${data.score}`) @@ -100,11 +106,6 @@ export const Recaptcha = ({ onSuccess(componentInstance) return } - if (!data) { - $log.warn('Data was missing!') - onSuccess(componentInstance) - return - } if (!isValidAction(data?.action)) { $log.warn(`Invalid action: ${data?.action}`) onFailure(componentInstance) From 754a38f5d90bf5c953fbdbe1b4737f6ba8e6230e Mon Sep 17 00:00:00 2001 From: Caleb Cox <canac@users.noreply.github.com> Date: Wed, 4 Dec 2024 14:32:44 -0600 Subject: [PATCH 4/7] Fix designationsService tests --- .../services/api/designations.service.js | 3 +- .../services/api/designations.service.spec.js | 92 +++++++++++-------- 2 files changed, 55 insertions(+), 40 deletions(-) diff --git a/src/common/services/api/designations.service.js b/src/common/services/api/designations.service.js index 810a3d1d2..802080590 100644 --- a/src/common/services/api/designations.service.js +++ b/src/common/services/api/designations.service.js @@ -4,6 +4,7 @@ import toFinite from 'lodash/toFinite' import startsWith from 'lodash/startsWith' import { Observable } from 'rxjs/Observable' import 'rxjs/add/observable/from' +import 'rxjs/add/observable/of' import 'rxjs/add/operator/map' import 'rxjs/add/operator/catch' import moment from 'moment' @@ -233,7 +234,7 @@ class DesignationsService { } return suggestedAmounts }) - .catch(() => []) + .catch(() => Observable.of([])) } facebookPixel (code) { diff --git a/src/common/services/api/designations.service.spec.js b/src/common/services/api/designations.service.spec.js index 54145e941..c5b0c616e 100644 --- a/src/common/services/api/designations.service.spec.js +++ b/src/common/services/api/designations.service.spec.js @@ -24,7 +24,7 @@ describe('designation service', () => { }) describe('productSearch', () => { - it('should send a request to API and get results', () => { + it('should send a request to API and get results', done => { self.$httpBackend.expectGET('https://give-stage2.cru.org/search?keyword=steve').respond(200, searchResponse) self.designationsService.productSearch({ keyword: 'steve' @@ -36,11 +36,12 @@ describe('designation service', () => { name: 'John and Jane Doe', type: 'Staff' })]) - }) + done() + }, done) self.$httpBackend.flush() }) - it('should handle undefined fields', () => { + it('should handle undefined fields', done => { self.$httpBackend.expectGET('https://give-stage2.cru.org/search?keyword=steve').respond(200, { hits: [{}] }) self.designationsService.productSearch({ keyword: 'steve' @@ -52,14 +53,15 @@ describe('designation service', () => { name: null, type: null })]) - }) + done() + }, done) self.$httpBackend.flush() }) }) describe('productLookup', () => { const expectedResponse = { - uri: 'items/crugive/a5t4fmspmfpwpqvqli7teksyhu=', + uri: 'carts/items/crugive/a5t4fmspmfpwpqvqli7teksyhu=/form', frequencies: [ { name: 'QUARTERLY', @@ -90,58 +92,63 @@ describe('designation service', () => { frequency: 'NA', displayName: 'Steve Peck', designationType: 'Staff', + orgId: 'STAFF', code: '0354433', designationNumber: '0354433' } - it('should get product details for a designation number', () => { + it('should get product details for a designation number', done => { self.$httpBackend.expectPOST('https://give-stage2.cru.org/cortex/items/crugive/lookups/form?FollowLocation=true&zoom=code,offer:code,definition,definition:options:element:selector:choice,definition:options:element:selector:choice:description,definition:options:element:selector:choice:selectaction,definition:options:element:selector:chosen,definition:options:element:selector:chosen:description', { code: '0354433' }) .respond(200, lookupResponse) self.designationsService.productLookup('0354433') .subscribe((data) => { expect(data).toEqual(expectedResponse) - }) + done() + }, done) self.$httpBackend.flush() }) - it('should get product details for a uri', () => { + it('should get product details for a uri', done => { self.$httpBackend.expectPOST('https://give-stage2.cru.org/cortex/itemselections/crugive/a5t4fmspmhbkez6cwbnd6mrkla74hdgcupbl4xjb=/options/izzgk4lvmvxgg6i=/values/jzaq=/selector?FollowLocation=true&zoom=code,offer:code,definition,definition:options:element:selector:choice,definition:options:element:selector:choice:description,definition:options:element:selector:choice:selectaction,definition:options:element:selector:chosen,definition:options:element:selector:chosen:description') .respond(200, lookupResponse) self.designationsService.productLookup('/itemselections/crugive/a5t4fmspmhbkez6cwbnd6mrkla74hdgcupbl4xjb=/options/izzgk4lvmvxgg6i=/values/jzaq=/selector', true) .subscribe(data => { expect(data).toEqual(expectedResponse) - }) + done() + }, done) self.$httpBackend.flush() }) - it('should handle an empty response', () => { + it('should handle an empty response', done => { self.$httpBackend.expectPOST('https://give-stage2.cru.org/cortex/itemselections/crugive/a5t4fmspmhbkez6cwbnd6mrkla74hdgcupbl4xjb=/options/izzgk4lvmvxgg6i=/values/jzaq=/selector?FollowLocation=true&zoom=code,offer:code,definition,definition:options:element:selector:choice,definition:options:element:selector:choice:description,definition:options:element:selector:choice:selectaction,definition:options:element:selector:chosen,definition:options:element:selector:chosen:description') .respond(200, '') self.designationsService.productLookup('/itemselections/crugive/a5t4fmspmhbkez6cwbnd6mrkla74hdgcupbl4xjb=/options/izzgk4lvmvxgg6i=/values/jzaq=/selector', true) .subscribe(() => { - fail('success should not have been called') + done('success should not have been called') }, error => { - expect(error).toEqual('Product lookup response contains no code data') + expect(error.message).toEqual('Product lookup response contains no code data') + done() }) self.$httpBackend.flush() }) }) describe('bulkLookup', () => { - it('should take an array of designation numbers and return corresponding links for items', () => { + it('should take an array of designation numbers and return corresponding links for items', done => { self.$httpBackend.expectPOST('https://give-stage2.cru.org/cortex/items/crugive/lookups/batches/form?FollowLocation=true', { codes: ['0123456', '1234567'] }) .respond(200, bulkLookupResponse) self.designationsService.bulkLookup(['0123456', '1234567']) .subscribe(data => { expect(data).toEqual(bulkLookupResponse) - }) + done() + }, done) self.$httpBackend.flush() }) }) describe('suggestedAmounts', () => { - it('should load suggested amounts', () => { + it('should load suggested amounts', done => { const itemConfig = { amount: 50, 'campaign-page': 9876 } self.$httpBackend.expectGET('https://give-stage2.cru.org/content/give/us/en/campaigns/0/1/2/3/4/0123456/9876.infinity.json') .respond(200, campaignResponse) @@ -154,11 +161,12 @@ describe('designation service', () => { expect(itemConfig['default-campaign-code']).toEqual('867EM1') expect(itemConfig['jcr-title']).toEqual('PowerPacksTM for Inner City Children') - }) + done() + }, done) self.$httpBackend.flush() }) - it('should handle an invalid campaign page', () => { + it('should handle an invalid campaign page', done => { const itemConfig = { amount: 50, 'campaign-page': 9876 } self.$httpBackend.expectGET('https://give-stage2.cru.org/content/give/us/en/campaigns/0/1/2/3/4/0123456/9876.infinity.json') .respond(400, {}) @@ -167,11 +175,12 @@ describe('designation service', () => { expect(suggestedAmounts).toEqual([]) expect(itemConfig['default-campaign-code']).toBeUndefined() expect(itemConfig['jcr-title']).toBeUndefined() - }) + done() + }, done) self.$httpBackend.flush() }) - it('should handle no campaign page', () => { + it('should handle no campaign page', done => { const itemConfig = { amount: 50 } self.$httpBackend.expectGET('https://give-stage2.cru.org/content/give/us/en/designations/0/1/2/3/4/0123456.infinity.json') .respond(200, designationResponse) @@ -180,25 +189,27 @@ describe('designation service', () => { expect(suggestedAmounts).toEqual([]) expect(itemConfig['default-campaign-code']).toEqual('867EM1') expect(itemConfig['jcr-title']).toEqual('PowerPacksTM for Inner City Children') - }) + done() + }, done) self.$httpBackend.flush() }) }) describe('facebookPixel', () => { - it('should load facebook pixel id from JCR', () => { + it('should load facebook pixel id from JCR', done => { self.$httpBackend.expectGET('https://give-stage2.cru.org/content/give/us/en/designations/0/1/2/3/4/0123456.infinity.json') .respond(200, designationResponse) self.designationsService.facebookPixel('0123456') .subscribe(pixelId => { expect(pixelId).toEqual('123456') - }) + done() + }, done) self.$httpBackend.flush() }) }) describe('givingLinks', () => { - it('should load givingLinks from JCR', () => { + it('should load givingLinks from JCR', done => { self.$httpBackend.expectGET('https://give-stage2.cru.org/content/give/us/en/designations/0/1/2/3/4/0123456.infinity.json') .respond(200, designationResponse) self.designationsService.givingLinks('0123456') @@ -206,7 +217,8 @@ describe('designation service', () => { expect(givingLinks).toEqual([ { name: 'Name', url: 'https://example.com', order: 0 } ]) - }) + done() + }, done) self.$httpBackend.flush() }) }) @@ -244,31 +256,33 @@ describe('designation service', () => { }) describe('ministriesList', () => { - it('should return a list of ministries', () => { + it('should return a list of ministries', done => { jest.spyOn(self.$location, 'protocol').mockImplementationOnce(() => 'https') jest.spyOn(self.$location, 'host').mockImplementationOnce(() => 'give-stage-cloud.cru.org') const pagePath = 'page.html' const ministriesResponse = { - ministries: [{ - name: 'Some Ministry', - designationNumber: '0123456', - path: '/some-vanity', - extra: 'something-else' - }] + ministries: [ + JSON.stringify({ + name: 'Some Ministry', + designationNumber: '0123456', + path: '/some-vanity', + extra: 'something-else' + }) + ] } self.$httpBackend.expectGET(`https://give-stage-cloud.cru.org/${pagePath}/jcr:content/content-parsys/designation_search_r.json`) .respond(200, ministriesResponse) - const expectedResult = { - ministries: [{ - name: 'Some Ministry', - designationNumber: '0123456', - path: '/some-vanity' - }] - } + const expectedResult = [{ + name: 'Some Ministry', + designationNumber: '0123456', + facet: null, + path: '/some-vanity' + }] self.designationsService.ministriesList(pagePath).subscribe(actualResult => { expect(actualResult).toEqual(expectedResult) - }) + done() + }, done) self.$httpBackend.flush() }) }) From 521dc078067985328ed93fcfe7e4a9ba1ba036be Mon Sep 17 00:00:00 2001 From: Caleb Cox <canac@users.noreply.github.com> Date: Wed, 4 Dec 2024 13:44:53 -0600 Subject: [PATCH 5/7] Ignore giving links without names or urls --- src/common/services/api/designations.service.js | 4 ++++ .../services/api/designations.service.spec.js | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/common/services/api/designations.service.js b/src/common/services/api/designations.service.js index 802080590..52b7e4710 100644 --- a/src/common/services/api/designations.service.js +++ b/src/common/services/api/designations.service.js @@ -254,6 +254,10 @@ class DesignationsService { // Map giving links if (data.data['jcr:content'].givingLinks) { angular.forEach(data.data['jcr:content'].givingLinks, (v, k) => { + if (!v || !v.name || !v.url) { + return + } + if (toFinite(k) > 0 || startsWith(k, 'item')) { givingLinks.push({ name: v.name, diff --git a/src/common/services/api/designations.service.spec.js b/src/common/services/api/designations.service.spec.js index c5b0c616e..0b4abfccf 100644 --- a/src/common/services/api/designations.service.spec.js +++ b/src/common/services/api/designations.service.spec.js @@ -221,6 +221,21 @@ describe('designation service', () => { }, done) self.$httpBackend.flush() }) + + it('should ignore givingLinks without names or urls', done => { + const response = angular.copy(designationResponse) + response['jcr:content'].givingLinks.item1 = { 'jcr:primaryType': 'nt:unstructured' } + self.$httpBackend.expectGET('https://give-stage2.cru.org/content/give/us/en/designations/0/1/2/3/4/0123456.infinity.json') + .respond(200, response) + self.designationsService.givingLinks('0123456') + .subscribe(givingLinks => { + expect(givingLinks).toEqual([ + { name: 'Name', url: 'https://example.com', order: 0 } + ]) + done() + }, done) + self.$httpBackend.flush() + }) }) describe('generatePath', () => { From fe486ee05b1921022ca97aefcb15224b92c4fa78 Mon Sep 17 00:00:00 2001 From: Caleb Cox <canac@users.noreply.github.com> Date: Wed, 4 Dec 2024 14:05:43 -0600 Subject: [PATCH 6/7] Add valid giving link after invalid giving link --- src/common/services/api/designations.service.spec.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/common/services/api/designations.service.spec.js b/src/common/services/api/designations.service.spec.js index 0b4abfccf..b956af4fd 100644 --- a/src/common/services/api/designations.service.spec.js +++ b/src/common/services/api/designations.service.spec.js @@ -225,12 +225,14 @@ describe('designation service', () => { it('should ignore givingLinks without names or urls', done => { const response = angular.copy(designationResponse) response['jcr:content'].givingLinks.item1 = { 'jcr:primaryType': 'nt:unstructured' } + response['jcr:content'].givingLinks.item2 = { 'jcr:primaryType': 'nt:unstructured', url: 'https://example2.com', name: 'Name 2' } self.$httpBackend.expectGET('https://give-stage2.cru.org/content/give/us/en/designations/0/1/2/3/4/0123456.infinity.json') .respond(200, response) self.designationsService.givingLinks('0123456') .subscribe(givingLinks => { expect(givingLinks).toEqual([ - { name: 'Name', url: 'https://example.com', order: 0 } + { name: 'Name', url: 'https://example.com', order: 0 }, + { name: 'Name 2', url: 'https://example2.com', order: 2 } ]) done() }, done) From 06a8948a4c8448cfb9910da906a351a7bf402354 Mon Sep 17 00:00:00 2001 From: Caleb Cox <canac@users.noreply.github.com> Date: Thu, 5 Dec 2024 08:52:08 -0600 Subject: [PATCH 7/7] Document workaround for empty giving links --- src/common/services/api/designations.service.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/common/services/api/designations.service.js b/src/common/services/api/designations.service.js index 52b7e4710..f10dc15a2 100644 --- a/src/common/services/api/designations.service.js +++ b/src/common/services/api/designations.service.js @@ -255,6 +255,9 @@ class DesignationsService { if (data.data['jcr:content'].givingLinks) { angular.forEach(data.data['jcr:content'].givingLinks, (v, k) => { if (!v || !v.name || !v.url) { + // Some accounts contain multiple, empty giving links. Until we figure how how they + // are being created, we are ignoring them on the frontend. + // https://jira.cru.org/browse/EP-2554 return }