Skip to content

Commit 7f2914a

Browse files
Update portal frontend to support consent management api v0.3.0
1 parent 7c0d5bd commit 7f2914a

11 files changed

Lines changed: 277 additions & 80 deletions

File tree

portal/frontend/src/__tests__/ConsentApprovalApi.test.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
*/
1818

1919
import { afterEach, describe, expect, it, vi } from 'vitest'
20-
import { approveMyConsent } from '../features/consent-registry/api/consentsApi'
20+
import { approveMyConsent, revokeMyConsent } from '../features/consent-registry/api/consentsApi'
2121
import { apiRequest } from '../utils/apiClient'
2222

2323
const fetchMock = vi.fn()
@@ -36,7 +36,14 @@ describe('approveMyConsent', () => {
3636
json: async () => ({}),
3737
})
3838

39-
const selectedOptionalElements = [{ purposeName: 'profile_access', elementName: 'last_name' }]
39+
const selectedOptionalElements = [
40+
{
41+
purposeId: 'purpose-profile',
42+
purposeVersion: 'v2',
43+
elementId: 'element-last-name',
44+
elementVersion: 'v3',
45+
},
46+
]
4047

4148
await approveMyConsent('consent/123?draft', selectedOptionalElements)
4249

@@ -53,6 +60,25 @@ describe('approveMyConsent', () => {
5360
expect(requestHeaders.get('Accept')).toBe('application/json')
5461
expect(requestHeaders.get('Content-Type')).toBe('application/json')
5562
})
63+
64+
it('revokes consent with POST', async () => {
65+
vi.stubGlobal('fetch', fetchMock)
66+
fetchMock.mockResolvedValue({
67+
ok: true,
68+
status: 200,
69+
json: async () => ({}),
70+
})
71+
72+
await revokeMyConsent('consent-123')
73+
74+
expect(fetchMock).toHaveBeenCalledTimes(1)
75+
const [, requestInit] = fetchMock.mock.calls[0] ?? []
76+
expect(requestInit).toMatchObject({
77+
method: 'POST',
78+
credentials: 'include',
79+
body: JSON.stringify({}),
80+
})
81+
})
5682
})
5783

