diff --git a/package-lock.json b/package-lock.json index b77cea1f..df7ecf0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6769,12 +6769,12 @@ } }, "node_modules/dunder-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", - "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", + "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" }, @@ -10431,13 +10431,13 @@ } }, "node_modules/is-finalizationregistry": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.0.tgz", - "integrity": "sha512-qfMdqbAQEwBw78ZyReKnlA8ezmPdb9BemzIIip/JkjaZUhitfXDkkr+3QTboW0JrSXT1QWyYShpvnNHGZ4c4yA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7" + "call-bound": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -10760,14 +10760,14 @@ } }, "node_modules/is-weakset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", - "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4" + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -12441,9 +12441,9 @@ } }, "node_modules/libphonenumber-js": { - "version": "1.11.16", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.16.tgz", - "integrity": "sha512-Noyazmt0yOvnG0OeRY45Cd1ur8G7Z0HWVkuCuKe+yysGNxPQwBAODBQQ40j0AIagi9ZWurfmmZWNlpg4h4W+XQ==", + "version": "1.11.17", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.17.tgz", + "integrity": "sha512-Jr6v8thd5qRlOlc6CslSTzGzzQW03uiscab7KHQZX1Dfo4R6n6FDhZ0Hri6/X7edLIDv9gl4VMZXhxTjLnl0VQ==", "license": "MIT" }, "node_modules/lilconfig": { diff --git a/package.json b/package.json index 7b89f750..853869bd 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ "audit:fix": "npm audit --fix", "build": "nest build -p tsconfig.build.json", "housekeeping": "npm update --save --legacy-peer-deps && npm i --legacy-peer-deps && npm run type-check && npm run prettier:fix && npm run lint:fix && npm run validate:yml && npm run audit:fix && npm run spellcheck", - "lint": "eslint . --ext .ts", - "lint:fix": "eslint . --ext .ts --fix", + "lint": "eslint . --ext .js,.ts", + "lint:fix": "eslint . --ext .js,.ts --fix", "prettier": "prettier --no-error-on-unmatched-pattern --check **/*.{ts,js}", "prettier:fix": "prettier --write **/*.{ts,js}", "spellcheck": "cspell lint --gitignore --no-must-find-files --unique --no-progress --show-suggestions --color '**/*'", @@ -28,8 +28,8 @@ "lint-staged": { "**/package.json": "sort-package-json", "**/*.{js,ts}": [ - "lint:fix", - "prettier:fix" + "npm run lint:fix", + "npm run prettier:fix" ], "**/*.md": [ "prettier --write" diff --git a/src/constants/govuk-notify.constant.ts b/src/constants/govuk-notify.constant.ts index d32afb4e..633e144d 100644 --- a/src/constants/govuk-notify.constant.ts +++ b/src/constants/govuk-notify.constant.ts @@ -11,6 +11,7 @@ export const GOVUK_NOTIFY = { RESPONSE_URI: 'https://api.notifications.service.gov.uk/v2/notifications/efd12345-1234-5678-9012-ee123456789f', TEMPLATE_ID: 'tmpl1234-1234-5678-9012-abcd12345678', TEMPLATE_URI: 'https://api.notifications.service.gov.uk/services/abc12345-a123-4567-8901-123456789012/templates/tmpl1234-1234-5678-9012-abcd12345678', + FILE: Buffer.from('example-file.xlsx'), }, FIELD_LENGTHS: { TEMPLATE_ID: 36, diff --git a/src/helper-modules/govuk-notify/examples/example-response-for-prepare-upload.json b/src/helper-modules/govuk-notify/examples/example-response-for-prepare-upload.json new file mode 100644 index 00000000..f242edf8 --- /dev/null +++ b/src/helper-modules/govuk-notify/examples/example-response-for-prepare-upload.json @@ -0,0 +1,15 @@ +{ + "id": "740e5834-3a29-46b4-9a6f-16142fde533a", + "reference": "your_reference_here", + "content": { + "subject": "SUBJECT TEXT", + "body": "MESSAGE TEXT", + "from_email": "SENDER EMAIL" + }, + "uri": "https: //api.notifications.service.gov.uk/v2/notifications/740e5834-3a29-46b4-9a6f-16142fde533a", + "template": { + "id": "f33517ff-2a88-4f6e-b855-c550268ce08a", + "version": "24", + "uri": "https: //api.notifications.service.gov.uk/v2/template/f33517ff-2a88-4f6e-b855-c550268ce08a" + } +} diff --git a/src/helper-modules/govuk-notify/examples/example-response-for-send-emails.json b/src/helper-modules/govuk-notify/examples/example-response-for-send-emails.json index c4cab822..fbe44149 100644 --- a/src/helper-modules/govuk-notify/examples/example-response-for-send-emails.json +++ b/src/helper-modules/govuk-notify/examples/example-response-for-send-emails.json @@ -1,17 +1,17 @@ { "content": { - "body": "Dear John Smith,\r\n\r\nThe status of your MIA for EuroStar has been updated.\r\n\r\n* Your bank reference: EuroStar bridge\r\n* Current status: Acknowledged\r\n* Previous status: Submitted\r\n* Updated by: Joe Bloggs (Joe.Bloggs@example.com)\r\n\r\nSign in to our service for more information: \r\nhttps://www.test.service.gov.uk/\r\n\r\nWith regards,\r\n\r\nThe Digital Trade Finance Service team\r\n\r\nEmail: test@test.gov.uk\r\nPhone: +44 (0)202 123 4567\r\nOpening times: Monday to Friday, 9am to 5pm (excluding public holidays)", - "from_email": "test@notifications.service.gov.uk", - "subject": "Status update: EuroStar bridge", - "unsubscribe_link": null + "body": "Dear John Smith,\r\n\r\nThe status of your MIA for EuroStar has been updated.\r\n\r\n* Your bank reference: EuroStar bridge\r\n* Current status: Acknowledged\r\n* Previous status: Submitted\r\n* Updated by: Joe Bloggs (Joe.Bloggs@example.com)\r\n\r\nSign in to our service for more information: \r\nhttps://www.test.service.gov.uk/\r\n\r\nWith regards,\r\n\r\nThe Digital Trade Finance Service team\r\n\r\nEmail: test@test.gov.uk\r\nPhone: +44 (0)202 123 4567\r\nOpening times: Monday to Friday, 9am to 5pm (excluding public holidays)", + "from_email": "test@notifications.service.gov.uk", + "subject": "Status update: EuroStar bridge", + "unsubscribe_link": null }, "id": "efd12345-1234-5678-9012-ee123456789f", "reference": "tmpl1234-1234-5678-9012-abcd12345678-1713346533467", "scheduled_for": null, "template": { - "id": "tmpl1234-1234-5678-9012-abcd12345678", - "uri": "https://api.notifications.service.gov.uk/services/abc12345-a123-4567-8901-123456789012/templates/tmpl1234-1234-5678-9012-abcd12345678", - "version": 24 + "id": "tmpl1234-1234-5678-9012-abcd12345678", + "uri": "https://api.notifications.service.gov.uk/services/abc12345-a123-4567-8901-123456789012/templates/tmpl1234-1234-5678-9012-abcd12345678", + "version": 24 }, "uri": "https://api.notifications.service.gov.uk/v2/notifications/efd12345-1234-5678-9012-ee123456789f" } diff --git a/src/helper-modules/govuk-notify/govuk-notify.service.test.ts b/src/helper-modules/govuk-notify/govuk-notify.service.test.ts index a2bb3730..411dabbe 100644 --- a/src/helper-modules/govuk-notify/govuk-notify.service.test.ts +++ b/src/helper-modules/govuk-notify/govuk-notify.service.test.ts @@ -5,7 +5,9 @@ import { AxiosError, AxiosResponse } from 'axios'; import { PinoLogger } from 'nestjs-pino'; import { NotifyClient } from 'notifications-node-client'; -import expectedResponse from './examples/example-response-for-send-emails.json'; +import { PostEmailsRequestDto } from '../../modules/emails/dto/post-emails-request.dto'; +import expectedPrepareUploadResponse from './examples/example-response-for-prepare-upload.json'; +import expectedSendEmailsResponse from './examples/example-response-for-send-emails.json'; import { GovukNotifyService } from './govuk-notify.service'; jest.mock('notifications-node-client'); @@ -24,9 +26,18 @@ describe('GovukNotifyService', () => { supplierName: valueGenerator.word(), }; - const sendEmailMethodMock = jest - .spyOn(NotifyClient.prototype, 'sendEmail') - .mockImplementation(() => Promise.resolve({ status: 201, data: expectedResponse })); + const filePersonalisation = { + ...personalisation, + file: 'mock-file-buffer', + }; + + const mockSendEmailResponse = { status: 201, data: expectedSendEmailsResponse }; + const mockPrepareUploadResponse = { status: 201, data: expectedPrepareUploadResponse }; + + const sendEmailMethodMock = jest.spyOn(NotifyClient.prototype, 'sendEmail').mockImplementation(() => Promise.resolve(mockSendEmailResponse)); + + const prepareUploadMethodMock = jest.spyOn(NotifyClient.prototype, 'prepareUpload').mockImplementation(() => Promise.resolve(mockPrepareUploadResponse)); + const logger = new PinoLogger({}); logger.error = loggerError; const service = new GovukNotifyService(logger); @@ -65,10 +76,40 @@ describe('GovukNotifyService', () => { expect(sendEmailMethodMock).toHaveBeenCalledWith(templateId, sendToEmailAddress, { personalisation, reference }); }); + describe('when a file property is provided', () => { + it('calls GOV.UK Notify client prepareUpload function with the correct arguments', async () => { + const mockParams: PostEmailsRequestDto = { + sendToEmailAddress, + templateId, + personalisation: filePersonalisation, + reference, + file: 'mock-file-buffer', + }; + + await service.sendEmail(govUkNotifyKey, mockParams); + + expect(prepareUploadMethodMock).toHaveBeenCalledTimes(1); + expect(prepareUploadMethodMock).toHaveBeenCalledWith(mockParams.file, { confirmEmailBeforeDownload: true }); + }); + + it('calls GOV.UK Notify client sendEmail function with the correct arguments', async () => { + await service.sendEmail(govUkNotifyKey, { sendToEmailAddress, templateId, personalisation: filePersonalisation, reference }); + + expect(sendEmailMethodMock).toHaveBeenCalledTimes(1); + + const expectedPersonalisation = { + ...filePersonalisation, + linkToFile: mockPrepareUploadResponse, + }; + + expect(sendEmailMethodMock).toHaveBeenCalledWith(templateId, sendToEmailAddress, { personalisation: expectedPersonalisation, reference }); + }); + }); + it('returns a 201 response from GOV.UK Notify when sending the email is successful', async () => { const response = await service.sendEmail(govUkNotifyKey, { sendToEmailAddress, templateId, personalisation }); - expect(response).toEqual({ status: 201, data: expectedResponse }); + expect(response).toEqual({ status: 201, data: expectedSendEmailsResponse }); }); it.each([ diff --git a/src/helper-modules/govuk-notify/govuk-notify.service.ts b/src/helper-modules/govuk-notify/govuk-notify.service.ts index 13631f4a..cb4e3145 100644 --- a/src/helper-modules/govuk-notify/govuk-notify.service.ts +++ b/src/helper-modules/govuk-notify/govuk-notify.service.ts @@ -33,10 +33,18 @@ export class GovukNotifyService { async sendEmail(govUkNotifyKey: string, postEmailsRequest: PostEmailsRequestDto): Promise { // We create new client for each request because govUkNotifyKey (auth key) might be different. const notifyClient = new NotifyClient(govUkNotifyKey); + const reference = postEmailsRequest.reference || `${postEmailsRequest.templateId}-${Date.now()}`; + + const { personalisation } = postEmailsRequest; + + if (personalisation.file) { + personalisation.linkToFile = await notifyClient.prepareUpload(personalisation.file, { confirmEmailBeforeDownload: true }); + } + const notifyResponse = await notifyClient .sendEmail(postEmailsRequest.templateId, postEmailsRequest.sendToEmailAddress, { - personalisation: postEmailsRequest.personalisation, + personalisation, reference, }) .then((response: any) => response) diff --git a/src/modules/emails/dto/post-emails-request.dto.ts b/src/modules/emails/dto/post-emails-request.dto.ts index 227c67b4..0b965243 100644 --- a/src/modules/emails/dto/post-emails-request.dto.ts +++ b/src/modules/emails/dto/post-emails-request.dto.ts @@ -40,7 +40,7 @@ export class PostEmailsRequestDto { @IsString() @IsOptional() @MinLength(1) - // 100 characters is arbitrary max limit, GOV.UK Notify can accept references at least 400 characters long. + // 100 characters is an arbitrary max limit. GOV.UK Notify can accept references at least 400 characters long. @MaxLength(100) @ApiProperty({ example: GOVUK_NOTIFY.EXAMPLES.REFERENCE, @@ -51,4 +51,18 @@ export class PostEmailsRequestDto { maxLength: 100, }) readonly reference?: string | null; + + @IsString() + @IsOptional() + @MinLength(1) + @MaxLength(400) + @ApiProperty({ + example: GOVUK_NOTIFY.EXAMPLES.FILE, + description: 'File for GovNotify to consume and generate a link to download', + required: false, + nullable: true, + minLength: 1, + maxLength: 400, + }) + readonly file?: string | null; }