From 512b2dbd4b8cdcd798fd33bd8ea61399b6f30312 Mon Sep 17 00:00:00 2001 From: Sameen Fatima Date: Wed, 23 Aug 2023 23:52:29 +0200 Subject: [PATCH] feat: subsidy box points to learner credit added tests added more tests minor fixes --- .../sidebar/LearnerCreditSummaryCard.jsx | 83 ++++++++ .../dashboard/sidebar/SubsidiesSummary.jsx | 13 +- .../dashboard/sidebar/data/constants.js | 6 + .../sidebar/tests/DashboardSidebar.test.jsx | 36 +++- .../tests/LearnerCreditSummaryCard.test.jsx | 85 +++++++++ .../enterprise-user-subsidy/UserSubsidy.jsx | 8 +- .../data/hooks/hooks.js | 26 +++ .../data/hooks/hooks.test.jsx | 36 ++++ .../enterprise-user-subsidy/data/service.js | 10 + .../tests/UserSubsidy.test.jsx | 18 +- .../tests/hooks.test.jsx | 177 ++++++++++++++++++ 11 files changed, 493 insertions(+), 5 deletions(-) create mode 100644 src/components/dashboard/sidebar/LearnerCreditSummaryCard.jsx create mode 100644 src/components/dashboard/sidebar/tests/LearnerCreditSummaryCard.test.jsx create mode 100644 src/components/enterprise-user-subsidy/tests/hooks.test.jsx diff --git a/src/components/dashboard/sidebar/LearnerCreditSummaryCard.jsx b/src/components/dashboard/sidebar/LearnerCreditSummaryCard.jsx new file mode 100644 index 0000000000..3083ff8d6e --- /dev/null +++ b/src/components/dashboard/sidebar/LearnerCreditSummaryCard.jsx @@ -0,0 +1,83 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Badge, Row, Col } from '@edx/paragon'; +import dayjs from 'dayjs'; +import { + LEARNER_CREDIT_SUMMARY_CARD_TITLE, + LEARNER_CREDIT_ACTIVE_BADGE_LABEL, + LEARNER_CREDIT_ACTIVE_BADGE_VARIANT, + LEARNER_CREDIT_CARD_SUMMARY, +} from './data/constants'; +import SidebarCard from './SidebarCard'; + +const LearnerCreditSummaryCard = ({ + className, subsidyLearnerCredit, searchCoursesCta, +}) => { + const totalRemainingBalanceForUser = subsidyLearnerCredit.reduce( + (accumulator, currentSubsidy) => accumulator + (currentSubsidy.remainingBalancePerUser || 0), + 0, + ); + const subsidyExpiringFirst = subsidyLearnerCredit.sort( + (a, b) => new Date(a.subsidyExpirationDate) - new Date(b.subsidyExpirationDate), + )[0]; + return ( + +

{LEARNER_CREDIT_SUMMARY_CARD_TITLE}

+ + {LEARNER_CREDIT_ACTIVE_BADGE_LABEL} + + + ) + } + cardClassNames={className} + > + {totalRemainingBalanceForUser + ? ( +

+ Apply your ${totalRemainingBalanceForUser}{' '} + balance to enroll into courses. +

+ ) + : ( +

+ { LEARNER_CREDIT_CARD_SUMMARY } +

+ )} + + {subsidyExpiringFirst.subsidyExpirationDate && ( +

+ Available until {dayjs(subsidyExpiringFirst.subsidyExpirationDate).format('MMM D, YYYY')} +

+ )} + + {searchCoursesCta && ( + + {searchCoursesCta} + + )} +
+ ); +}; + +LearnerCreditSummaryCard.propTypes = { + subsidyLearnerCredit: PropTypes.arrayOf(PropTypes.shape({ + subsidyExpirationDate: PropTypes.string, + remainingBalancePerUser: PropTypes.number, + })).isRequired, + className: PropTypes.string, + searchCoursesCta: PropTypes.node, +}; + +LearnerCreditSummaryCard.defaultProps = { + className: undefined, + searchCoursesCta: undefined, +}; + +export default LearnerCreditSummaryCard; diff --git a/src/components/dashboard/sidebar/SubsidiesSummary.jsx b/src/components/dashboard/sidebar/SubsidiesSummary.jsx index 6a28f068c3..0715ded4db 100644 --- a/src/components/dashboard/sidebar/SubsidiesSummary.jsx +++ b/src/components/dashboard/sidebar/SubsidiesSummary.jsx @@ -8,6 +8,7 @@ import classNames from 'classnames'; import CouponCodesSummaryCard from './CouponCodesSummaryCard'; import SubscriptionSummaryCard from './SubscriptionSummaryCard'; import EnterpriseOffersSummaryCard from './EnterpriseOffersSummaryCard'; +import LearnerCreditSummaryCard from './LearnerCreditSummaryCard'; import { UserSubsidyContext } from '../../enterprise-user-subsidy'; import { LICENSE_STATUS } from '../../enterprise-user-subsidy/data/constants'; import { CATALOG_ACCESS_CARD_BUTTON_TEXT } from './data/constants'; @@ -35,6 +36,7 @@ const SubsidiesSummary = ({ couponCodes: { couponCodesCount }, enterpriseOffers, canEnrollWithEnterpriseOffers, + subsidyLearnerCredit, } = useContext(UserSubsidyContext); const { @@ -54,9 +56,10 @@ const SubsidiesSummary = ({ && userSubscriptionLicense?.status === LICENSE_STATUS.ACTIVATED) || licenseRequests.length > 0; const hasAssignedCodesOrCodeRequests = couponCodesCount > 0 || couponCodeRequests.length > 0; + const hasAvailableLearnerCredit = subsidyLearnerCredit?.length > 0; const hasAvailableSubsidyOrRequests = hasActiveLicenseOrLicenseRequest - || hasAssignedCodesOrCodeRequests || canEnrollWithEnterpriseOffers; + || hasAssignedCodesOrCodeRequests || canEnrollWithEnterpriseOffers || hasAvailableLearnerCredit; if (!hasAvailableSubsidyOrRequests) { return null; @@ -100,12 +103,18 @@ const SubsidiesSummary = ({ className="border-0 shadow-none" /> )} - {canEnrollWithEnterpriseOffers && ( + {!hasAvailableLearnerCredit && canEnrollWithEnterpriseOffers && ( )} + { hasAvailableLearnerCredit && ( + + )} {searchCoursesCta && ( ', () => { couponCodesCount: 0, }, enterpriseOffers: [], + subsidyLearnerCredit: undefined, }; const initialAppState = { enterpriseConfig: { @@ -173,7 +175,7 @@ describe('', () => { ); expect(screen.queryByText(SUBSCRIPTION_SUMMARY_CARD_TITLE)).toBeFalsy(); }); - test('Enterprise offers summary card is displayed when enterprise has active offers and no subscriptions or coupons', () => { + test('Enterprise offers summary card is displayed when enterprise has active offers and no subscriptions or coupons or learner credit', () => { renderWithRouter( ', () => { expect(screen.queryByText(SUBSCRIPTION_SUMMARY_CARD_TITLE)).not.toBeInTheDocument(); expect(screen.queryByText(COUPON_CODES_SUMMARY_NOTICE)).toBeInTheDocument(); }); + + test('Learner credit summary card is displayed when enterprise has learner credit', () => { + renderWithRouter( + , + ); + expect(screen.queryByText(LEARNER_CREDIT_SUMMARY_CARD_TITLE)).toBeInTheDocument(); + }); + + test('Only learner credit summary card is displayed when enterprise has both; learner credit and offers', () => { + renderWithRouter( + , + ); + expect(screen.queryByTestId('learner-credit-text-detailed')).toBeInTheDocument(); + expect(screen.queryByTestId('offer-summary-text-detailed')).toBeFalsy(); + }); + test('Find a course button is not rendered when user has no coupon codes or license subsidy', () => { renderWithRouter( ', () => { + it('should render searchCoursesCta', () => { + const cta = 'Search Courses'; + render( + {cta} + } + />, + ); + + expect(screen.getByText(LEARNER_CREDIT_SUMMARY_CARD_TITLE)).toBeInTheDocument(); + expect(screen.getByText(cta)).toBeInTheDocument(); + }); + + it('should render default summary text if remainingBalancePerUser is null', () => { + render( + , + ); + + expect(screen.getByTestId('learner-credit-summary-text')).toBeInTheDocument(); + }); + + it('should render detailed summary text if remainingBalancePerUser is not null', () => { + const subsidiesWithCredit = [ + mockSubsidyWithRemainingBalance, + { + ...mockSubsidyWithRemainingBalance, + remainingBalancePerUser: 100, + }, + ]; + render( + , + ); + // calculate sum of balance available for user across all subsidies + const totalRemainingBalance = subsidiesWithCredit.reduce((acc, subsidy) => { + if (subsidy.remainingBalancePerUser) { + return acc + subsidy.remainingBalancePerUser; + } + return acc; + }, 0); + expect(screen.getByTestId('learner-credit-text-detailed')).toBeInTheDocument(); + expect(screen.getByText(`$${totalRemainingBalance}`)).toBeInTheDocument(); + }); + + it('should render earliest subsidy end date, if applicable', () => { + render( + , + ); + + expect(screen.getByTestId('learner-credit-summary-end-date-text')).toBeInTheDocument(); + expect(screen.getByText('2022', { exact: false })).toBeInTheDocument(); + }); +}); diff --git a/src/components/enterprise-user-subsidy/UserSubsidy.jsx b/src/components/enterprise-user-subsidy/UserSubsidy.jsx index 87ac8f8f78..0893a4789a 100644 --- a/src/components/enterprise-user-subsidy/UserSubsidy.jsx +++ b/src/components/enterprise-user-subsidy/UserSubsidy.jsx @@ -9,6 +9,7 @@ import { LoadingSpinner } from '../loading-spinner'; import { useCouponCodes, useSubscriptions, + useSubsidyLearnerCredit, } from './data/hooks'; import { useEnterpriseOffers } from './enterprise-offers/data/hooks'; import { LOADING_SCREEN_READER_TEXT } from './data/constants'; @@ -18,6 +19,7 @@ export const UserSubsidyContext = createContext(); const UserSubsidy = ({ children }) => { const { enterpriseConfig, authenticatedUser } = useContext(AppContext); + const { userId } = authenticatedUser; // Subscriptions const { customerAgreementConfig, @@ -28,6 +30,7 @@ const UserSubsidy = ({ children }) => { activateUserLicense, } = useSubscriptions({ enterpriseConfig, authenticatedUser }); + const [subsidyLearnerCredit, isLoadingLearnerCredit] = useSubsidyLearnerCredit(enterpriseConfig.uuid, userId); // Coupon Codes const [couponCodes, isLoadingCouponCodes] = useCouponCodes(enterpriseConfig.uuid); @@ -50,10 +53,11 @@ const UserSubsidy = ({ children }) => { isLoadingSubscriptions, isLoadingCouponCodes, isLoadingEnterpriseOffers, + isLoadingLearnerCredit, ]; return loadingStates.includes(true); }, - [isLoadingSubscriptions, isLoadingCouponCodes, isLoadingEnterpriseOffers], + [isLoadingSubscriptions, isLoadingCouponCodes, isLoadingEnterpriseOffers, isLoadingLearnerCredit], ); const contextValue = useMemo( @@ -72,6 +76,7 @@ const UserSubsidy = ({ children }) => { showExpirationNotifications, customerAgreementConfig, activateUserLicense, + subsidyLearnerCredit, }; }, [ @@ -86,6 +91,7 @@ const UserSubsidy = ({ children }) => { showExpirationNotifications, customerAgreementConfig, activateUserLicense, + subsidyLearnerCredit, ], ); diff --git a/src/components/enterprise-user-subsidy/data/hooks/hooks.js b/src/components/enterprise-user-subsidy/data/hooks/hooks.js index d8d556f57c..6e24589f0b 100644 --- a/src/components/enterprise-user-subsidy/data/hooks/hooks.js +++ b/src/components/enterprise-user-subsidy/data/hooks/hooks.js @@ -13,6 +13,7 @@ import { LICENSE_STATUS } from '../constants'; import { fetchSubscriptionLicensesForUser, fetchCustomerAgreementData, + fetchSubsidyLearnerCredit, requestAutoAppliedLicense, activateLicense, } from '../service'; @@ -244,3 +245,28 @@ export function useCustomerAgreementData(enterpriseId) { return [customerAgreement, isLoading]; } + +export function useSubsidyLearnerCredit(enterpriseId, userID) { + const [learnerCreditSubsidies, setLearnerCreditSubsidies] = useState(); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + fetchSubsidyLearnerCredit(enterpriseId, userID) + .then((response) => { + const results = camelCaseObject(response.data); + if (results.length) { + setLearnerCreditSubsidies(results); + } else { + setLearnerCreditSubsidies(null); + } + }) + .catch((error) => { + logError(new Error(error)); + setLearnerCreditSubsidies(null); + }) + .finally(() => { + setIsLoading(false); + }); + }, [enterpriseId, userID]); + return [learnerCreditSubsidies, isLoading]; +} diff --git a/src/components/enterprise-user-subsidy/data/hooks/hooks.test.jsx b/src/components/enterprise-user-subsidy/data/hooks/hooks.test.jsx index 31824dc105..4cafcd9cea 100644 --- a/src/components/enterprise-user-subsidy/data/hooks/hooks.test.jsx +++ b/src/components/enterprise-user-subsidy/data/hooks/hooks.test.jsx @@ -7,12 +7,14 @@ import { useCouponCodes, useCustomerAgreementData, useSubscriptionLicense, + useSubsidyLearnerCredit, } from '.'; import { fetchSubscriptionLicensesForUser, activateLicense, requestAutoAppliedLicense, fetchCustomerAgreementData, + fetchSubsidyLearnerCredit, } from '../service'; import { fetchCouponsOverview } from '../../coupons/data/service'; import { fetchCouponCodeAssignments } from '../../coupons'; @@ -37,6 +39,7 @@ jest.mock('../../../../config', () => ({ const TEST_SUBSCRIPTION_UUID = 'test-subscription-uuid'; const TEST_LICENSE_UUID = 'test-license-uuid'; const TEST_ENTERPRISE_UUID = 'test-enterprise-uuid'; +const TEST_USER_ID = '35'; const TEST_ACTIVATION_KEY = 'test-activation-key'; const mockEnterpriseConfig = { @@ -50,6 +53,25 @@ const mockLicense = { activation_key: TEST_ACTIVATION_KEY, }; const mockSubscriptionPlan = { uuid: TEST_SUBSCRIPTION_UUID, is_active: true, title: 'title' }; +const mockLearnerCredit = [ + { + uuid: '3a93089e-9ff6-4d70-88d5-71c9cda7ce12', + policy_redemption_url: 'http://localhost:18270/api/v1/policy-redemption/3a93089e-9ff6-4d70-88d5-71c9cda7ce12/redeem/', + remaining_balance_per_user: 500, + remaining_balance: 50000, + subsidy_expiration_date: '2030-01-01 12:00:00Z', + policy_type: 'PerLearnerEnrollmentCreditAccessPolicy', + enterprise_customer_uuid: 'd0a6c526-670b-4991-b213-83f7b1216f29', + description: 'test Subsidy policy', + active: true, + catalog_uuid: '8e353fcc-b623-4912-85ee-1f0d045c6d1c', + subsidy_uuid: '8e353fcc-b623-4912-85ee-1f0d045c6d1c', + access_method: 'direct', + spend_limit: 5000, + per_learner_enrollment_limit: 10, + per_learner_spend_limit: 1500, + }, +]; const mockUser = { roles: [] }; const mockEnterpriseUser = { roles: [`enterprise_learner:${TEST_ENTERPRISE_UUID}`] }; const mockCustomerAgreement = { @@ -342,3 +364,17 @@ describe('useCustomerAgreementData', () => { ]); }); }); + +describe('useSubsidyLearnerCredit', () => { + it('fetches and returns learner subsidy credit', async () => { + fetchSubsidyLearnerCredit.mockResolvedValue({ + data: mockLearnerCredit, + }); + const { result, waitForNextUpdate } = renderHook( + () => useSubsidyLearnerCredit(TEST_ENTERPRISE_UUID, TEST_USER_ID), + ); + await waitForNextUpdate(); + expect(fetchSubsidyLearnerCredit).toHaveBeenCalledWith(TEST_ENTERPRISE_UUID, TEST_USER_ID); + expect(result.current[0]).toEqual(camelCaseObject(mockLearnerCredit)); + }); +}); diff --git a/src/components/enterprise-user-subsidy/data/service.js b/src/components/enterprise-user-subsidy/data/service.js index f07541efc6..82c535e124 100644 --- a/src/components/enterprise-user-subsidy/data/service.js +++ b/src/components/enterprise-user-subsidy/data/service.js @@ -35,6 +35,16 @@ export function fetchCustomerAgreementData(enterpriseUUID) { return getAuthenticatedHttpClient().get(url); } +export function fetchSubsidyLearnerCredit(enterpriseUUID, userID) { + const queryParams = new URLSearchParams({ + enterprise_customer_uuid: enterpriseUUID, + lms_user_id: userID, + }); + const config = getConfig(); + const url = `${config.ENTERPRISE_ACCESS_BASE_URL}/api/v1/policy-redemption/credits_available/?${queryParams.toString()}`; + return getAuthenticatedHttpClient().get(url); +} + export function requestAutoAppliedLicense(customerAgreementId) { const config = getConfig(); const url = `${config.LICENSE_MANAGER_URL}/api/v1/customer-agreement/${customerAgreementId}/auto-apply/`; diff --git a/src/components/enterprise-user-subsidy/tests/UserSubsidy.test.jsx b/src/components/enterprise-user-subsidy/tests/UserSubsidy.test.jsx index f34291ecb9..233443176e 100644 --- a/src/components/enterprise-user-subsidy/tests/UserSubsidy.test.jsx +++ b/src/components/enterprise-user-subsidy/tests/UserSubsidy.test.jsx @@ -6,13 +6,14 @@ import '@testing-library/jest-dom/extend-expect'; import UserSubsidy from '../UserSubsidy'; import { LOADING_SCREEN_READER_TEXT } from '../data/constants'; -import { useCouponCodes, useSubscriptions } from '../data/hooks'; +import { useCouponCodes, useSubscriptions, useSubsidyLearnerCredit } from '../data/hooks'; import { useEnterpriseOffers } from '../enterprise-offers/data/hooks'; jest.mock('../data/hooks', () => ({ ...jest.requireActual('../data/hooks'), useSubscriptions: jest.fn().mockReturnValue({}), useCouponCodes: jest.fn().mockReturnValue([]), + useSubsidyLearnerCredit: jest.fn().mockReturnValue([]), })); jest.mock('../enterprise-offers/data/hooks', () => ({ @@ -60,47 +61,62 @@ describe('UserSubsidy', () => { isSubscriptionsLoading: false, isCouponCodesLoading: false, isEnterpriseOffersLoading: false, + isLoadingLearnerCredit: false, isLoadingExpected: false, }, { isSubscriptionsLoading: true, isCouponCodesLoading: false, isEnterpriseOffersLoading: false, + isLoadingLearnerCredit: false, isLoadingExpected: true, }, { isSubscriptionsLoading: false, isCouponCodesLoading: true, isEnterpriseOffersLoading: false, + isLoadingLearnerCredit: false, isLoadingExpected: true, }, { isSubscriptionsLoading: false, isCouponCodesLoading: false, isEnterpriseOffersLoading: true, + isLoadingLearnerCredit: false, + isLoadingExpected: true, + }, + { + isSubscriptionsLoading: false, + isCouponCodesLoading: false, + isEnterpriseOffersLoading: false, + isLoadingLearnerCredit: true, isLoadingExpected: true, }, { isSubscriptionsLoading: true, isCouponCodesLoading: true, isEnterpriseOffersLoading: true, + isLoadingLearnerCredit: true, isLoadingExpected: true, }, { isSubscriptionsLoading: true, isCouponCodesLoading: true, isEnterpriseOffersLoading: false, + isLoadingLearnerCredit: true, isLoadingExpected: true, }, ])('shows loading spinner when expected (%s)', ({ isSubscriptionsLoading, isCouponCodesLoading, isEnterpriseOffersLoading, + isLoadingLearnerCredit, isLoadingExpected, }) => { useSubscriptions.mockReturnValue({ isLoading: isSubscriptionsLoading }); useCouponCodes.mockReturnValue([[], isCouponCodesLoading]); useEnterpriseOffers.mockReturnValue({ isLoading: isEnterpriseOffersLoading }); + useSubsidyLearnerCredit.mockReturnValue([[], isLoadingLearnerCredit]); const Component = ( diff --git a/src/components/enterprise-user-subsidy/tests/hooks.test.jsx b/src/components/enterprise-user-subsidy/tests/hooks.test.jsx new file mode 100644 index 0000000000..79f88aadeb --- /dev/null +++ b/src/components/enterprise-user-subsidy/tests/hooks.test.jsx @@ -0,0 +1,177 @@ +import { renderHook } from '@testing-library/react-hooks'; +import * as logging from '@edx/frontend-platform/logging'; +import { camelCaseObject } from '@edx/frontend-platform/utils'; +import { useSubscriptionLicense, useSubsidyLearnerCredit } from '../data/hooks'; +import { + fetchSubscriptionLicensesForUser, + activateLicense, + fetchSubsidyLearnerCredit, +} from '../data/service'; +import { LICENSE_STATUS } from '../data/constants'; + +jest.mock('../data/service'); +jest.mock('@edx/frontend-platform/logging', () => ({ + logError: jest.fn(), +})); + +const TEST_SUBSCRIPTION_UUID = 'test-subscription-uuid'; +const TEST_LICENSE_UUID = 'test-license-uuid'; +const TEST_ENTERPRISE_UUID = 'test-enterprise-uuid'; +const TEST_ACTIVATION_KEY = 'test-activation-key'; +const TEST_USER_ID = '34'; + +const mockEnterpriseConfig = { + uuid: TEST_ENTERPRISE_UUID, + identityProvider: undefined, +}; + +const mockLicense = { + uuid: TEST_LICENSE_UUID, + status: LICENSE_STATUS.ASSIGNED, + subscription_plan_uuid: TEST_SUBSCRIPTION_UUID, + activation_key: TEST_ACTIVATION_KEY, +}; + +const mockSubscriptionPlan = { uuid: TEST_SUBSCRIPTION_UUID, is_active: true, title: 'title' }; + +const mockLearnerCredit = [ + { + uuid: '3a93089e-9ff6-4d70-88d5-71c9cda7ce12', + policy_redemption_url: 'http://localhost:18270/api/v1/policy-redemption/3a93089e-9ff6-4d70-88d5-71c9cda7ce12/redeem/', + remaining_balance_per_user: 500, + remaining_balance: 50000, + subsidy_expiration_date: '2030-01-01 12:00:00Z', + policy_type: 'PerLearnerEnrollmentCreditAccessPolicy', + enterprise_customer_uuid: 'd0a6c526-670b-4991-b213-83f7b1216f29', + description: 'test Subsidy policy', + active: true, + catalog_uuid: '8e353fcc-b623-4912-85ee-1f0d045c6d1c', + subsidy_uuid: '8e353fcc-b623-4912-85ee-1f0d045c6d1c', + access_method: 'direct', + spend_limit: 5000, + per_learner_enrollment_limit: 10, + per_learner_spend_limit: 1500, + }, +]; + +describe('useSubscriptionLicense', () => { + it('does nothing if customer agreement is still loading', async () => { + const args = { + enterpriseConfig: mockEnterpriseConfig, + customerAgreementConfig: undefined, + isLoadingCustomerAgreementConfig: true, + }; + renderHook(() => useSubscriptionLicense(args)); + expect(fetchSubscriptionLicensesForUser).not.toHaveBeenCalled(); + }); + + it('fetches user license if customer agreement is finished loading', async () => { + const args = { + enterpriseConfig: mockEnterpriseConfig, + customerAgreementConfig: undefined, + isLoadingCustomerAgreementConfig: false, + }; + const { waitForNextUpdate } = renderHook(() => useSubscriptionLicense(args)); + await waitForNextUpdate(); + expect(fetchSubscriptionLicensesForUser).toHaveBeenCalledWith(TEST_ENTERPRISE_UUID); + }); + + it('sets the subscription plan object on the user license', async () => { + fetchSubscriptionLicensesForUser.mockResolvedValueOnce({ + data: { + results: [mockLicense], + }, + }); + + const args = { + enterpriseConfig: mockEnterpriseConfig, + customerAgreementConfig: { + subscriptions: [mockSubscriptionPlan], + }, + isLoadingCustomerAgreementConfig: false, + }; + const { result, waitForNextUpdate } = renderHook(() => useSubscriptionLicense(args)); + await waitForNextUpdate(); + + expect(fetchSubscriptionLicensesForUser).toHaveBeenCalledWith(TEST_ENTERPRISE_UUID); + expect(result.current.license.subscriptionPlan).toEqual(mockSubscriptionPlan); + }); + + describe('activateUserLicense', () => { + beforeEach(() => { + fetchSubscriptionLicensesForUser.mockResolvedValue({ + data: { + results: [mockLicense], + }, + }); + }); + + afterEach(() => jest.clearAllMocks()); + + it('activates the user license and updates the license status', async () => { + activateLicense.mockResolvedValueOnce(true); + + const args = { + enterpriseConfig: mockEnterpriseConfig, + customerAgreementConfig: { + subscriptions: [mockSubscriptionPlan], + }, + isLoadingCustomerAgreementConfig: false, + }; + const { result, waitForNextUpdate } = renderHook(() => useSubscriptionLicense(args)); + + await waitForNextUpdate(); + + const { activateUserLicense } = result.current; + + activateUserLicense(); + + await waitForNextUpdate(); + + expect(activateLicense).toHaveBeenCalledWith(mockLicense.activation_key); + expect(result.current.license.status).toEqual(LICENSE_STATUS.ACTIVATED); + }); + + it('handles errors', async () => { + const mockError = new Error('something went swrong'); + activateLicense.mockRejectedValueOnce(mockError); + + const args = { + enterpriseConfig: mockEnterpriseConfig, + customerAgreementConfig: { + subscriptions: [mockSubscriptionPlan], + }, + isLoadingCustomerAgreementConfig: false, + }; + const { result, waitForNextUpdate } = renderHook(() => useSubscriptionLicense(args)); + + await waitForNextUpdate(); + + const { activateUserLicense } = result.current; + + try { + await activateUserLicense(); + } catch (error) { + expect(error).toEqual(mockError); + } + + expect(activateLicense).toHaveBeenCalledWith(mockLicense.activation_key); + expect(result.current.license.status).toEqual(LICENSE_STATUS.ASSIGNED); + expect(logging.logError).toHaveBeenCalledWith(mockError); + }); + }); + + describe('useSubsidyLearnerCredit', () => { + it('fetches and returns learner subsidy credit', async () => { + fetchSubsidyLearnerCredit.mockResolvedValue({ + data: mockLearnerCredit, + }); + const { result, waitForNextUpdate } = renderHook( + () => useSubsidyLearnerCredit(TEST_ENTERPRISE_UUID, TEST_USER_ID), + ); + await waitForNextUpdate(); + expect(fetchSubsidyLearnerCredit).toHaveBeenCalledWith(TEST_ENTERPRISE_UUID, TEST_USER_ID); + expect(result.current[0]).toEqual(camelCaseObject(mockLearnerCredit)); + }); + }); +});