Skip to content

Commit

Permalink
feat: Improve code coverage 3 (#1065)
Browse files Browse the repository at this point in the history
  • Loading branch information
brobro10000 committed Apr 25, 2024
1 parent 5d7cddb commit c0e341a
Show file tree
Hide file tree
Showing 12 changed files with 1,609 additions and 131 deletions.
1 change: 0 additions & 1 deletion src/components/app/data/hooks/useBrowseAndRequest.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ const Wrapper = ({ children }) => (
<AppContext.Provider value={{ authenticatedUser: mockAuthenticatedUser }}>
{children}
</AppContext.Provider>

</QueryClientProvider>
);

Expand Down
60 changes: 35 additions & 25 deletions src/components/app/data/hooks/useCourseRedemptionEligibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,36 @@ import { queryCanRedeem } from '../queries';
import useEnterpriseCustomer from './useEnterpriseCustomer';
import useLateRedemptionBufferDays from './useLateRedemptionBufferDays';

export function transformCourseRedemptionEligibility({
courseMetadata,
canRedeemData,
courseRunKey,
}) {
const redeemabilityForActiveCourseRun = canRedeemData.find(r => r.contentKey === courseMetadata.activeCourseRun?.key);
const missingSubsidyAccessPolicyReason = redeemabilityForActiveCourseRun?.reasons[0];
const preferredSubsidyAccessPolicy = redeemabilityForActiveCourseRun?.redeemableSubsidyAccessPolicy;
const otherSubsidyAccessPolicy = canRedeemData.find(
r => r.redeemableSubsidyAccessPolicy,
)?.redeemableSubsidyAccessPolicy;
const listPrice = redeemabilityForActiveCourseRun?.listPrice?.usd;
const hasSuccessfulRedemption = courseRunKey
? !!canRedeemData.find(r => r.contentKey === courseRunKey)?.hasSuccessfulRedemption
: canRedeemData.some(r => r.hasSuccessfulRedemption);

// If there is a redeemable subsidy access policy for the active course run, use that. Otherwise, use any other
// redeemable subsidy access policy for any of the content keys.
const redeemableSubsidyAccessPolicy = preferredSubsidyAccessPolicy || otherSubsidyAccessPolicy;
const isPolicyRedemptionEnabled = hasSuccessfulRedemption || !!redeemableSubsidyAccessPolicy;
return {
isPolicyRedemptionEnabled,
redeemabilityPerContentKey: canRedeemData,
redeemableSubsidyAccessPolicy,
missingSubsidyAccessPolicyReason,
hasSuccessfulRedemption,
listPrice,
};
}

/**
* Retrieves the course redemption eligibility for the given enterprise customer and course key.
* @returns {Types.UseQueryResult}} The query results for the course redemption eligibility.
Expand All @@ -21,31 +51,11 @@ export default function useCourseRedemptionEligibility(queryOptions = {}) {
...queryCanRedeem(enterpriseCustomer.uuid, courseMetadata, isEnrollableBufferDays),
enabled: !!courseMetadata,
select: (data) => {
const redeemabilityForActiveCourseRun = data.find(r => r.contentKey === courseMetadata.activeCourseRun?.key);
const missingSubsidyAccessPolicyReason = redeemabilityForActiveCourseRun?.reasons[0];
const preferredSubsidyAccessPolicy = redeemabilityForActiveCourseRun?.redeemableSubsidyAccessPolicy;
const otherSubsidyAccessPolicy = data.find(
r => r.redeemableSubsidyAccessPolicy,
)?.redeemableSubsidyAccessPolicy;
const listPrice = redeemabilityForActiveCourseRun?.listPrice?.usd;

const hasSuccessfulRedemption = courseRunKey
? !!data.find(r => r.contentKey === courseRunKey)?.hasSuccessfulRedemption
: data.some(r => r.hasSuccessfulRedemption);

// If there is a redeemable subsidy access policy for the active course run, use that. Otherwise, use any other
// redeemable subsidy access policy for any of the content keys.
const redeemableSubsidyAccessPolicy = preferredSubsidyAccessPolicy || otherSubsidyAccessPolicy;
const isPolicyRedemptionEnabled = hasSuccessfulRedemption || !!redeemableSubsidyAccessPolicy;

const transformedData = {
isPolicyRedemptionEnabled,
redeemabilityPerContentKey: data,
redeemableSubsidyAccessPolicy,
missingSubsidyAccessPolicyReason,
hasSuccessfulRedemption,
listPrice,
};
const transformedData = transformCourseRedemptionEligibility({
courseMetadata,
canRedeemData: data,
courseRunKey,
});
if (select) {
return select({
original: data,
Expand Down
197 changes: 197 additions & 0 deletions src/components/app/data/hooks/useCourseRedemptionEligibility.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import { renderHook } from '@testing-library/react-hooks';
import { QueryClientProvider } from '@tanstack/react-query';
import { useParams } from 'react-router-dom';
import dayjs from 'dayjs';
import { enterpriseCustomerFactory } from '../services/data/__factories__';
import useEnterpriseCustomer from './useEnterpriseCustomer';
import { queryClient } from '../../../../utils/tests';
import { fetchCanRedeem } from '../services';
import useCourseMetadata from './useCourseMetadata';
import { useCourseRedemptionEligibility, useLateRedemptionBufferDays } from './index';
import { transformCourseRedemptionEligibility } from './useCourseRedemptionEligibility';

jest.mock('./useEnterpriseCustomer');
jest.mock('./useCourseMetadata');
jest.mock('./useLateRedemptionBufferDays');
jest.mock('../services', () => ({
...jest.requireActual('../services'),
fetchCanRedeem: jest.fn().mockResolvedValue(null),
}));
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useParams: jest.fn(),
}));
const mockEnterpriseCustomer = enterpriseCustomerFactory();
const mockCourseRunKey = 'course-v1:edX+DemoX+T2024';
const mockCourseMetadata = {
key: 'edX+DemoX',
courseRuns: [{
key: mockCourseRunKey,
isMarketable: true,
availability: 'Current',
enrollmentStart: dayjs().add(10, 'day').toISOString(),
enrollmentEnd: dayjs().add(15, 'day').toISOString(),
isEnrollable: true,
}],
activeCourseRun: {
key: mockCourseRunKey,
},
};
const mockCanRedeemData = [{
contentKey: mockCourseRunKey,
listPrice: {
usd: 1,
usdCents: 100,
},
redemptions: [],
hasSuccessfulRedemption: false,
redeemableSubsidyAccessPolicy: {
uuid: 'test-access-policy-uuid',
policyRedemptionUrl: 'https://enterprise-access.stage.edx.org/api/v1/policy-redemption/8c4a92c7-3578-407d-9ba1-9127c4e4cc0b/redeem/',
isLateRedemptionAllowed: false,
policyType: 'PerLearnerSpendCreditAccessPolicy',
enterpriseCustomerUuid: mockEnterpriseCustomer.uuid,
displayName: 'Learner driven plan --- Open Courses',
description: 'Initial Policy Display Name: Learner driven plan --- Open Courses, Initial Policy Value: $10,000, Initial Subsidy Value: $260,000',
active: true,
retired: false,
catalogUuid: 'test-catalog-uuid',
subsidyUuid: 'test-subsidy-uuid',
accessMethod: 'direct',
spendLimit: 1000000,
lateRedemptionAllowedUntil: null,
perLearnerEnrollmentLimit: null,
perLearnerSpendLimit: null,
assignmentConfiguration: null,
},
canRedeem: true,
reasons: [],
}];

const mockExpectedUseCouseRedemptionEligibilityReturn = transformCourseRedemptionEligibility({
courseMetadata: mockCourseMetadata,
courseRunKey: mockCourseRunKey,
canRedeemData: mockCanRedeemData,
});

describe('useCourseRedemptionEligibility', () => {
const Wrapper = ({ children }) => (
<QueryClientProvider client={queryClient()}>
{children}
</QueryClientProvider>
);
beforeEach(() => {
jest.clearAllMocks();
useEnterpriseCustomer.mockReturnValue({ data: mockEnterpriseCustomer });
fetchCanRedeem.mockResolvedValue(mockCanRedeemData);
useParams.mockReturnValue({ courseRunKey: mockCourseRunKey });
useCourseMetadata.mockReturnValue({ data: mockCourseMetadata });
useLateRedemptionBufferDays.mockReturnValue(undefined);
});
it('should handle resolved value correctly', async () => {
const { result, waitForNextUpdate } = renderHook(() => useCourseRedemptionEligibility(), { wrapper: Wrapper });
await waitForNextUpdate();

expect(result.current).toEqual(
expect.objectContaining({
data: mockExpectedUseCouseRedemptionEligibilityReturn,
isLoading: false,
isFetching: false,
}),
);
});

it.each([
{
courseRunKey: mockCourseRunKey,
canRedeemData: [{
...mockCanRedeemData[0],
hasSuccessfulRedemption: false,
}],
expectedHasSuccessfulRedemption: false,
},
{
courseRunKey: mockCourseRunKey,
canRedeemData: [{
...mockCanRedeemData[0],
hasSuccessfulRedemption: true,
}],
expectedHasSuccessfulRedemption: true,
},
{
courseRunKey: null,
canRedeemData: [
{
...mockCanRedeemData[0],
hasSuccessfulRedemption: false,
},
{
...mockCanRedeemData[0],
hasSuccessfulRedemption: false,
},
{
...mockCanRedeemData[0],
hasSuccessfulRedemption: true,
},
],
expectedHasSuccessfulRedemption: true,
},
{
courseRunKey: null,
canRedeemData: mockCanRedeemData,
expectedHasSuccessfulRedemption: false,
},
])('should resolve as expected for hasSuccessfulRedemption (%s)', async ({
courseRunKey,
canRedeemData,
expectedHasSuccessfulRedemption,
}) => {
useParams.mockReturnValue({ courseRunKey });
fetchCanRedeem.mockResolvedValue(canRedeemData);

const { result, waitForNextUpdate } = renderHook(() => useCourseRedemptionEligibility(), { wrapper: Wrapper });
await waitForNextUpdate();
expect(result.current.data.hasSuccessfulRedemption).toEqual(expectedHasSuccessfulRedemption);
});
it.each([
{
courseMetadata: mockCourseMetadata,
canRedeemData: mockCanRedeemData,
expectedRedeemableSubsidyAccessPolicy: mockCanRedeemData[0].redeemableSubsidyAccessPolicy,
},
{
courseMetadata: {
...mockCourseMetadata,
activeCourseRun: {
key: 'course-v1:edX+DemoX+T2023',
},
},
canRedeemData: [
{
...mockCanRedeemData[0],
redeemableSubsidyAccessPolicy: {},
},
],
expectedRedeemableSubsidyAccessPolicy: {},
},
])('should resolve as expected for redeemableSubsidyAccessPolicy (%s)', async ({
courseMetadata,
canRedeemData,
expectedRedeemableSubsidyAccessPolicy,
}) => {
useCourseMetadata.mockReturnValue({ data: courseMetadata });
fetchCanRedeem.mockResolvedValue(canRedeemData);
const { result, waitForNextUpdate } = renderHook(() => useCourseRedemptionEligibility(), { wrapper: Wrapper });
await waitForNextUpdate();
expect(result.current.data.redeemableSubsidyAccessPolicy).toEqual(expectedRedeemableSubsidyAccessPolicy);
});
it('should return the original and transformed data when select is passed', async () => {
const { result, waitForNextUpdate } = renderHook(() => useCourseRedemptionEligibility({
select: (data) => data,
}), { wrapper: Wrapper });
await waitForNextUpdate();

expect(result.current.data.original).toEqual(mockCanRedeemData);
expect(result.current.data.transformed).toEqual(mockExpectedUseCouseRedemptionEligibilityReturn);
});
});
28 changes: 19 additions & 9 deletions src/components/app/data/hooks/useEnterpriseCourseEnrollments.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@ import {
} from '../utils';
import { COURSE_STATUSES } from '../../../../constants';

export const transformAllEnrollmentsByStatus = ({
enterpriseCourseEnrollments,
requests,
contentAssignments,
}) => {
const enrollmentsByStatus = groupCourseEnrollmentsByStatus(enterpriseCourseEnrollments);
const licenseRequests = requests.subscriptionLicenses;
const couponCodeRequests = requests.couponCodes;
const subsidyRequests = [].concat(licenseRequests).concat(couponCodeRequests);
enrollmentsByStatus[COURSE_STATUSES.requested] = subsidyRequests;
enrollmentsByStatus[COURSE_STATUSES.assigned] = contentAssignments;
return enrollmentsByStatus;
};

/**
* Retrieves the relevant enterprise course enrollments, subsidy requests (e.g., license
* requests), and content assignments for the active enterprise customer user.
Expand Down Expand Up @@ -55,15 +69,11 @@ export default function useEnterpriseCourseEnrollments() {
},
});

const allEnrollmentsByStatus = useMemo(() => {
const enrollmentsByStatus = groupCourseEnrollmentsByStatus(enterpriseCourseEnrollments);
const licenseRequests = requests.subscriptionLicenses;
const couponCodeRequests = requests.couponCodes;
const subsidyRequests = [].concat(licenseRequests).concat(couponCodeRequests);
enrollmentsByStatus[COURSE_STATUSES.requested] = subsidyRequests;
enrollmentsByStatus[COURSE_STATUSES.assigned] = contentAssignments;
return enrollmentsByStatus;
}, [enterpriseCourseEnrollments, requests, contentAssignments]);
const allEnrollmentsByStatus = useMemo(() => transformAllEnrollmentsByStatus({
enterpriseCourseEnrollments,
requests,
contentAssignments,
}), [contentAssignments, enterpriseCourseEnrollments, requests]);

return useMemo(() => ({
data: {
Expand Down
Loading

0 comments on commit c0e341a

Please sign in to comment.