5884
describe('apiRequest', () => {

portal/frontend/src/__tests__/ConsentRegistryModals.test.tsx

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,38 @@ describe('consent registry dialogs', () => {
7070
loading={false}
7171
purposes={[
7272
{
73+
purposeId: 'purpose-accounts',
7374
name: 'Accounts',
75+
version: 'v2',
76+
displayName: 'Accounts',
7477
elements: [
75-
{ name: 'Account Number', isUserApproved: true, isMandatory: true },
76-
{ name: 'Transaction History', isUserApproved: true, isMandatory: false },
77-
{ name: 'Marketing Messages', isUserApproved: false, isMandatory: false },
78+
{
79+
elementId: 'element-account-number',
80+
name: 'account_number',
81+
namespace: 'accounts',
82+
version: 'v1',
83+
displayName: 'Account Number',
84+
approved: true,
85+
mandatory: true,
86+
},
87+
{
88+
elementId: 'element-transactions',
89+
name: 'transaction_history',
90+
namespace: 'accounts',
91+
version: 'v3',
92+
displayName: 'Transaction History',
93+
approved: true,
94+
mandatory: false,
95+
},
96+
{
97+
elementId: 'element-marketing',
98+
name: 'marketing_messages',
99+
namespace: 'accounts',
100+
version: 'v2',
101+
displayName: 'Marketing Messages',
102+
approved: false,
103+
mandatory: false,
104+
},
78105
],
79106
},
80107
]}
@@ -89,8 +116,18 @@ describe('consent registry dialogs', () => {
89116
fireEvent.click(screen.getByRole('button', { name: /approve & continue/i }))
90117

91118
expect(onConfirm).toHaveBeenCalledWith([
92-
{ purposeName: 'Accounts', elementName: 'Transaction History' },
93-
{ purposeName: 'Accounts', elementName: 'Marketing Messages' },
119+
{
120+
purposeId: 'purpose-accounts',
121+
purposeVersion: 'v2',
122+
elementId: 'element-transactions',
123+
elementVersion: 'v3',
124+
},
125+
{
126+
purposeId: 'purpose-accounts',
127+
purposeVersion: 'v2',
128+
elementId: 'element-marketing',
129+
elementVersion: 'v2',
130+
},
94131
])
95132
})
96133

portal/frontend/src/__tests__/ConsentRegistryPage.test.tsx

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,18 @@ describe('ConsentRegistryPage', () => {
8888
data: [
8989
{
9090
id: 'CON/8291?draft',
91-
clientId: 'Tesco_Bank_v1',
91+
groupId: 'tesco-bank',
9292
type: 'Accounts',
9393
status: 'ACTIVE',
9494
createdTime: 1702800000,
9595
updatedTime: 1702800000,
96-
validityTime: 0,
96+
expirationTime: 0,
9797
purposes: [
9898
{
99-
name: 'Marketing',
99+
purposeId: 'purpose-marketing',
100+
name: 'marketing_preferences',
101+
version: 'v1',
102+
displayName: 'Marketing',
100103
elements: [],
101104
},
102105
],
@@ -118,7 +121,7 @@ describe('ConsentRegistryPage', () => {
118121
expect(await screen.findByRole('heading', { name: 'All Consents' })).toBeInTheDocument()
119122
expect(screen.getByLabelText('Consent filters')).toBeInTheDocument()
120123
expect(screen.getByRole('button', { name: 'Clear all filters' })).toBeInTheDocument()
121-
expect(await screen.findByText('Client: Tesco_Bank_v1')).toBeInTheDocument()
124+
expect(await screen.findByText('Group ID: tesco-bank')).toBeInTheDocument()
122125
expect(screen.getByRole('table', { name: 'Consent registry table' })).toBeInTheDocument()
123126
expect(await screen.findByText('Marketing')).toBeInTheDocument()
124127
expect(await screen.findByText('Not applicable')).toBeInTheDocument()
@@ -149,6 +152,50 @@ describe('ConsentRegistryPage', () => {
149152
expect(screen.queryByRole('table', { name: 'Consent registry table' })).not.toBeInTheDocument()
150153
})
151154

155+
it('shows the empty state for an empty v0.3 response', async () => {
156+
vi.stubGlobal('fetch', fetchMock)
157+
fetchMock.mockResolvedValue({
158+
ok: true,
159+
status: 200,
160+
json: async () => ({
161+
data: [],
162+
metadata: { total: 0, offset: 0, count: 0, limit: 10 },
163+
}),
164+
})
165+
166+
renderConsentRegistryPage(createQueryClient())
167+
168+
expect(
169+
await screen.findByText('No consents found for the selected filters.'),
170+
).toBeInTheDocument()
171+
})
172+
173+
it('shows the error state when a consent response has an unsupported status', async () => {
174+
vi.stubGlobal('fetch', fetchMock)
175+
fetchMock.mockResolvedValue({
176+
ok: true,
177+
status: 200,
178+
json: async () => ({
179+
data: [
180+
{
181+
id: 'CON-INVALID',
182+
groupId: 'sample-group',
183+
type: 'Accounts',
184+
status: 'UNKNOWN',
185+
createdTime: 1702800000000,
186+
updatedTime: 1702800000000,
187+
purposes: [],
188+
},
189+
],
190+
metadata: { total: 1, offset: 0, count: 1, limit: 10 },
191+
}),
192+
})
193+
194+
renderConsentRegistryPage(createQueryClient())
195+
196+
expect(await screen.findByText('Unable to load consents right now.')).toBeInTheDocument()
197+
})
198+
152199
it('does not render approve action for rejected consents', async () => {
153200
vi.stubGlobal('fetch', fetchMock)
154201
fetchMock.mockResolvedValue({
@@ -158,14 +205,17 @@ describe('ConsentRegistryPage', () => {
158205
data: [
159206
{
160207
id: 'CON-9123',
161-
clientId: 'Sample_Client',
208+
groupId: 'sample-group',
162209
type: 'Accounts',
163210
status: 'REJECTED',
164211
createdTime: 1702800000,
165212
updatedTime: 1702800000,
166213
purposes: [
167214
{
168-
name: 'Marketing',
215+
purposeId: 'purpose-marketing',
216+
name: 'marketing_preferences',
217+
version: 'v1',
218+
displayName: 'Marketing',
169219
elements: [],
170220
},
171221
],
@@ -217,4 +267,34 @@ describe('ConsentRegistryPage', () => {
217267
expect(String(requestUrl)).toContain('limit=25')
218268
expect(String(requestUrl)).toContain('offset=25')
219269
})
270+
271+
it('maps URL filters to v0.3 consent search parameters', async () => {
272+
vi.stubGlobal('fetch', fetchMock)
273+
fetchMock.mockResolvedValue({
274+
ok: true,
275+
status: 200,
276+
json: async () => ({
277+
data: [],
278+
metadata: { total: 0, offset: 0, count: 0, limit: 10 },
279+
}),
280+
})
281+
282+
renderConsentRegistryPage(
283+
createQueryClient(),
284+
'/consents?status=Pending&startDate=2026-01-01&endDate=2026-01-02&consentType=Accounts',
285+
)
286+
287+
await waitFor(() => {
288+
expect(fetchMock).toHaveBeenCalled()
289+
})
290+
291+
const [requestUrl] = fetchMock.mock.calls[0] ?? []
292+
const url = new URL(String(requestUrl))
293+
expect(url.searchParams.get('consentStatuses')).toBe('CREATED')
294+
expect(url.searchParams.get('consentTypes')).toBe('Accounts')
295+
expect(Number(url.searchParams.get('fromTime'))).toBeGreaterThan(1_000_000_000_000)
296+
expect(Number(url.searchParams.get('toTime'))).toBeGreaterThan(
297+
Number(url.searchParams.get('fromTime')),
298+
)
299+
})
220300
})

portal/frontend/src/features/consent-registry/api/consentsApi.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export async function approveMyConsent(
6161

6262
export async function revokeMyConsent(consentID: string): Promise<unknown> {
6363
return apiRequest<unknown>(`/me/consents/${encodeURIComponent(consentID)}/revoke`, {
64-
method: 'PUT',
64+
method: 'POST',
6565
headers: {
6666
'Content-Type': 'application/json',
6767
},

0 commit comments

Comments
 (0)