Skip to content

Commit 18814e3

Browse files
authored
New: [AEA-4656] - One role with access (#303)
## Summary 🎫 [AEA-4656](https://nhsd-jira.digital.nhs.uk/browse/AEA-4656) One role with access 🧪 Regression Tests: [PR-212](NHSDigital/electronic-prescription-service-api-regression-tests#212) 🆔 User UID: 555043300081 ✨ New Feature ### Details A user has one role with access to CPTS.
1 parent 43b40cf commit 18814e3

File tree

10 files changed

+221
-114
lines changed

10 files changed

+221
-114
lines changed

.github/workflows/deploy_website_content.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ jobs:
7575
export NEXT_PUBLIC_hostedLoginDomain=${hostedLoginDomain}
7676
export NEXT_PUBLIC_userPoolClientId=${userPoolClientId}
7777
export NEXT_PUBLIC_userPoolId=${userPoolId}
78-
export NEXT_PUBLIC_redirectSignIn="https://${fullCloudfrontDomain}/site/auth_demo.html"
78+
export NEXT_PUBLIC_redirectSignIn="https://${fullCloudfrontDomain}/site/selectyourrole.html"
7979
export NEXT_PUBLIC_redirectSignOut="https://${fullCloudfrontDomain}/site/"
8080
export NEXT_PUBLIC_COMMIT_ID=${{ inputs.COMMIT_ID }}
8181

.github/workflows/link_dev_website.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,4 @@ jobs:
4040
uses: unsplash/comment-on-pr@master
4141
with:
4242
msg: |
43-
Deployed URL: https://cpt-ui-pr-${{ needs.get_issue_number.outputs.issue_number }}.dev.eps.national.nhs.uk
43+
Deployed URL: https://cpt-ui-pr-${{ needs.get_issue_number.outputs.issue_number }}.dev.eps.national.nhs.uk/site

.vscode/eps-prescription-tracker-ui.code-workspace

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115
"reingested",
116116
"Reingestion",
117117
"rvest",
118+
"searchforaprescription",
118119
"selectyourrole",
119120
"serialisation",
120121
"shellcheck",

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export LOCAL_DEV=true
101101
export API_DOMAIN_OVERRIDE=https://${SERVICE_NAME}.dev.eps.national.nhs.uk/
102102
103103
export NEXT_PUBLIC_hostedLoginDomain=${SERVICE_NAME}.auth.eu-west-2.amazoncognito.com
104-
export NEXT_PUBLIC_redirectSignIn=http://localhost:3000/auth_demo/
104+
export NEXT_PUBLIC_redirectSignIn=http://localhost:3000/selectyourrole/
105105
export NEXT_PUBLIC_redirectSignOut=http://localhost:3000/
106106
107107
export NEXT_PUBLIC_COMMIT_ID="Local Development Server"

packages/cdk/resources/Cognito.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,9 @@ export class Cognito extends Construct {
188188
const callbackUrls = [
189189
`https://${props.fullCloudfrontDomain}/site/`,
190190
// FIXME: This is temporary, until we get routing fixed
191-
`https://${props.fullCloudfrontDomain}/site/auth_demo.html`,
191+
`https://${props.fullCloudfrontDomain}/site/selectyourrole.html`,
192192
`https://${props.fullCloudfrontDomain}/auth_demo/`,
193+
`https://${props.fullCloudfrontDomain}/site/selectyourrole/`,
193194
`https://${props.fullCloudfrontDomain}/oauth2/idpresponse`
194195
]
195196

@@ -200,9 +201,10 @@ export class Cognito extends Construct {
200201
]
201202

202203
if (props.useLocalhostCallback) {
203-
callbackUrls.push( "http://localhost:3000/auth/")
204-
callbackUrls.push( "http://localhost:3000/auth_demo/")
205-
logoutUrls.push( "http://localhost:3000/")
204+
callbackUrls.push("http://localhost:3000/auth/")
205+
callbackUrls.push("http://localhost:3000/auth_demo/")
206+
callbackUrls.push("http://localhost:3000/selectyourrole/")
207+
logoutUrls.push("http://localhost:3000/")
206208
}
207209
// add the web client
208210
const userPoolWebClient = userPool.addClient("WebClient", {
@@ -221,7 +223,8 @@ export class Cognito extends Construct {
221223
],
222224
callbackUrls: callbackUrls,
223225
logoutUrls: logoutUrls
224-
}})
226+
}
227+
})
225228

