diff --git a/src/subscription/checkout/SubscriptionCheckout.test.jsx b/src/subscription/checkout/SubscriptionCheckout.test.jsx index ee3e2a7e9..51ad8419b 100644 --- a/src/subscription/checkout/SubscriptionCheckout.test.jsx +++ b/src/subscription/checkout/SubscriptionCheckout.test.jsx @@ -1,155 +1,97 @@ -/* eslint-disable react/jsx-no-constructed-context-values */ -/* eslint-disable global-require */ import React from 'react'; -import { mount } from 'enzyme'; -import { act } from 'react-dom/test-utils'; -import { createStore, applyMiddleware } from 'redux'; -import thunkMiddleware from 'redux-thunk'; -import { Provider } from 'react-redux'; import { Factory } from 'rosie'; -import { createSerializer } from 'enzyme-to-json'; -import { IntlProvider, configure as configureI18n } from '@edx/frontend-platform/i18n'; -import { getConfig } from '@edx/frontend-platform'; -import * as analytics from '@edx/frontend-platform/analytics'; -import Cookies from 'universal-cookie'; -import { Elements } from '@stripe/react-stripe-js'; -// import { loadStripe } from '@stripe/stripe-js'; import '../__factories__/subscription.factory'; -import '../../payment/__factories__/userAccount.factory'; -import { AppContext } from '@edx/frontend-platform/react'; +import { loadStripe } from '@stripe/stripe-js'; +import { + render, act, screen, store, +} from '../test-utils'; +import * as mocks from '../../payment/checkout/stripeMocks'; + import { SubscriptionCheckout } from './SubscriptionCheckout'; -import createRootReducer from '../../data/reducers'; import { fetchSubscriptionDetails, subscriptionDetailsReceived } from '../data/details/actions'; import { camelCaseObject } from '../../payment/data/utils'; -expect.addSnapshotSerializer(createSerializer({ mode: 'deep', noKey: true })); - -const config = getConfig(); -const locale = 'en'; - -configureI18n({ - config: { - ENVIRONMENT: process.env.ENVIRONMENT, - LANGUAGE_PREFERENCE_COOKIE_NAME: process.env.LANGUAGE_PREFERENCE_COOKIE_NAME, - }, - loggingService: { - logError: jest.fn(), - logInfo: jest.fn(), - }, - messages: { - uk: {}, - th: {}, - ru: {}, - 'pt-br': {}, - pl: {}, - 'ko-kr': {}, - id: {}, - he: {}, - ca: {}, - 'zh-cn': {}, - fr: {}, - 'es-419': {}, - ar: {}, - fa: {}, - 'fa-ir': {}, - }, -}); - -jest.mock('universal-cookie', () => { - class MockCookies { - static result = { - [process.env.LANGUAGE_PREFERENCE_COOKIE_NAME]: 'en', - [process.env.CURRENCY_COOKIE_NAME]: { - code: 'MXN', - rate: 19.092733, - }, - }; - - get(cookieName) { - return MockCookies.result[cookieName]; - } - } - return MockCookies; -}); - -jest.mock('@edx/frontend-platform/analytics', () => ({ - sendTrackEvent: jest.fn(), - sendPageEvent: jest.fn(), -})); - -// https://github.com/wwayne/react-tooltip/issues/595#issuecomment-638438372 -jest.mock('react-tooltip/node_modules/uuid', () => ({ - v4: () => '00000000-0000-0000-0000-000000000000', -})); - jest.mock('./StripeOptions', () => ({ - getStripeOptions: jest.fn().mockReturnValue({}), + getStripeOptions: jest.fn().mockReturnValue({ + mode: 'subscription', + amount: 55, + currency: 'usd', + paymentMethodCreation: 'manual', + }), })); -// jest.mock('@stripe/stripe-js', () => ({ -// loadStripe: jest.fn(), -// })); +jest.mock('@stripe/stripe-js', () => ({ + loadStripe: jest.fn(() => ({ + // mock implementation of the stripe object + })), +})); +/** + * SubscriptionCheckout Test + * https://github.com/stripe-archive/react-stripe-elements/issues/427 + * https://github.com/stripe/react-stripe-js/issues/59 + */ describe('', () => { - let store; - let tree; - + let subscriptionDetails; + let mockStripe; + let mockElements; + let mockElement; + let mockStripePromise; beforeEach(() => { - const authenticatedUser = Factory.build('userAccount'); - store = createStore(createRootReducer(), {}, applyMiddleware(thunkMiddleware)); - // eslint-disable-next-line no-import-assign - analytics.sendTrackingLogEvent = jest.fn(); - Cookies.result[process.env.CURRENCY_COOKIE_NAME] = undefined; - - // Mock the response of loadStripe method - // const mockedStripe = { - // elements: jest.fn(), - // }; - // loadStripe.mockResolvedValue(mockedStripe); - // TODO: make sure to test the form submit events - - const component = ( - - - - - - - - ); - - tree = mount(component); - }); - - afterEach(() => { - tree.unmount(); + // Arrange + mockStripe = mocks.mockStripe(); + mockElement = mocks.mockElement(); + mockElements = mocks.mockElements(); + mockStripe.elements.mockReturnValue(mockElements); + mockElements.create.mockReturnValue(mockElement); + mockStripePromise = jest.fn(() => Promise.resolve({ + ...mockStripe, + })); + subscriptionDetails = camelCaseObject(Factory.build('subscription', {}, { numProducts: 2 })); + loadStripe.mockResolvedValue(mockStripePromise); }); it('should render the loading skeleton for SubscriptionCheckout', () => { - tree.update(); + render(); + expect(screen.queryByText('Last Name (required)')).not.toBeInTheDocument(); // it doesn't exist + expect( + screen.queryByText('You’ll be charged $55.00 USD on April 21, 2025 then every 31 days until you cancel your subscription.'), + ).toBeNull(); + }); - expect(tree.find('CheckoutSkeleton')).toHaveLength(1); + it('should render the with the subscription details', () => { + const stripePromise = mockStripePromise(); - expect(tree).toMatchSnapshot(); - }); + loadStripe.mockResolvedValue(stripePromise); - it('should render the subscription checkout details', () => { + const { container } = render(); act(() => { store.dispatch( subscriptionDetailsReceived( - camelCaseObject(Factory.build('subscription', {}, { numProducts: 1 })), + subscriptionDetails, ), ); store.dispatch(fetchSubscriptionDetails.fulfill()); }); + expect(loadStripe).toHaveBeenCalledWith( + process.env.STRIPE_PUBLISHABLE_KEY, + { + betas: [process.env.STRIPE_DEFERRED_INTENT_BETA_FLAG], + apiVersion: process.env.STRIPE_API_VERSION, + locale: 'en', + }, + ); + expect(container).toMatchSnapshot(); + // screen.debug(container.querySelector('#payment-element')); - tree.update(); - expect(tree).toMatchSnapshot(); - - expect(tree.find('SubscriptionCheckout')).toHaveLength(1); + expect(container.querySelector('#payment-element')).toBeDefined(); - expect(tree.find(Elements)).toHaveLength(1); - expect(tree.find('StripePaymentForm')).toHaveLength(1); + // verify that Checkout Form fields are present in the DOM + expect(screen.queryByText('Last Name (required)')).toBeDefined(); + // verify that MonthlySubscriptionNotification is present in the DOM + expect( + screen.queryByText('You’ll be charged $55.00 USD on April 21, 2025 then every 31 days until you cancel your subscription.'), + ).toBeDefined(); }); }); diff --git a/src/subscription/checkout/__snapshots__/SubscriptionCheckout.test.jsx.snap b/src/subscription/checkout/__snapshots__/SubscriptionCheckout.test.jsx.snap index c83d2ec5b..48f3518d4 100644 --- a/src/subscription/checkout/__snapshots__/SubscriptionCheckout.test.jsx.snap +++ b/src/subscription/checkout/__snapshots__/SubscriptionCheckout.test.jsx.snap @@ -1,1778 +1,1472 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` should render the loading skeleton for SubscriptionCheckout 1`] = ` - - Array [ - - - - - - , - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - , - - - - - - , - should render the with the subscription details 1`] = ` + + + - - - - - - - - + Card Holder Information + - - - - - - + First Name (required) + + - - - - - - + Last Name (required) + + - - , - - - - - - - - - - , - - - - - - , - ] - -`; - -exports[` should render the subscription checkout details 1`] = ` - - - - - Card Holder Information - - - - - First Name (required) - - - - - - Last Name (required) - - - - - - - - Address (required) - - - - - Suite/Apartment Number - - - - - - - + Address (required) + + + + - City (required) - - + + Suite/Apartment Number + + + - - Country (required) - - + City (required) + + + + + - - Choose country - - - Afghanistan - - - Albania - - - Algeria - - - American Samoa - - - Andorra - - - Angola - - - Anguilla - - - Antarctica - - - Antigua and Barbuda - - - Argentina - - - Armenia - - - Aruba - - - Australia - - - Austria - - - Azerbaijan - - - Bahamas - - - Bahrain - - - Bangladesh - - - Barbados - - - Belarus - - - Belgium - - - Belize - - - Benin - - - Bermuda - - - Bhutan - - - Bolivia - - - Bosnia and Herzegovina - - - Botswana - - - Bouvet Island - - - Brazil - - - British Indian Ocean Territory - - - Brunei Darussalam - - - Bulgaria - - - Burkina Faso - - - Burundi - - - Cambodia - - - Cameroon - - - Canada - - - Cape Verde - - - Cayman Islands - - - Central African Republic - - - Chad - - - Chile - - - China - - - Christmas Island - - - Cocos (Keeling) Islands - - - Colombia - - - Comoros - - - Congo - - - Congo, the Democratic Republic of the - - - Cook Islands - - - Costa Rica - - - Cote D'Ivoire - - - Croatia - - - Cuba - - - Cyprus - - - Czech Republic - - - Denmark - - - Djibouti - - - Dominica - - - Dominican Republic - - - Ecuador - - - Egypt - - - El Salvador - - - Equatorial Guinea - - - Eritrea - - - Estonia - - - Ethiopia - - - Falkland Islands (Malvinas) - - - Faroe Islands - - - Fiji - - - Finland - - - France - - - French Guiana - - - French Polynesia - - - French Southern Territories - - - Gabon - - - Gambia - - - Georgia - - - Germany - - - Ghana - - - Gibraltar - - - Greece - - - Greenland - - - Grenada - - - Guadeloupe - - - Guam - - - Guatemala - - - Guinea - - - Guinea-Bissau - - - Guyana - - - Haiti - - - Heard Island and Mcdonald Islands - - - Holy See (Vatican City State) - - - Honduras - - - Hong Kong - - - Hungary - - - Iceland - - - India - - - Indonesia - - - Iran, Islamic Republic of - - - Iraq - - - Ireland - - - Israel - - - Italy - - - Jamaica - - - Japan - - - Jordan - - - Kazakhstan - - - Kenya - - - Kiribati - - - North Korea - - - South Korea - - - Kuwait - - - Kyrgyzstan - - - Lao People's Democratic Republic - - - Latvia - - - Lebanon - - - Lesotho - - - Liberia - - - Libya - - - Liechtenstein - - - Lithuania - - - Luxembourg - - - Macao - - - Madagascar - - - Malawi - - - Malaysia - - - Maldives - - - Mali - - - Malta - - - Marshall Islands - - - Martinique - - - Mauritania - - - Mauritius - - - Mayotte - - - Mexico - - - Micronesia, Federated States of - - - Moldova, Republic of - - - Monaco - - - Mongolia - - - Montserrat - - - Morocco - - - Mozambique - - - Myanmar - - - Namibia - - - Nauru - - - Nepal - - - Netherlands - - - New Caledonia - - - New Zealand - - - Nicaragua - - - Niger - - - Nigeria - - - Niue - - - Norfolk Island - - - North Macedonia, Republic of - - - Northern Mariana Islands - - - Norway - - - Oman - - - Pakistan - - - Palau - - - Palestinian Territory, Occupied - - - Panama - - - Papua New Guinea - - - Paraguay - - - Peru - - - Philippines - - - Pitcairn - - - Poland - - - Portugal - - - Puerto Rico - - - Qatar - - - Reunion - - - Romania - - - Russian Federation - - - Rwanda - - - Saint Helena - - - Saint Kitts and Nevis - - - Saint Lucia - - - Saint Pierre and Miquelon - - - Saint Vincent and the Grenadines - - - Samoa - - - San Marino - - - Sao Tome and Principe - - - Saudi Arabia - - - Senegal - - - Seychelles - - - Sierra Leone - - - Singapore - - - Slovakia - - - Slovenia - - - Solomon Islands - - - Somalia - - - South Africa - - - South Georgia and the South Sandwich Islands - - - Spain - - - Sri Lanka - - - Sudan - - - Suriname - - - Svalbard and Jan Mayen - - - Swaziland - - - Sweden - - - Switzerland - - - Syrian Arab Republic - - - Taiwan - - - Tajikistan - - - Tanzania, United Republic of - - - Thailand - - - Timor-Leste - - - Togo - - - Tokelau - - - Tonga - - - Trinidad and Tobago - - - Tunisia - - - Turkey - - - Turkmenistan - - - Turks and Caicos Islands - - - Tuvalu - - - Uganda - - - Ukraine - - - United Arab Emirates - - - United Kingdom - - - United States of America - - - United States Minor Outlying Islands - - - Uruguay - - - Uzbekistan - - - Vanuatu - - - Venezuela - - - Viet Nam - - - Virgin Islands, British - - - Virgin Islands, U.S. - - - Wallis and Futuna - - - Western Sahara - - - Yemen - - - Zambia - - - Zimbabwe - - - Åland Islands - - - Bonaire, Sint Eustatius and Saba - - - Curaçao - - - Guernsey - - - Isle of Man - - - Jersey - - - Montenegro - - - Saint Barthélemy - - - Saint Martin (French part) - - - Serbia - - - Sint Maarten (Dutch part) - - - South Sudan - - - Kosovo - - + Country (required) + + + + + Choose country + + + Afghanistan + + + Albania + + + Algeria + + + American Samoa + + + Andorra + + + Angola + + + Anguilla + + + Antarctica + + + Antigua and Barbuda + + + Argentina + + + Armenia + + + Aruba + + + Australia + + + Austria + + + Azerbaijan + + + Bahamas + + + Bahrain + + + Bangladesh + + + Barbados + + + Belarus + + + Belgium + + + Belize + + + Benin + + + Bermuda + + + Bhutan + + + Bolivia + + + Bosnia and Herzegovina + + + Botswana + + + Bouvet Island + + + Brazil + + + British Indian Ocean Territory + + + Brunei Darussalam + + + Bulgaria + + + Burkina Faso + + + Burundi + + + Cambodia + + + Cameroon + + + Canada + + + Cape Verde + + + Cayman Islands + + + Central African Republic + + + Chad + + + Chile + + + China + + + Christmas Island + + + Cocos (Keeling) Islands + + + Colombia + + + Comoros + + + Congo + + + Congo, the Democratic Republic of the + + + Cook Islands + + + Costa Rica + + + Cote D'Ivoire + + + Croatia + + + Cuba + + + Cyprus + + + Czech Republic + + + Denmark + + + Djibouti + + + Dominica + + + Dominican Republic + + + Ecuador + + + Egypt + + + El Salvador + + + Equatorial Guinea + + + Eritrea + + + Estonia + + + Ethiopia + + + Falkland Islands (Malvinas) + + + Faroe Islands + + + Fiji + + + Finland + + + France + + + French Guiana + + + French Polynesia + + + French Southern Territories + + + Gabon + + + Gambia + + + Georgia + + + Germany + + + Ghana + + + Gibraltar + + + Greece + + + Greenland + + + Grenada + + + Guadeloupe + + + Guam + + + Guatemala + + + Guinea + + + Guinea-Bissau + + + Guyana + + + Haiti + + + Heard Island and Mcdonald Islands + + + Holy See (Vatican City State) + + + Honduras + + + Hong Kong + + + Hungary + + + Iceland + + + India + + + Indonesia + + + Iran, Islamic Republic of + + + Iraq + + + Ireland + + + Israel + + + Italy + + + Jamaica + + + Japan + + + Jordan + + + Kazakhstan + + + Kenya + + + Kiribati + + + North Korea + + + South Korea + + + Kuwait + + + Kyrgyzstan + + + Lao People's Democratic Republic + + + Latvia + + + Lebanon + + + Lesotho + + + Liberia + + + Libya + + + Liechtenstein + + + Lithuania + + + Luxembourg + + + Macao + + + Madagascar + + + Malawi + + + Malaysia + + + Maldives + + + Mali + + + Malta + + + Marshall Islands + + + Martinique + + + Mauritania + + + Mauritius + + + Mayotte + + + Mexico + + + Micronesia, Federated States of + + + Moldova, Republic of + + + Monaco + + + Mongolia + + + Montserrat + + + Morocco + + + Mozambique + + + Myanmar + + + Namibia + + + Nauru + + + Nepal + + + Netherlands + + + New Caledonia + + + New Zealand + + + Nicaragua + + + Niger + + + Nigeria + + + Niue + + + Norfolk Island + + + North Macedonia, Republic of + + + Northern Mariana Islands + + + Norway + + + Oman + + + Pakistan + + + Palau + + + Palestinian Territory, Occupied + + + Panama + + + Papua New Guinea + + + Paraguay + + + Peru + + + Philippines + + + Pitcairn + + + Poland + + + Portugal + + + Puerto Rico + + + Qatar + + + Reunion + + + Romania + + + Russian Federation + + + Rwanda + + + Saint Helena + + + Saint Kitts and Nevis + + + Saint Lucia + + + Saint Pierre and Miquelon + + + Saint Vincent and the Grenadines + + + Samoa + + + San Marino + + + Sao Tome and Principe + + + Saudi Arabia + + + Senegal + + + Seychelles + + + Sierra Leone + + + Singapore + + + Slovakia + + + Slovenia + + + Solomon Islands + + + Somalia + + + South Africa + + + South Georgia and the South Sandwich Islands + + + Spain + + + Sri Lanka + + + Sudan + + + Suriname + + + Svalbard and Jan Mayen + + + Swaziland + + + Sweden + + + Switzerland + + + Syrian Arab Republic + + + Taiwan + + + Tajikistan + + + Tanzania, United Republic of + + + Thailand + + + Timor-Leste + + + Togo + + + Tokelau + + + Tonga + + + Trinidad and Tobago + + + Tunisia + + + Turkey + + + Turkmenistan + + + Turks and Caicos Islands + + + Tuvalu + + + Uganda + + + Ukraine + + + United Arab Emirates + + + United Kingdom + + + United States of America + + + United States Minor Outlying Islands + + + Uruguay + + + Uzbekistan + + + Vanuatu + + + Venezuela + + + Viet Nam + + + Virgin Islands, British + + + Virgin Islands, U.S. + + + Wallis and Futuna + + + Western Sahara + + + Yemen + + + Zambia + + + Zimbabwe + + + Åland Islands + + + Bonaire, Sint Eustatius and Saba + + + Curaçao + + + Guernsey + + + Isle of Man + + + Jersey + + + Montenegro + + + Saint Barthélemy + + + Saint Martin (French part) + + + Serbia + + + Sint Maarten (Dutch part) + + + South Sudan + + + Kosovo + + + - - - Array [ + State/Province - , + , - ] - - - + + - Zip/Postal Code - - + + Zip/Postal Code + + + - - - Billing Information (Required) - - - - - You will be charged $55 USD on April 21, 2025, then monthly until you cancel your subscription. - - - + + Billing Information (Required) + + + + You will be charged $55 USD on April 21, 2025, then monthly until you cancel your subscription. + + + - + + + - - - + + + `; diff --git a/src/subscription/data/service.js b/src/subscription/data/service.js index 6662619ab..fdceb9f1b 100644 --- a/src/subscription/data/service.js +++ b/src/subscription/data/service.js @@ -8,7 +8,7 @@ import { camelCaseObject } from '../../payment/data/utils'; * @param {data} to transform * @returns transformed data */ -const transformSubscriptionDetails = (data) => { +export const transformSubscriptionDetails = (data) => { const obj = camelCaseObject(data); obj.price = parseFloat(obj.price); obj.totalPrice = parseFloat(obj.totalPrice); diff --git a/src/subscription/data/service.test.js b/src/subscription/data/service.test.js new file mode 100644 index 000000000..06cfe2182 --- /dev/null +++ b/src/subscription/data/service.test.js @@ -0,0 +1,180 @@ +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { getConfig } from '@edx/frontend-platform'; + +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; +import { + getDetails, + postDetails, + handleDetailsApiError, + transformSubscriptionDetails, +} from './service'; + +const axiosMock = new MockAdapter(axios); + +jest.mock('axios'); + +// Mock the getAuthenticatedHttpClient function +jest.mock('@edx/frontend-platform/auth', () => ({ + getAuthenticatedHttpClient: jest.fn(), +})); + +// Mock the getConfig function +jest.mock('@edx/frontend-platform', () => ({ + getConfig: jest.fn(() => ({ + SUBSCRIPTIONS_BASE_URL: process.env.SUBSCRIPTIONS_BASE_URL, + })), + ensureConfig: jest.fn(), +})); + +getAuthenticatedHttpClient.mockReturnValue(axios); + +beforeEach(() => { + axiosMock.reset(); +}); + +const errorJSON = { + code: 'invalid_card_details', + user_message: 'Invalid Card Details.', +}; + +describe('getDetails', () => { + const subscriptionDetails = { + price: '79.00', + total_price: '79.00', + program_title: 'Blockchain Fundamentals', + is_eligible_trial: true, + payment_processor: 'stripe', + currency: 'usd', + }; + test('should return transformed data on success', async () => { + axios.get.mockResolvedValue({ + data: subscriptionDetails, + }); + + const result = await getDetails(); + + expect(result).toEqual({ + price: 79, + totalPrice: 79, + programTitle: 'Blockchain Fundamentals', + isEligibleTrial: true, + paymentProcessor: 'stripe', + currency: 'usd', + }); + expect(axios.get).toHaveBeenCalledWith(`${getConfig().SUBSCRIPTIONS_BASE_URL}/api/v1/stripe-checkout/`); + }); + + test('should throw an error on API failure', async () => { + const error = new Error(); + error.errors = [errorJSON]; + axios.get.mockRejectedValue({ + data: { + response: errorJSON, + }, + }); + await expect(getDetails()).rejects.toEqual(error); + expect(axios.get).toHaveBeenCalledWith(`${getConfig().SUBSCRIPTIONS_BASE_URL}/api/v1/stripe-checkout/`); + }); +}); + +describe('postDetails', () => { + const payload = { + programTitle: 'Blockchain Fundamentals', + programUuid: 'program-uuid', + paymentMethodId: 'pm-payment-method', + billingDetails: { address: '123 street address' }, + }; + const subscriptionSuccessStatus = { + confirmation_status: 'succeeded', + subscription_id: 'subscription-id', + price: 79, + total_price: 79, + }; + + test('should return transformed data on success', async () => { + axios.post.mockResolvedValue({ + data: subscriptionSuccessStatus, + }); + + const result = await postDetails(payload); + + expect(result).toEqual({ + subscriptionId: 'subscription-id', + confirmationStatus: 'succeeded', + price: 79, + totalPrice: 79, + }); + expect(axios.post).toHaveBeenCalledWith(`${getConfig().SUBSCRIPTIONS_BASE_URL}/api/v1/stripe-checkout/`, payload); + }); + + test('should throw an error on API failure', async () => { + const error = new Error(); + error.errors = [errorJSON]; + axios.post.mockRejectedValue({ + data: { + response: errorJSON, + }, + }); + await expect(postDetails(payload)).rejects.toEqual(error); + expect(axios.post).toHaveBeenCalledWith(`${getConfig().SUBSCRIPTIONS_BASE_URL}/api/v1/stripe-checkout/`, payload); + }); +}); + +describe('handleDetailsApiError', () => { + test('should throw an error with API errors', () => { + const requestError = { + response: { + data: { + error_code: 'SOME_ERROR', + user_message: 'Some error occurred', + }, + }, + }; + + expect(() => { + handleDetailsApiError(requestError); + }).toThrow(Error); + + try { + handleDetailsApiError(requestError); + } catch (error) { + expect(error.errors).toEqual([ + { + code: 'SOME_ERROR', + userMessage: 'Some error occurred', + }, + ]); + } + }); + + test('should throw an error without API errors', () => { + const requestError = {}; + + expect(() => { + handleDetailsApiError(requestError); + }).toThrow(Error); + + try { + handleDetailsApiError(requestError); + } catch (error) { + expect(error.errors).toEqual([]); + } + }); +}); + +describe('transformSubscriptionDetails', () => { + test('should transform and parse price values', () => { + const data = { + price: '9.99', + total_price: '19.00', + }; + + const transformedData = transformSubscriptionDetails(data); + + expect(transformedData).toEqual({ + price: 9.99, + totalPrice: 19, + }); + }); +}); diff --git a/src/subscription/subscription-methods/stripe/service.test.js b/src/subscription/subscription-methods/stripe/service.test.js new file mode 100644 index 000000000..e34ab9a25 --- /dev/null +++ b/src/subscription/subscription-methods/stripe/service.test.js @@ -0,0 +1,91 @@ +import { logError } from '@edx/frontend-platform/logging'; +import { subscriptionStripeCheckout } from './service'; + +jest.mock('@edx/frontend-platform/logging', () => ({ + logError: jest.fn(), +})); + +describe('subscriptionStripeCheckout', () => { + const programUuid = '123'; + const programTitle = 'Sample Program'; + const elements = { /* mock elements */ }; + const mockedBillingDetails = { + address: '123 Main St', + unit: 'Apt 4', + city: 'City', + country: 'Country', + state: 'State', + postalCode: '12345', + }; + const context = { authenticatedUser: { email: 'test@example.com' } }; + const values = { + firstName: 'John', + lastName: 'Doe', + ...mockedBillingDetails, + }; + + it('should return the expected post data', async () => { + // Arrange + const stripe = { + createPaymentMethod: jest.fn().mockResolvedValue({ + paymentMethod: { + id: 'payment-method-id', + billing_details: mockedBillingDetails, + }, + error: null, + }), + }; + + // Act + const postData = await subscriptionStripeCheckout({ programUuid, programTitle }, { + elements, stripe, context, values, + }); + + // Assert + expect(stripe.createPaymentMethod).toHaveBeenCalledWith({ + elements, + params: { + billing_details: { + address: { + city: values.city, + country: values.country, + line1: values.address, + line2: values.unit || '', + postal_code: values.postalCode || '', + state: values.state || '', + }, + email: context.authenticatedUser.email, + name: `${values.firstName} ${values.lastName}`, + }, + }, + }); + + expect(postData).toEqual({ + program_uuid: programUuid, + program_title: programTitle, + payment_method_id: 'payment-method-id', + billing_details: { + ...mockedBillingDetails, + firstname: values.firstName, + lastname: values.lastName, + }, + }); + expect(logError).not.toHaveBeenCalled(); + }); + + it('should throw an error and log it', async () => { + // Arrange + const stripe = { createPaymentMethod: jest.fn().mockResolvedValue({ paymentMethod: null, error: new Error('Stripe Error') }) }; + + // Act & Assert + await expect(subscriptionStripeCheckout({ programUuid, programTitle }, { + elements, stripe, context, values, + })).rejects.toThrow(Error); + expect(logError).toHaveBeenCalledWith(new Error('Stripe Error'), { + messagePrefix: 'Stripe Submit Error', + paymentMethod: 'createPaymentMethod', + paymentErrorType: 'createPaymentMethod Error', + programUuid, + }); + }); +});
- You will be charged $55 USD on April 21, 2025, then monthly until you cancel your subscription. -
+ You will be charged $55 USD on April 21, 2025, then monthly until you cancel your subscription. +