| 
									
										
										
										
											2020-03-13 12:25:57 +03:00
										 |  |  | /* eslint-disable camelcase */ | 
					
						
							|  |  |  | /* global describe it expect */ | 
					
						
							|  |  |  | /* global jest*/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import axios from 'axios'; | 
					
						
							|  |  |  | import keys from '../../../config/secrets'; | 
					
						
							|  |  |  | import { | 
					
						
							|  |  |  |   getAsyncPaypalToken, | 
					
						
							|  |  |  |   verifyWebHook, | 
					
						
							|  |  |  |   updateUser, | 
					
						
							|  |  |  |   capitalizeKeys, | 
					
						
							|  |  |  |   createDonationObj | 
					
						
							|  |  |  | } from './donation'; | 
					
						
							|  |  |  | import { mockActivationHook, mockCancellationHook } from './__mocks__/donation'; | 
					
						
							|  |  |  | import { | 
					
						
							|  |  |  |   mockApp, | 
					
						
							|  |  |  |   createDonationMockFn, | 
					
						
							|  |  |  |   createUserMockFn, | 
					
						
							|  |  |  |   updateDonationAttr, | 
					
						
							|  |  |  |   updateUserAttr | 
					
						
							|  |  |  | } from '../boot_tests/fixtures'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | jest.mock('axios'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-16 14:32:35 +05:30
										 |  |  | const verificationUrl = `https://api.sandbox.paypal.com/v1/notifications/verify-webhook-signature`; | 
					
						
							|  |  |  | const tokenUrl = `https://api.sandbox.paypal.com/v1/oauth2/token`; | 
					
						
							| 
									
										
										
										
											2020-03-13 12:25:57 +03:00
										 |  |  | const { | 
					
						
							|  |  |  |   body: activationHookBody, | 
					
						
							|  |  |  |   headers: activationHookHeaders | 
					
						
							|  |  |  | } = mockActivationHook; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | describe('donation', () => { | 
					
						
							|  |  |  |   describe('getAsyncPaypalToken', () => { | 
					
						
							|  |  |  |     it('call paypal api for token ', async () => { | 
					
						
							|  |  |  |       const res = { | 
					
						
							|  |  |  |         data: { | 
					
						
							|  |  |  |           access_token: 'token' | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       axios.post.mockImplementationOnce(() => Promise.resolve(res)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       await expect(getAsyncPaypalToken()).resolves.toEqual( | 
					
						
							|  |  |  |         res.data.access_token | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       expect(axios.post).toHaveBeenCalledTimes(1); | 
					
						
							|  |  |  |       expect(axios.post).toHaveBeenCalledWith(tokenUrl, null, { | 
					
						
							|  |  |  |         headers: { | 
					
						
							|  |  |  |           'Content-Type': 'application/x-www-form-urlencoded' | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         auth: { | 
					
						
							|  |  |  |           username: keys.paypal.client, | 
					
						
							|  |  |  |           password: keys.paypal.secret | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         params: { | 
					
						
							|  |  |  |           grant_type: 'client_credentials' | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('verifyWebHook', () => { | 
					
						
							|  |  |  |     // normalize headers
 | 
					
						
							|  |  |  |     capitalizeKeys(activationHookHeaders); | 
					
						
							|  |  |  |     const mockWebhookId = 'qwdfq;3w12341dfa4'; | 
					
						
							|  |  |  |     const mockAccessToken = '241231223$!@$#1243'; | 
					
						
							|  |  |  |     const mockPayLoad = { | 
					
						
							|  |  |  |       auth_algo: activationHookHeaders['PAYPAL-AUTH-ALGO'], | 
					
						
							|  |  |  |       cert_url: activationHookHeaders['PAYPAL-CERT-URL'], | 
					
						
							|  |  |  |       transmission_id: activationHookHeaders['PAYPAL-TRANSMISSION-ID'], | 
					
						
							|  |  |  |       transmission_sig: activationHookHeaders['PAYPAL-TRANSMISSION-SIG'], | 
					
						
							|  |  |  |       transmission_time: activationHookHeaders['PAYPAL-TRANSMISSION-TIME'], | 
					
						
							|  |  |  |       webhook_id: mockWebhookId, | 
					
						
							|  |  |  |       webhook_event: activationHookBody | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     const failedVerificationErr = { | 
					
						
							|  |  |  |       message: `Failed token verification.`, | 
					
						
							|  |  |  |       type: 'FailedPaypalTokenVerificationError' | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     const axiosOptions = { | 
					
						
							|  |  |  |       headers: { | 
					
						
							|  |  |  |         'Content-Type': 'application/json', | 
					
						
							|  |  |  |         Authorization: `Bearer ${mockAccessToken}` | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     const successVerificationResponce = { | 
					
						
							|  |  |  |       data: { | 
					
						
							|  |  |  |         verification_status: 'SUCCESS' | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     const failedVerificationResponce = { | 
					
						
							|  |  |  |       data: { | 
					
						
							|  |  |  |         verification_status: 'FAILED' | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('calls paypal for Webhook verification', async () => { | 
					
						
							|  |  |  |       axios.post.mockImplementationOnce(() => | 
					
						
							|  |  |  |         Promise.resolve(successVerificationResponce) | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       await expect( | 
					
						
							|  |  |  |         verifyWebHook( | 
					
						
							|  |  |  |           activationHookHeaders, | 
					
						
							|  |  |  |           activationHookBody, | 
					
						
							|  |  |  |           mockAccessToken, | 
					
						
							|  |  |  |           mockWebhookId | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |       ).resolves.toEqual(activationHookBody); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       expect(axios.post).toHaveBeenCalledWith( | 
					
						
							|  |  |  |         verificationUrl, | 
					
						
							|  |  |  |         mockPayLoad, | 
					
						
							|  |  |  |         axiosOptions | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     it('throws error if verification not successful', async () => { | 
					
						
							|  |  |  |       axios.post.mockImplementationOnce(() => | 
					
						
							|  |  |  |         Promise.resolve(failedVerificationResponce) | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       await expect( | 
					
						
							|  |  |  |         verifyWebHook( | 
					
						
							|  |  |  |           activationHookHeaders, | 
					
						
							|  |  |  |           activationHookBody, | 
					
						
							|  |  |  |           mockAccessToken, | 
					
						
							|  |  |  |           mockWebhookId | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |       ).rejects.toEqual(failedVerificationErr); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('updateUser', () => { | 
					
						
							|  |  |  |     it('created a donation when a machting user found', () => { | 
					
						
							|  |  |  |       updateUser(activationHookBody, mockApp); | 
					
						
							|  |  |  |       expect(createDonationMockFn).toHaveBeenCalledTimes(1); | 
					
						
							|  |  |  |       expect(createDonationMockFn).toHaveBeenCalledWith( | 
					
						
							|  |  |  |         createDonationObj(activationHookBody) | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     it('create a user and donation when no machting user found', () => { | 
					
						
							|  |  |  |       let newActivationHookBody = activationHookBody; | 
					
						
							|  |  |  |       newActivationHookBody.resource.subscriber.email_address = | 
					
						
							|  |  |  |         'new@freecodecamp.org'; | 
					
						
							|  |  |  |       updateUser(newActivationHookBody, mockApp); | 
					
						
							|  |  |  |       expect(createUserMockFn).toHaveBeenCalledTimes(1); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('modify user and donation records on cancellation', () => { | 
					
						
							|  |  |  |       const { body: cancellationHookBody } = mockCancellationHook; | 
					
						
							|  |  |  |       const { | 
					
						
							|  |  |  |         resource: { status_update_time = new Date(Date.now()).toISOString() } | 
					
						
							|  |  |  |       } = cancellationHookBody; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       updateUser(cancellationHookBody, mockApp); | 
					
						
							|  |  |  |       expect(updateDonationAttr).toHaveBeenCalledWith({ | 
					
						
							|  |  |  |         endDate: new Date(status_update_time).toISOString() | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       expect(updateUserAttr).toHaveBeenCalledWith({ | 
					
						
							|  |  |  |         isDonating: false | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | }); |