226229
// ensure dependencies are set correctly so items are created in the correct order
227230
userPoolWebClient.node.addDependency(primaryPoolIdentityProvider)
Lines changed: 134 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import "@testing-library/jest-dom";
2-
import { render, screen, waitFor } from "@testing-library/react";
3-
import React from "react";
4-
import SelectYourRolePage from "@/app/selectyourrole/page";
5-
import { AuthContext } from "@/context/AuthProvider";
1+
import "@testing-library/jest-dom"
2+
import {render, screen, waitFor} from "@testing-library/react"
3+
import {useRouter} from 'next/navigation'
4+
import React from "react"
5+
import SelectYourRolePage from "@/app/selectyourrole/page"
6+
import {AuthContext} from "@/context/AuthProvider"
67

78
// Mock the card strings, so we have known text for the tests
89

@@ -34,18 +35,26 @@ jest.mock("@/constants/ui-strings/CardStrings", () => {
3435
loadingMessage: "Loading...",
3536
}
3637

37-
return { SELECT_YOUR_ROLE_PAGE_TEXT };
38-
});
38+
return {SELECT_YOUR_ROLE_PAGE_TEXT}
39+
})
40+
41+
// Define mockJWT
42+
const mockJWT = {
43+
token: 'mock-token',
44+
payload: {
45+
exp: Math.floor(Date.now() / 1000) + 3600, // Expires in 1 hour
46+
},
47+
}
3948

4049
// Mock `next/navigation` to prevent errors during component rendering in test
4150
jest.mock("next/navigation", () => ({
4251
usePathname: jest.fn(),
4352
useRouter: jest.fn(),
44-
}));
53+
}))
4554

4655
// Create a global mock for `fetch` to simulate API requests
47-
const mockFetch = jest.fn();
48-
global.fetch = mockFetch;
56+
const mockFetch = jest.fn()
57+
global.fetch = mockFetch
4958

5059
// Default mock values for the `AuthContext` to simulate authentication state
5160
const defaultAuthContext = {
@@ -56,82 +65,82 @@ const defaultAuthContext = {
5665
accessToken: null, // No access token available
5766
cognitoSignIn: jest.fn(), // Mock Cognito sign-in function
5867
cognitoSignOut: jest.fn(), // Mock Cognito sign-out function
59-
};
68+
}
6069

6170
// Utility function to render the component with custom AuthContext overrides
6271
const renderWithAuth = (authOverrides = {}) => {
63-
const authValue = { ...defaultAuthContext, ...authOverrides };
72+
const authValue = {...defaultAuthContext, ...authOverrides}
6473
return render(
6574
<AuthContext.Provider value={authValue}>
6675
<SelectYourRolePage />
6776
</AuthContext.Provider>
68-
);
69-
};
77+
)
78+
}
7079

71-
import { SELECT_YOUR_ROLE_PAGE_TEXT } from "@/constants/ui-strings/CardStrings";
80+
import {SELECT_YOUR_ROLE_PAGE_TEXT} from "@/constants/ui-strings/CardStrings"
7281

7382
describe("SelectYourRolePage", () => {
7483
// Clear all mock calls before each test to avoid state leaks
7584
beforeEach(() => {
76-
jest.clearAllMocks();
77-
});
85+
jest.clearAllMocks()
86+
})
7887

7988
it("renders loading state when signed in but fetch hasn't resolved yet", async () => {
8089
// Mock fetch to hang indefinitely, simulating a pending request
81-
mockFetch.mockImplementation(() => new Promise(() => {}));
90+
mockFetch.mockImplementation(() => new Promise(() => {}))
8291

8392
// Render the page with user signed in
84-
renderWithAuth({ isSignedIn: true, idToken: "mock-id-token" });
93+
renderWithAuth({isSignedIn: true, idToken: "mock-id-token"})
8594

8695
// Verify that the loading text appears
87-
const loadingText = screen.getByText(SELECT_YOUR_ROLE_PAGE_TEXT.loadingMessage);
88-
expect(loadingText).toBeInTheDocument();
89-
});
96+
const loadingText = screen.getByText(SELECT_YOUR_ROLE_PAGE_TEXT.loadingMessage)
97+
expect(loadingText).toBeInTheDocument()
98+
})
9099

