Skip to content

Commit 44d972f

Browse files
CCM-11453: message plan list page (#697)
Co-authored-by: alex.nuttall1 <[email protected]>
1 parent 3bd2401 commit 44d972f

File tree

48 files changed

+1861
-234
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1861
-234
lines changed

.github/workflows/stage-2-test.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ jobs:
7474
runs-on: ubuntu-latest
7575
timeout-minutes: 5
7676
needs: [discover-workspaces]
77+
env:
78+
TZ: Europe/London
7779
strategy:
7880
fail-fast: false
7981
matrix:

data-migration/user-transfer/src/migrate.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export async function migrate(params: MigrateParameters) {
126126
const filename = `${name}-${params.dryRun ? 'dryrun' : 'run'}${ext}`;
127127
const data = JSON.stringify(output);
128128

129+
// eslint-disable-next-line security/detect-non-literal-fs-filename
129130
writeFileSync(path.join(dir, filename), data);
130131

131132
await writeRemote(

data-migration/user-transfer/src/plan.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export async function plan(params: PlanParameters) {
7171

7272
await writeRemote(path, data, templatesS3BackupBucketName);
7373

74+
// eslint-disable-next-line security/detect-non-literal-fs-filename
7475
writeFileSync(`./migrations/${filename}.json`, data);
7576

7677
print(`Results written to ${filename}.json`);
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/**
2+
* @jest-environment node
3+
*/
4+
import { getRoutingConfigs, countRoutingConfigs } from '@utils/message-plans';
5+
import {
6+
type MessagePlansProps,
7+
MessagePlans,
8+
} from '@molecules/MessagePlans/MessagePlans';
9+
import { serverIsFeatureEnabled } from '@utils/server-features';
10+
import MessagePlansPage, { generateMetadata } from '@app/message-plans/page';
11+
import { ReactElement } from 'react';
12+
import { RoutingConfig } from 'nhs-notify-backend-client';
13+
14+
jest.mock('@utils/message-plans');
15+
jest.mock('@utils/server-features');
16+
17+
const countRoutingConfigsMock = jest.mocked(countRoutingConfigs);
18+
const serverIsFeatureEnabledMock = jest.mocked(serverIsFeatureEnabled);
19+
const getRoutingConfigsMock = jest.mocked(getRoutingConfigs);
20+
21+
const buildRoutingConfig = (rc: Partial<RoutingConfig>): RoutingConfig => ({
22+
campaignId: 'abc',
23+
cascade: [],
24+
cascadeGroupOverrides: [],
25+
clientId: 'client-a',
26+
createdAt: '2025-09-09T10:00:00Z',
27+
status: 'DRAFT',
28+
id: '',
29+
name: '',
30+
updatedAt: '',
31+
...rc,
32+
});
33+
34+
describe('MessagePlansPage', () => {
35+
beforeEach(() => {
36+
jest.clearAllMocks();
37+
serverIsFeatureEnabledMock.mockResolvedValueOnce(true);
38+
});
39+
40+
test('page metadata', async () => {
41+
const metadata = await generateMetadata();
42+
43+
expect(metadata).toEqual({
44+
title: 'Message plans - NHS Notify',
45+
});
46+
});
47+
48+
test('renders the page with drafts and production routing plans', async () => {
49+
getRoutingConfigsMock.mockResolvedValueOnce([
50+
buildRoutingConfig({
51+
id: '1',
52+
name: 'Draft A',
53+
updatedAt: '2025-09-10T10:00:00Z',
54+
status: 'DRAFT',
55+
}),
56+
buildRoutingConfig({
57+
id: '2',
58+
name: 'Prod X',
59+
updatedAt: '2025-09-09T11:00:00Z',
60+
status: 'COMPLETED',
61+
}),
62+
buildRoutingConfig({
63+
id: '3',
64+
name: 'Prod Y',
65+
updatedAt: '2025-09-08T12:00:00Z',
66+
status: 'COMPLETED',
67+
}),
68+
]);
69+
70+
countRoutingConfigsMock.mockResolvedValueOnce(1).mockResolvedValueOnce(2);
71+
72+
const page = (await MessagePlansPage()) as ReactElement<
73+
MessagePlansProps,
74+
typeof MessagePlans
75+
>;
76+
77+
expect(getRoutingConfigsMock).toHaveBeenCalledTimes(1);
78+
79+
expect(countRoutingConfigsMock).toHaveBeenNthCalledWith(1, 'DRAFT');
80+
81+
expect(countRoutingConfigsMock).toHaveBeenNthCalledWith(2, 'COMPLETED');
82+
83+
expect(page.props.draft).toEqual({
84+
plans: [
85+
{
86+
id: '1',
87+
name: 'Draft A',
88+
lastUpdated: '2025-09-10T10:00:00Z',
89+
status: 'DRAFT',
90+
},
91+
],
92+
count: 1,
93+
});
94+
95+
expect(page.props.production).toEqual({
96+
plans: [
97+
{
98+
id: '2',
99+
name: 'Prod X',
100+
lastUpdated: '2025-09-09T11:00:00Z',
101+
status: 'COMPLETED',
102+
},
103+
{
104+
id: '3',
105+
name: 'Prod Y',
106+
lastUpdated: '2025-09-08T12:00:00Z',
107+
status: 'COMPLETED',
108+
},
109+
],
110+
count: 2,
111+
});
112+
});
113+
});

frontend/src/__tests__/components/forms/EmailTemplateForm/server-action.test.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const createTemplateMock = jest.mocked(createTemplate);
1414
const redirectMock = jest.mocked(redirect);
1515

1616
const initialState: EmailTemplate = {
17-
id: 'template-id',
17+
id: '89cc73bb-6d61-4b8b-a728-b5e40f0751fd',
1818
templateType: 'EMAIL',
1919
templateStatus: 'NOT_YET_SUBMITTED',
2020
name: 'name',
@@ -109,25 +109,52 @@ describe('CreateEmailTemplate server actions', () => {
109109
})
110110
);
111111

112-
expect(saveTemplateMock).toHaveBeenCalledWith({
112+
expect(saveTemplateMock).toHaveBeenCalledWith(initialState.id, {
113113
...initialState,
114114
name: 'template-name',
115115
subject: 'template-subject-line',
116116
message: 'template-message',
117117
});
118118

119119
expect(redirectMock).toHaveBeenCalledWith(
120-
'/preview-email-template/template-id?from=edit',
120+
`/preview-email-template/${initialState.id}?from=edit`,
121121
'push'
122122
);
123123
});
124124

125+
test('redirects to invalid-template if the ID in formState is not a uuid', async () => {
126+
const badIdState = {
127+
...initialState,
128+
id: 'non-uuid',
129+
name: 'template-name',
130+
subject: 'template-subject-line',
131+
message: 'template-message',
132+
createdAt: '2025-01-13T10:19:25.579Z',
133+
updatedAt: '2025-01-13T10:19:25.579Z',
134+
};
135+
136+
await processFormActions(
137+
badIdState,
138+
getMockFormData({
139+
emailTemplateName: 'template-name',
140+
emailTemplateSubjectLine: 'template-subject-line',
141+
emailTemplateMessage: 'template-message',
142+
})
143+
);
144+
145+
expect(saveTemplateMock).not.toHaveBeenCalled();
146+
147+
expect(redirectMock).toHaveBeenCalledWith('/invalid-template', 'replace');
148+
});
149+
125150
test('should create the template and redirect', async () => {
126151
const { id: _, ...initialDraftState } = initialState; // eslint-disable-line sonarjs/no-unused-vars
127152

153+
const generatedId = '06c6fb58-e749-4ee4-8343-57d7f7ecfe1f';
154+
128155
createTemplateMock.mockResolvedValue({
129156
...initialDraftState,
130-
id: 'new-template-id',
157+
id: generatedId,
131158
name: 'template-name',
132159
subject: 'template-subject-line',
133160
message: 'template-message',
@@ -152,7 +179,7 @@ describe('CreateEmailTemplate server actions', () => {
152179
});
153180

154181
expect(redirectMock).toHaveBeenCalledWith(
155-
'/preview-email-template/new-template-id?from=edit',
182+
`/preview-email-template/${generatedId}?from=edit`,
156183
'push'
157184
);
158185
});

frontend/src/__tests__/components/forms/NhsAppTemplateForm/server-action.test.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getMockFormData } from '@testhelpers';
22
import { saveTemplate, createTemplate } from '@utils/form-actions';
3-
import {
3+
import type {
44
NHSAppTemplate,
55
CreateUpdateNHSAppTemplate,
66
} from 'nhs-notify-web-template-management-utils';
@@ -23,7 +23,7 @@ const initialState: CreateUpdateNHSAppTemplate = {
2323

2424
const savedState: NHSAppTemplate = {
2525
...initialState,
26-
id: 'template-id',
26+
id: 'a28c9e75-d6c9-4efe-879e-e082238938cf',
2727
templateStatus: 'NOT_YET_SUBMITTED',
2828
createdAt: '2025-01-13T10:19:25.579Z',
2929
updatedAt: '2025-01-13T10:19:25.579Z',
@@ -130,18 +130,37 @@ describe('CreateNHSAppTemplate server actions', () => {
130130
})
131131
);
132132

133-
expect(saveTemplateMock).toHaveBeenCalledWith({
133+
expect(saveTemplateMock).toHaveBeenCalledWith(savedState.id, {
134134
...savedState,
135135
name: 'template-name',
136136
message: 'template-message',
137137
});
138138

139139
expect(redirectMock).toHaveBeenCalledWith(
140-
`/preview-nhs-app-template/template-id?from=edit`,
140+
`/preview-nhs-app-template/${savedState.id}?from=edit`,
141141
'push'
142142
);
143143
});
144144

145+
test('redirects to invalid-template if the ID in formState is not a uuid', async () => {
146+
const badIdState = {
147+
...savedState,
148+
id: 'no-uuid',
149+
};
150+
151+
await processFormActions(
152+
badIdState,
153+
getMockFormData({
154+
nhsAppTemplateName: 'template-name',
155+
nhsAppTemplateMessage: 'template-message',
156+
})
157+
);
158+
159+
expect(saveTemplateMock).not.toHaveBeenCalled();
160+
161+
expect(redirectMock).toHaveBeenCalledWith('/invalid-template', 'replace');
162+
});
163+
145164
test('should save a template that contains a url with angle brackets, if they are url encoded', async () => {
146165
saveTemplateMock.mockResolvedValue({
147166
...savedState,
@@ -159,15 +178,15 @@ describe('CreateNHSAppTemplate server actions', () => {
159178
})
160179
);
161180

162-
expect(saveTemplateMock).toHaveBeenCalledWith({
181+
expect(saveTemplateMock).toHaveBeenCalledWith(savedState.id, {
163182
...savedState,
164183
name: 'template-name',
165184
message:
166185
'**a message linking to [example.com](https://www.example.com/withparams?param1=%3C%3E)**',
167186
});
168187

169188
expect(redirectMock).toHaveBeenCalledWith(
170-
`/preview-nhs-app-template/template-id?from=edit`,
189+
`/preview-nhs-app-template/${savedState.id}?from=edit`,
171190
'push'
172191
);
173192
});
@@ -190,7 +209,7 @@ describe('CreateNHSAppTemplate server actions', () => {
190209
});
191210

192211
expect(redirectMock).toHaveBeenCalledWith(
193-
'/preview-nhs-app-template/template-id?from=edit',
212+
`/preview-nhs-app-template/${savedState.id}?from=edit`,
194213
'push'
195214
);
196215
});

0 commit comments

Comments
 (0)