Skip to content

Commit d71fce3

Browse files
add tests for Manage button gating and ContractAdminPage branches
1 parent c33a872 commit d71fce3

2 files changed

Lines changed: 193 additions & 0 deletions

File tree

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import React from "react"
2+
import { renderWithProviders, screen } from "@/test-utils"
3+
import { setMockResponse } from "api/test-utils"
4+
import { factories } from "api/mitxonline-test-utils"
5+
import { useFeatureFlagEnabled } from "posthog-js/react"
6+
import { allowConsoleErrors } from "ol-test-utilities"
7+
import { ForbiddenError } from "@/common/errors"
8+
import ContractAdminPage from "./ContractAdminPage"
9+
10+
jest.mock("posthog-js/react", () => ({
11+
...jest.requireActual("posthog-js/react"),
12+
useFeatureFlagEnabled: jest.fn(),
13+
}))
14+
const mockedUseFeatureFlagEnabled = jest.mocked(useFeatureFlagEnabled)
15+
16+
const API_BASE_URL = process.env.NEXT_PUBLIC_MITX_ONLINE_BASE_URL
17+
const managerOrgsUrl = `${API_BASE_URL}/api/v0/b2b/manager/organizations/`
18+
const managerContractDetailUrl = (orgId: number, contractId: number) =>
19+
`${API_BASE_URL}/api/v0/b2b/manager/organizations/${orgId}/contracts/${contractId}/`
20+
21+
const makeOrgWithContract = () => {
22+
const contract = factories.contracts.contract()
23+
const org = factories.organizations.organization({ contracts: [contract] })
24+
return { org, contract }
25+
}
26+
27+
describe("ContractAdminPage", () => {
28+
beforeEach(() => {
29+
mockedUseFeatureFlagEnabled.mockReturnValue(undefined)
30+
})
31+
32+
test("throws ForbiddenError when feature flag is explicitly false", () => {
33+
mockedUseFeatureFlagEnabled.mockReturnValue(false)
34+
allowConsoleErrors()
35+
36+
expect(() =>
37+
renderWithProviders(
38+
<ContractAdminPage orgSlug="any-org" contractSlug="any-contract" />,
39+
),
40+
).toThrow(ForbiddenError)
41+
})
42+
43+
test("renders nothing while feature flag is loading (undefined)", () => {
44+
mockedUseFeatureFlagEnabled.mockReturnValue(undefined)
45+
46+
const { view } = renderWithProviders(
47+
<ContractAdminPage orgSlug="any-org" contractSlug="any-contract" />,
48+
)
49+
50+
expect(view.container.firstChild).toBeNull()
51+
})
52+
53+
test("shows 'Organization not found' when user is not a manager for the requested org", async () => {
54+
mockedUseFeatureFlagEnabled.mockReturnValue(true)
55+
56+
const otherOrg = factories.organizations.organization({})
57+
setMockResponse.get(managerOrgsUrl, [otherOrg])
58+
59+
renderWithProviders(
60+
<ContractAdminPage orgSlug="not-my-org" contractSlug="some-contract" />,
61+
)
62+
63+
await screen.findByRole("heading", { name: "Organization not found" })
64+
})
65+
66+
test("shows 'Contract not found' when org is found but contract slug does not match", async () => {
67+
mockedUseFeatureFlagEnabled.mockReturnValue(true)
68+
69+
const { org } = makeOrgWithContract()
70+
setMockResponse.get(managerOrgsUrl, [org])
71+
72+
renderWithProviders(
73+
<ContractAdminPage
74+
orgSlug={org.slug}
75+
contractSlug="wrong-contract-slug"
76+
/>,
77+
)
78+
79+
await screen.findByRole("heading", { name: "Contract not found" })
80+
})
81+
82+
test("renders org name and contract name when flag is on and user is a manager", async () => {
83+
mockedUseFeatureFlagEnabled.mockReturnValue(true)
84+
85+
const { org, contract } = makeOrgWithContract()
86+
setMockResponse.get(managerOrgsUrl, [org])
87+
setMockResponse.get(managerContractDetailUrl(org.id, contract.id), {
88+
...contract,
89+
attachment_percentage: null,
90+
total_enrollments: 0,
91+
total_codes: 50,
92+
})
93+
94+
renderWithProviders(
95+
<ContractAdminPage orgSlug={org.slug} contractSlug={contract.slug} />,
96+
)
97+
98+
await screen.findByRole("heading", { name: org.name })
99+
expect(screen.getByText(contract.name)).toBeInTheDocument()
100+
})
101+
102+
test("renders seat count from contract detail", async () => {
103+
mockedUseFeatureFlagEnabled.mockReturnValue(true)
104+
105+
const { org, contract } = makeOrgWithContract()
106+
setMockResponse.get(managerOrgsUrl, [org])
107+
setMockResponse.get(managerContractDetailUrl(org.id, contract.id), {
108+
...contract,
109+
attachment_percentage: null,
110+
total_enrollments: 12,
111+
total_codes: 75,
112+
})
113+
114+
renderWithProviders(
115+
<ContractAdminPage orgSlug={org.slug} contractSlug={contract.slug} />,
116+
)
117+
118+
await screen.findByText("75 seats")
119+
})
120+
})