91100
it("renders error summary if fetch returns non-200 status", async () => {
92101
// Mock fetch to return a 500 status code (server error)
93-
mockFetch.mockResolvedValue({ status: 500 });
102+
mockFetch.mockResolvedValue({status: 500})
94103

95104
// Render the page with user signed in
96-
renderWithAuth({ isSignedIn: true, idToken: "mock-id-token" });
105+
renderWithAuth({isSignedIn: true, idToken: "mock-id-token"})
97106

98107
// Wait for the error message to appear
99108
await waitFor(() => {
100109
// Check for error summary heading
101110
const errorHeading = screen.getByRole("heading", {
102111
name: SELECT_YOUR_ROLE_PAGE_TEXT.errorDuringRoleSelection,
103-
});
104-
expect(errorHeading).toBeInTheDocument();
112+
})
113+
expect(errorHeading).toBeInTheDocument()
105114

106115
// Check for specific error text
107-
const errorItem = screen.getByText("Failed to fetch CPT user info");
108-
expect(errorItem).toBeInTheDocument();
109-
});
110-
});
116+
const errorItem = screen.getByText("Failed to fetch CPT user info")
117+
expect(errorItem).toBeInTheDocument()
118+
})
119+
})
111120

112121
it("renders error summary if fetch returns 200 but no userInfo is present", async () => {
113122
// Mock fetch to return 200 OK but with an empty JSON body
114123
mockFetch.mockResolvedValue({
115124
status: 200,
116125
json: async () => ({}), // No `userInfo` key in response
117-
});
126+
})
118127

119128
// Render the page with user signed in
120-
renderWithAuth({ isSignedIn: true, idToken: "mock-id-token" });
129+
renderWithAuth({isSignedIn: true, idToken: "mock-id-token"})
121130

122131
// Wait for the error message to appear
123132
await waitFor(() => {
124133
// Check for error summary heading
125134
const errorHeading = screen.getByRole("heading", {
126135
name: SELECT_YOUR_ROLE_PAGE_TEXT.errorDuringRoleSelection,
127-
});
128-
expect(errorHeading).toBeInTheDocument();
136+
})
137+
expect(errorHeading).toBeInTheDocument()
129138

130139
// Check for specific error text
131-
const errorItem = screen.getByText("Failed to fetch CPT user info");
132-
expect(errorItem).toBeInTheDocument();
133-
});
134-
});
140+
const errorItem = screen.getByText("Failed to fetch CPT user info")
141+
expect(errorItem).toBeInTheDocument()
142+
})
143+
})
135144

136145
it("renders the page content when valid userInfo is returned", async () => {
137146
// Mock user data to simulate valid API response
@@ -152,54 +161,115 @@ describe("SelectYourRolePage", () => {
152161
site_address: "2 Fake Street",
153162
},
154163
],
155-
};
164+
}
156165

157166
// Mock fetch to return 200 OK with valid userInfo
158167
mockFetch.mockResolvedValue({
159168
status: 200,
160-
json: async () => ({ userInfo: mockUserInfo }),
161-
});
169+
json: async () => ({userInfo: mockUserInfo}),
170+
})
162171

163172
// Render the page with user signed in
164-
renderWithAuth({ isSignedIn: true, idToken: "mock-id-token" });
173+
renderWithAuth({isSignedIn: true, idToken: "mock-id-token"})
165174

166175
// Wait for the main content to load
167176
await waitFor(() => {
168177
// Check for the page heading
169-
const heading = screen.getByRole("heading", { level: 1 });
170-
expect(heading).toHaveTextContent(SELECT_YOUR_ROLE_PAGE_TEXT.title);
171-
});
178+
const heading = screen.getByRole("heading", {level: 1})
179+
expect(heading).toHaveTextContent(SELECT_YOUR_ROLE_PAGE_TEXT.title)
180+
})
172181

173182
// Verify the page caption
174-
const caption = screen.getByText(SELECT_YOUR_ROLE_PAGE_TEXT.caption);
175-
expect(caption).toBeInTheDocument();
183+
const caption = screen.getByText(SELECT_YOUR_ROLE_PAGE_TEXT.caption)
184+
expect(caption).toBeInTheDocument()
176185

177186
// Verify the "Roles without access" section (expander)
178-
const expanderText = SELECT_YOUR_ROLE_PAGE_TEXT.roles_without_access_table_title;
179-
const expander = screen.getByText(expanderText);
180-
expect(expander).toBeInTheDocument();
187+
const expanderText = SELECT_YOUR_ROLE_PAGE_TEXT.roles_without_access_table_title
188+
const expander = screen.getByText(expanderText)
189+
expect(expander).toBeInTheDocument()
181190

