Skip to content

Commit 6ec04b5

Browse files
authored
Merge pull request #127 from NHSDigital/feature/CCM-4893_update-email
CCM-4893: update email to show subject line
2 parents 0088909 + 3fcd8cb commit 6ec04b5

File tree

52 files changed

+4176
-233
lines changed

Some content is hidden

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

52 files changed

+4176
-233
lines changed

amplify/__tests__/functions/send-email/handler.test.ts

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,16 @@ type HandlerCallbackType = Parameters<
1515
Schema['sendEmail']['functionHandler']
1616
>[2];
1717

18+
const notifyDomainName = process.env.NOTIFY_DOMAIN_NAME;
19+
1820
beforeAll(() => {
1921
jest.useFakeTimers();
2022
jest.setSystemTime(new Date('2022-01-01 10:00'));
23+
process.env.NOTIFY_DOMAIN_NAME = 'test.notify.nhs.uk';
24+
});
25+
26+
afterAll(() => {
27+
process.env.NOTIFY_DOMAIN_NAME = notifyDomainName;
2128
});
2229

2330
test('sends email', async () => {
@@ -49,32 +56,18 @@ test('sends email', async () => {
4956

5057
const rawMimeMessage = sesCallInput.RawMessage?.Data?.toString();
5158

52-
const messageId = rawMimeMessage?.match(
53-
/Message-ID: <([\dA-z]+)@undefined>/
54-
)?.[1];
55-
const messageBoundary = rawMimeMessage?.match(/boundary=([\dA-z]+)/)?.[1];
59+
const messageId = rawMimeMessage?.match(/Message-ID: <([^@]+)@/)?.[1];
5660

5761
const expectedMessage = `Date: Sat, 01 Jan 2022 10:00:00 +0000
58-
From: =?utf-8?B?TkhTIE5vdGlmeQ==?= <no-reply@undefined>
62+
From: =?utf-8?B?TkhTIE5vdGlmeQ==?= <no-reply@test.notify.nhs.uk>
5963
To: <recipient-email>
60-
Message-ID: <${messageId}@undefined>
64+
Message-ID: <${messageId}@test.notify.nhs.uk>
6165
Subject: =?utf-8?B?VGVtcGxhdGUgc3VibWl0dGVkIC0gdGVtcGxhdGUtbmFtZQ==?=
6266
MIME-Version: 1.0
63-
Content-Type: multipart/mixed; boundary=${messageBoundary}
64-
65-
--${messageBoundary}
6667
Content-Type: text/html; charset=UTF-8
6768
Content-Transfer-Encoding: 7bit
6869
69-
${emailTemplate('template-id', 'template-name', 'template-message')}
70-
71-
--${messageBoundary}
72-
Content-Type: text/markdown; name="template-content.md"
73-
Content-Transfer-Encoding: base64
74-
Content-Disposition: attachment; filename="template-content.md"
75-
76-
dGVtcGxhdGUtbWVzc2FnZQ==
77-
--${messageBoundary}--`;
70+
${emailTemplate('template-id', 'template-name', 'template-message')}`;
7871

7972
expect(rawMimeMessage?.toString()).toEqual(expectedMessage);
8073
});

amplify/data/resource.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ const SessionStorageModel = {
88
templateType: a.enum([...templateTypes, 'UNKNOWN']),
99
nhsAppTemplateName: a.string().required(),
1010
nhsAppTemplateMessage: a.string().required(),
11+
emailTemplateName: a.string(),
12+
emailTemplateSubjectLine: a.string(),
13+
emailTemplateMessage: a.string(),
1114
smsTemplateName: a.string(),
1215
smsTemplateMessage: a.string(),
1316
ttl: a.integer().required(),
@@ -19,6 +22,7 @@ const TemplateStorageModel = {
1922
type: a.enum(templateTypes),
2023
version: a.integer().required(),
2124
fields: a.customType({
25+
subjectLine: a.string(),
2226
content: a.string().required(),
2327
}),
2428
};
@@ -39,6 +43,7 @@ const schema = a.schema({
3943
templateId: a.string().required(),
4044
templateName: a.string().required(),
4145
templateMessage: a.string().required(),
46+
templateSubjectLine: a.string(),
4247
})
4348
.returns(a.string())
4449
.handler(a.handler.function(sendEmail))

amplify/functions/send-email/email-template.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,14 @@
209209
<p class="dataValueShort">{{templateId}}</p>
210210
</td>
211211
</tr>
212+
{{#if templateSubjectLine}}
213+
<tr>
214+
<td colspan="2">
215+
<p class="dataLabel">Subject line</p>
216+
<p class="dataValueShort">{{templateSubjectLine}}</p>
217+
</td>
218+
</tr>
219+
{{/if}}
212220
<tr>
213221
<td colspan="2">
214222
<p class="dataLabel">Template content</p>

amplify/functions/send-email/email-template.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ const encoder = (rawText: string) => encode(rawText).replaceAll('\n', '<br />');
88
export const emailTemplate = (
99
templateId: string,
1010
templateName: string,
11-
templateMessage: string
11+
templateMessage: string,
12+
templateSubjectLine?: string | null
1213
): string => {
1314
const parameters = {
1415
templateId,
1516
templateName,
1617
templateMessage: encoder(templateMessage),
18+
templateSubjectLine,
1719
};
1820
return htmlTemplate(parameters);
1921
};

amplify/functions/send-email/handler.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,13 @@ export const handler: Schema['sendEmail']['functionHandler'] = async (
1414
const client = new SESClient({ region: 'eu-west-2' });
1515

1616
const { senderEmail } = config();
17-
const { recipientEmail, templateId, templateName, templateMessage } =
18-
event.arguments;
17+
const {
18+
recipientEmail,
19+
templateId,
20+
templateName,
21+
templateMessage,
22+
templateSubjectLine,
23+
} = event.arguments;
1924

2025
logger.info(
2126
`Sending email for template ${templateName} with ID ${templateId}`
@@ -27,12 +32,12 @@ export const handler: Schema['sendEmail']['functionHandler'] = async (
2732
msg.setSubject(`Template submitted - ${templateName}`);
2833
msg.addMessage({
2934
contentType: 'text/html',
30-
data: emailTemplate(templateId, templateName, templateMessage),
31-
});
32-
msg.addAttachment({
33-
filename: 'template-content.md',
34-
contentType: 'text/markdown',
35-
data: Buffer.from(templateMessage).toString('base64'),
35+
data: emailTemplate(
36+
templateId,
37+
templateName,
38+
templateMessage,
39+
templateSubjectLine
40+
),
3641
});
3742

3843
const command = new SendRawEmailCommand({
Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`CreateEmailTemplatePage 1`] = `
4-
<DocumentFragment>
5-
<h2
6-
class="nhsuk-heading-l"
7-
data-testid="page-sub-heading"
8-
>
9-
Placeholder email page
10-
</h2>
11-
</DocumentFragment>
3+
exports[`CreateEmailTemplatePage should render CreateEmailTemplatePage component when session is found 1`] = `
4+
<CreateEmailTemplate
5+
initialState={
6+
{
7+
"id": "session-id",
8+
"nhsAppTemplateMessage": "",
9+
"nhsAppTemplateName": "",
10+
"templateType": "EMAIL",
11+
}
12+
}
13+
/>
1214
`;
Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,69 @@
11
import CreateEmailTemplatePage from '@app/create-email-template/[sessionId]/page';
2-
import { render } from '@testing-library/react';
2+
import { getSession } from '@utils/form-actions';
3+
import { Session, TemplateType } from '@utils/types';
4+
import { redirect } from 'next/navigation';
5+
import { CreateEmailTemplate } from '@forms/CreateEmailTemplate/CreateEmailTemplate';
36

4-
test('CreateEmailTemplatePage', async () => {
5-
const page = await CreateEmailTemplatePage();
6-
const container = render(page);
7+
jest.mock('@utils/amplify-utils', () => ({
8+
getAmplifyBackendClient: () => ({
9+
models: {
10+
SessionStorage: {
11+
update: () => ({ data: {} }),
12+
},
13+
},
14+
}),
15+
}));
16+
jest.mock('@utils/form-actions');
17+
jest.mock('next/navigation');
18+
jest.mock('@forms/CreateEmailTemplate/CreateEmailTemplate');
719

8-
expect(container.asFragment()).toMatchSnapshot();
20+
const getSessionMock = jest.mocked(getSession);
21+
const redirectMock = jest.mocked(redirect);
22+
const CreateEmailTemplateMock = jest.mocked(CreateEmailTemplate);
23+
24+
const initialState: Session = {
25+
id: 'session-id',
26+
templateType: TemplateType.EMAIL,
27+
nhsAppTemplateName: '',
28+
nhsAppTemplateMessage: '',
29+
};
30+
31+
describe('CreateEmailTemplatePage', () => {
32+
beforeEach(jest.resetAllMocks);
33+
34+
it('should redirect to invalid-session when no session is found', async () => {
35+
getSessionMock.mockResolvedValueOnce(undefined);
36+
37+
await CreateEmailTemplatePage({ params: { sessionId: 'session-id' } });
38+
39+
expect(getSessionMock).toHaveBeenCalledWith('session-id');
40+
41+
expect(redirectMock).toHaveBeenCalledWith('/invalid-session', 'replace');
42+
});
43+
44+
it('should redirect to invalid-session when sessions template type is not EMAIL', async () => {
45+
getSessionMock.mockResolvedValueOnce({
46+
...initialState,
47+
templateType: TemplateType.NHS_APP,
48+
});
49+
50+
await CreateEmailTemplatePage({ params: { sessionId: 'session-id' } });
51+
52+
expect(getSessionMock).toHaveBeenCalledWith('session-id');
53+
54+
expect(redirectMock).toHaveBeenCalledWith('/invalid-session', 'replace');
55+
});
56+
57+
it('should render CreateEmailTemplatePage component when session is found', async () => {
58+
getSessionMock.mockResolvedValueOnce(initialState);
59+
CreateEmailTemplateMock.mockImplementationOnce(() => <p>rendered</p>);
60+
61+
const page = await CreateEmailTemplatePage({
62+
params: { sessionId: 'session-id' },
63+
});
64+
65+
expect(getSessionMock).toHaveBeenCalledWith('session-id');
66+
67+
expect(page).toMatchSnapshot();
68+
});
969
});
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`EmailTemplateSubmitted 1`] = `
4+
<DocumentFragment>
5+
<div
6+
class="nhsuk-grid-row"
7+
>
8+
<div
9+
class="nhsuk-grid-column-two-thirds"
10+
>
11+
<div
12+
class="notify-confirmation-panel"
13+
>
14+
<h1
15+
class="nhsuk-heading-l nhsuk-u-margin-bottom-0"
16+
id="template-submitted"
17+
>
18+
Template submitted
19+
</h1>
20+
</div>
21+
<h2
22+
class="nhsuk-heading-xs nhsuk-u-margin-bottom-1"
23+
>
24+
Template name
25+
</h2>
26+
<p>
27+
template-name
28+
</p>
29+
<h2
30+
class="nhsuk-heading-xs nhsuk-u-margin-bottom-1"
31+
>
32+
Template ID
33+
</h2>
34+
<p>
35+
template-id
36+
</p>
37+
<a
38+
href="/create-template"
39+
>
40+
Create another template
41+
</a>
42+
<h2
43+
class="nhsuk-u-margin-top-5"
44+
>
45+
What you need to do next
46+
</h2>
47+
<p>
48+
You'll receive a confirmation email, which contains the template name and ID.
49+
</p>
50+
<h3>
51+
If you're currently onboarding
52+
</h3>
53+
<p>
54+
Tell your onboarding manager once you've submitted all your templates.
55+
</p>
56+
<h3>
57+
If you've already onboarded
58+
</h3>
59+
<p>
60+
<a
61+
href="https://nhsdigitallive.service-now.com/nhs_digital?id=sc_cat_item&sys_id=6208dbce1be759102eee65b9bd4bcbf5"
62+
>
63+
Raise a request with the service desk
64+
</a>
65+
once you've submitted all your templates.
66+
</p>
67+
</div>
68+
</div>
69+
</DocumentFragment>
70+
`;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import nav from 'next/navigation';
2+
import EmailTemplateSubmittedPage from '@app/email-template-submitted/[templateId]/page';
3+
import { render } from '@testing-library/react';
4+
import { getTemplate } from '@utils/form-actions';
5+
6+
jest.mock('@utils/form-actions', () => ({
7+
getTemplate: jest.fn().mockImplementation((templateId: string) => {
8+
if (templateId === 'template-id') {
9+
return {
10+
id: 'template-id',
11+
name: 'template-name',
12+
};
13+
}
14+
}),
15+
}));
16+
17+
jest.mock('next/navigation', () => ({
18+
redirect: () => {
19+
throw new Error('Simulated redirect');
20+
},
21+
useRouter: () => {},
22+
23+
RedirectType: {
24+
push: 'push',
25+
replace: 'replace',
26+
},
27+
}));
28+
29+
test('EmailTemplateSubmitted', async () => {
30+
const page = await EmailTemplateSubmittedPage({
31+
params: {
32+
templateId: 'template-id',
33+
},
34+
});
35+
36+
const container = render(page);
37+
38+
expect(jest.mocked(getTemplate)).toHaveBeenCalledWith('template-id');
39+
expect(container.asFragment()).toMatchSnapshot();
40+
});
41+
42+
test('EmailTemplateSubmitted - should handle invalid template', async () => {
43+
const redirectSpy = jest.spyOn(nav, 'redirect');
44+
45+
await expect(
46+
EmailTemplateSubmittedPage({
47+
params: {
48+
templateId: 'invalid-template',
49+
},
50+
})
51+
).rejects.toThrow('Simulated redirect');
52+
53+
expect(jest.mocked(getTemplate)).toHaveBeenCalledWith('invalid-template');
54+
expect(redirectSpy).toHaveBeenCalledWith('/invalid-template', 'replace');
55+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`PreviewEmailTemplatePage should render ReviewEmailTemplate with session data 1`] = `
4+
<ReviewEmailTemplate
5+
initialState={
6+
{
7+
"emailTemplateMessage": "template-message",
8+
"emailTemplateName": "template-name",
9+
"emailTemplateSubjectLine": "template-subject-line",
10+
"id": "session-id",
11+
"nhsAppTemplateMessage": "",
12+
"nhsAppTemplateName": "",
13+
"templateType": "EMAIL",
14+
}
15+
}
16+
/>
17+
`;

0 commit comments

Comments
 (0)