frontends/main/src/app-pages/DashboardPage/ContractContent.test.tsx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ import {
1919
import { CourseWithCourseRunsSerializerV2 } from "@mitodl/mitxonline-api-axios/v2"
2020
import { faker } from "@faker-js/faker/locale/en"
2121
import invariant from "tiny-invariant"
22+
import { useFeatureFlagEnabled } from "posthog-js/react"
23+
import { FeatureFlags } from "@/common/feature_flags"
24+
import { contractAdminView } from "@/common/urls"
25+
26+
jest.mock("posthog-js/react", () => ({
27+
...jest.requireActual("posthog-js/react"),
28+
useFeatureFlagEnabled: jest.fn(),
29+
}))
30+
const mockedUseFeatureFlagEnabled = jest.mocked(useFeatureFlagEnabled)
31+
32+
const API_BASE_URL = process.env.NEXT_PUBLIC_MITX_ONLINE_BASE_URL
33+
const managerOrganizationsUrl = `${API_BASE_URL}/api/v0/b2b/manager/organizations/`
2234

2335
const makeCourseEnrollment = factories.enrollment.courseEnrollment
2436
const makeGrade = factories.enrollment.grade
@@ -56,6 +68,7 @@ describe("ContractContent", () => {
5668
setMockResponse.get(urls.enrollment.enrollmentsListV3(), [])
5769
setMockResponse.get(urls.programEnrollments.enrollmentsListV3(), [])
5870
setMockResponse.get(urls.contracts.contractsList(), [])
71+
mockedUseFeatureFlagEnabled.mockReturnValue(undefined)
5972
})
6073

6174
it("displays a header for each program returned and cards for courses in program", async () => {
@@ -1394,6 +1407,66 @@ describe("ContractContent", () => {
13941407
await screen.findByRole("heading", { name: "Contract not found" })
13951408
})
13961409

1410+
test("does not render the Manage button when the feature flag is off", async () => {
1411+
mockedUseFeatureFlagEnabled.mockImplementation(() => false)
1412+
const { orgX } = setupProgramsAndCourses()
1413+
1414+
setMockResponse.get(managerOrganizationsUrl, [orgX])
1415+
1416+
renderWithProviders(
1417+
<ContractContent
1418+
orgSlug={orgX.slug}
1419+
contractSlug={orgX.contracts[0].slug}
1420+
/>,
1421+
)
1422+
1423+
await screen.findByRole("heading", { name: orgX.name })
1424+
expect(screen.queryByRole("link", { name: "Manage" })).not.toBeInTheDocument()
1425+
})
1426+
1427+
test("does not render the Manage button when flag is on but user is not a manager for this org", async () => {
1428+
mockedUseFeatureFlagEnabled.mockImplementation(
1429+
(flag) => flag === FeatureFlags.B2BContractManagerDashboard,
1430+
)
1431+
const { orgX } = setupProgramsAndCourses()
1432+
1433+
// Return a different org — user is a manager elsewhere, not for orgX
1434+
const otherOrg = factories.organizations.organization({})
1435+
setMockResponse.get(managerOrganizationsUrl, [otherOrg])
1436+
1437+
renderWithProviders(
1438+
<ContractContent
1439+
orgSlug={orgX.slug}
1440+
contractSlug={orgX.contracts[0].slug}
1441+
/>,
1442+
)
1443+
1444+
await screen.findByRole("heading", { name: orgX.name })
1445+
expect(screen.queryByRole("link", { name: "Manage" })).not.toBeInTheDocument()
1446+
})
1447+
1448+
test("renders the Manage button with correct href when flag is on and user is a manager", async () => {
1449+
mockedUseFeatureFlagEnabled.mockImplementation(
1450+
(flag) => flag === FeatureFlags.B2BContractManagerDashboard,
1451+
)
1452+
const { orgX } = setupProgramsAndCourses()
1453+
1454+
setMockResponse.get(managerOrganizationsUrl, [orgX])
1455+
1456+
renderWithProviders(
1457+
<ContractContent
1458+
orgSlug={orgX.slug}
1459+
contractSlug={orgX.contracts[0].slug}
1460+
/>,
1461+
)
1462+
1463+
const manageButton = await screen.findByRole("link", { name: "Manage" })
1464+
expect(manageButton).toHaveAttribute(
1465+
"href",
1466+
contractAdminView(orgX.slug, orgX.contracts[0].slug),
1467+
)
1468+
})
1469+
13971470
test("sanitizes HTML content in welcome_message_extra", async () => {
13981471
const { orgX } = setupProgramsAndCourses()
13991472

0 commit comments

Comments
 (0)