182191
// Check for the table data in "Roles without access"
183-
const tableOrg = screen.getByText(/Tech Org \(ODS: ORG456\)/i);
184-
expect(tableOrg).toBeInTheDocument();
185-
const tableRole = screen.getByText("Technician");
186-
expect(tableRole).toBeInTheDocument();
187-
});
192+
const tableOrg = screen.getByText(/Tech Org \(ODS: ORG456\)/i)
193+
expect(tableOrg).toBeInTheDocument()
194+
const tableRole = screen.getByText("Technician")
195+
expect(tableRole).toBeInTheDocument()
196+
})
188197

189198
it("renders error summary when not signed in", async () => {
190199
// Render the page with `isSignedIn` set to false
191-
renderWithAuth({ isSignedIn: false, error: "Missing access or ID token" });
200+
renderWithAuth({isSignedIn: false, error: "Missing access or ID token"})
192201

193202
// Wait for the error message to appear
194203
await waitFor(() => {
195204
// Check for error summary heading
196205
const errorHeading = screen.getByRole("heading", {
197206
name: SELECT_YOUR_ROLE_PAGE_TEXT.errorDuringRoleSelection,
198-
});
199-
expect(errorHeading).toBeInTheDocument();
200-
201-
const errorItem = screen.getByText("Missing access or ID token");
202-
expect(errorItem).toBeInTheDocument();
203-
});
204-
});
205-
});
207+
})
208+
expect(errorHeading).toBeInTheDocument()
209+
210+
const errorItem = screen.getByText("Missing access or ID token")
211+
expect(errorItem).toBeInTheDocument()
212+
})
213+
})
214+
215+
it("redirects to searchforaprescription when there is one role with access and no roles without access", async () => {
216+
const mockUserInfo = {
217+
roles_with_access: [
218+
{
219+
role_name: "Pharmacist",
220+
org_name: "Test Pharmacy Org",
221+
org_code: "ORG123",
222+
site_address: "1 Fake Street",
223+
},
224+
],
225+
roles_without_access: [],
226+
}
227+
228+
// Mock fetch response
229+
global.fetch = jest.fn(() =>
230+
Promise.resolve({
231+
ok: true,
232+
status: 200,
233+
statusText: "OK",
234+
headers: new Headers(),
235+
redirected: false,
236+
type: "basic",
237+
url: "",
238+
clone: jest.fn(),
239+
body: null,
240+
bodyUsed: false,
241+
text: jest.fn(),
242+
json: jest.fn(() => Promise.resolve({userInfo: mockUserInfo})), // Simulate JSON body
243+
})
244+
) as jest.Mock
245+
246+
// Mock useRouter's push function
247+
const mockPush = jest.fn();
248+
(useRouter as jest.Mock).mockReturnValue({
249+
push: mockPush,
250+
})
251+
252+
// Mock AuthContext
253+
const mockAuthContext = {
254+
isSignedIn: true,
255+
idToken: mockJWT,
256+
accessToken: mockJWT,
257+
error: null,
258+
user: null,
259+
cognitoSignIn: jest.fn(),
260+
cognitoSignOut: jest.fn(),
261+
}
262+
263+
// Render the component
264+
render(
265+
<AuthContext.Provider value={mockAuthContext}>
266+
<SelectYourRolePage />
267+
</AuthContext.Provider>
268+
)
269+
270+
// Wait for redirection
271+
await waitFor(() => {
272+
expect(mockPush).toHaveBeenCalledWith("/searchforaprescription")
273+
})
274+
})
275+
})
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use client'
2+
3+
import React from 'react'
4+
import {Container, Row, Col} from 'nhsuk-react-components'
5+
6+
export default function SearchForAPrescriptionPage() {
7+
return (
8+
<main id="main-content" className="nhsuk-main-wrapper">
9+
<Container>
10+
<Row>
11+
<Col width="full">
12+
<h1 className="nhsuk-heading-xl" data-testid="search_prescription_heading">
13+
Search for a prescription
14+
</h1>
15+
</Col>
16+
</Row>
17+
</Container>
18+
</main>
19+
)
20+
}

0 commit comments

Comments
 (0)