From b9a9e632ec7129995deff6769908a744ad7423a7 Mon Sep 17 00:00:00 2001 From: wjames111 Date: Thu, 12 Dec 2024 18:37:04 -0500 Subject: [PATCH] Revert files. --- .../components/Recaptcha/Recaptcha.test.tsx | 68 ++++++++++- src/common/components/Recaptcha/Recaptcha.tsx | 20 ++-- .../services/api/designations.service.js | 10 +- .../services/api/designations.service.spec.js | 109 +++++++++++------- 4 files changed, 155 insertions(+), 52 deletions(-) diff --git a/src/common/components/Recaptcha/Recaptcha.test.tsx b/src/common/components/Recaptcha/Recaptcha.test.tsx index cb7e1994a..5a1a124d6 100644 --- a/src/common/components/Recaptcha/Recaptcha.test.tsx +++ b/src/common/components/Recaptcha/Recaptcha.test.tsx @@ -2,6 +2,15 @@ import { render, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { ButtonType, Recaptcha } from './Recaptcha' import React from 'react' +import { datadogRum } from '@datadog/browser-rum' + +jest.mock('@datadog/browser-rum', () => { + return { + datadogRum: { + addError: jest.fn() + } + } +}) let mockExecuteRecaptcha = jest.fn() const mockRecaptchaReady = jest.fn() @@ -114,7 +123,9 @@ describe('Recaptcha component', () => { await userEvent.click(getByRole('button')) await waitFor(() => { - expect($log.warn).toHaveBeenCalledWith('Captcha score was below the threshold: 0.2') + const errorMessage = 'Captcha score was below the threshold: 0.2' + expect($log.warn).toHaveBeenCalledWith(errorMessage) + expect(datadogRum.addError).toHaveBeenCalledWith(new Error(`Error submitting purchase: ${errorMessage}`), { context: 'Recaptcha', errorCode: 'lowScore' }) expect(onFailure).toHaveBeenCalledTimes(1) }) }) @@ -144,7 +155,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 }) }) }) @@ -156,8 +167,11 @@ describe('Recaptcha component', () => { await userEvent.click(getByRole('button')) await waitFor(() => { + const errorMessage = 'Invalid action: read' expect(onSuccess).not.toHaveBeenCalled() expect(onFailure).toHaveBeenCalled() + expect($log.warn).toHaveBeenCalledWith(errorMessage) + expect(datadogRum.addError).toHaveBeenCalledWith(new Error(`Error submitting purchase: ${errorMessage}`), { context: 'Recaptcha', errorCode: 'invalidAction' }) }) }) @@ -220,11 +234,55 @@ 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({}) + }) + }) + + 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:', {}) + }) + }) + + 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() + json: () => Promise.resolve({ success: true, action: 'submit_gift' }) }) }) @@ -238,7 +296,7 @@ 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:', { success: true, action: 'submit_gift' }) }) }) diff --git a/src/common/components/Recaptcha/Recaptcha.tsx b/src/common/components/Recaptcha/Recaptcha.tsx index 49f7163e3..adbd4c847 100644 --- a/src/common/components/Recaptcha/Recaptcha.tsx +++ b/src/common/components/Recaptcha/Recaptcha.tsx @@ -1,6 +1,7 @@ import angular from 'angular' import { react2angular } from 'react2angular' import React, { useCallback, useEffect, useState } from 'react' +import { datadogRum } from '@datadog/browser-rum' const componentName = 'recaptcha' @@ -86,9 +87,17 @@ 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}`) + const errorMessage = `Captcha score was below the threshold: ${data.score}` + $log.warn(errorMessage) + datadogRum.addError(new Error(`Error submitting purchase: ${errorMessage}`), { context: 'Recaptcha', errorCode: 'lowScore' }) onFailure(componentInstance) return } @@ -100,13 +109,10 @@ 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}`) + const errorMessage = `Invalid action: ${data?.action}` + $log.warn(errorMessage) + datadogRum.addError(new Error(`Error submitting purchase: ${errorMessage}`), { context: 'Recaptcha', errorCode: 'invalidAction' }) onFailure(componentInstance) } } catch (error) { diff --git a/src/common/services/api/designations.service.js b/src/common/services/api/designations.service.js index 810a3d1d2..f10dc15a2 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) { @@ -253,6 +254,13 @@ 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) { + // 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 + } + 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 54145e941..b956af4fd 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,25 @@ describe('designation service', () => { expect(givingLinks).toEqual([ { name: 'Name', url: 'https://example.com', order: 0 } ]) - }) + done() + }, 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' } + 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 2', url: 'https://example2.com', order: 2 } + ]) + done() + }, done) self.$httpBackend.flush() }) }) @@ -244,31 +273,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() }) })