Skip to content

Commit d350ee5

Browse files
CCM-8861: Changes in response to review comments
1 parent 6b1fc78 commit d350ee5

File tree

5 files changed

+197
-108
lines changed

5 files changed

+197
-108
lines changed

infrastructure/terraform/modules/backend-api/module_lambda_sftp_poll.tf

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module "lambda_sftp_poll" {
22
source = "../lambda-function"
3-
description = "Lambda to poll the SFTP suppliers and "
3+
description = "Lambda to poll the SFTP suppliers and copy proofs to the quarantine bucket"
44

55
function_name = "${local.csi}-sftp-poll"
66
filename = module.build_sftp_letters_lambdas.zips["src/sftp-poll.ts"].path
@@ -20,7 +20,8 @@ module "lambda_sftp_poll" {
2020
SFTP_ENVIRONMENT = local.sftp_environment
2121
}
2222

23-
timeout = 20
23+
timeout = 60 * 10
24+
memory_size = 2048
2425
}
2526

2627
data "aws_iam_policy_document" "sftp_poll" {

lambdas/sftp-letters/package.json

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
{
2-
"name": "nhs-notify-sftp-letters-lambdas",
3-
"version": "0.0.1",
4-
"private": true,
5-
"scripts": {
6-
"test:unit": "jest",
7-
"lint": "eslint .",
8-
"lint:fix": "eslint . --fix",
9-
"typecheck": "tsc --noEmit"
2+
"dependencies": {
3+
"@aws-sdk/client-dynamodb": "3.775.0",
4+
"@aws-sdk/client-s3": "3.775.0",
5+
"@aws-sdk/client-ssm": "3.775.0",
6+
"@aws-sdk/lib-dynamodb": "3.775.0",
7+
"csv-parse": "^5.6.0",
8+
"csv-stringify": "^6.4.4",
9+
"date-fns": "^4.1.0",
10+
"ksuid": "^3.0.0",
11+
"nhs-notify-entity-update-command-builder": "*",
12+
"nhs-notify-web-template-management-utils": "*",
13+
"node-cache": "^5.1.2",
14+
"ssh2-sftp-client": "^9.1.0",
15+
"zod": "^3.24.2"
1016
},
1117
"devDependencies": {
1218
"@swc/core": "^1.11.3",
@@ -20,24 +26,18 @@
2026
"esbuild": "^0.24.0",
2127
"jest": "^29.7.0",
2228
"jest-mock-extended": "^3.0.7",
29+
"nhs-notify-web-template-management-test-helper-utils": "*",
2330
"ts-jest": "^29.3.0",
2431
"ts-node": "^10.9.2",
2532
"typescript": "^5.8.2"
2633
},
27-
"dependencies": {
28-
"@aws-sdk/client-dynamodb": "3.775.0",
29-
"@aws-sdk/client-s3": "3.775.0",
30-
"@aws-sdk/client-ssm": "3.775.0",
31-
"@aws-sdk/lib-dynamodb": "3.775.0",
32-
"csv-parse": "^5.6.0",
33-
"csv-stringify": "^6.4.4",
34-
"date-fns": "^4.1.0",
35-
"ksuid": "^3.0.0",
36-
"nhs-notify-entity-update-command-builder": "*",
37-
"nhs-notify-web-template-management-test-helper-utils": "*",
38-
"nhs-notify-web-template-management-utils": "*",
39-
"node-cache": "^5.1.2",
40-
"ssh2-sftp-client": "^9.1.0",
41-
"zod": "^3.24.2"
42-
}
34+
"name": "nhs-notify-sftp-letters-lambdas",
35+
"private": true,
36+
"scripts": {
37+
"lint": "eslint .",
38+
"lint:fix": "eslint . --fix",
39+
"test:unit": "jest",
40+
"typecheck": "tsc --noEmit"
41+
},
42+
"version": "0.0.1"
4343
}

lambdas/sftp-letters/src/__tests__/app/poll.test.ts

Lines changed: 73 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { mockDeep } from 'jest-mock-extended';
33
import { App } from '../../app/poll';
44
import { SftpSupplierClientRepository } from '../../infra/sftp-supplier-client-repository';
55
import { SftpClient } from '../../infra/sftp-client';
6-
import { Logger } from 'nhs-notify-web-template-management-utils/logger';
76
import { S3Repository } from 'nhs-notify-web-template-management-utils';
7+
import { createMockLogger } from 'nhs-notify-web-template-management-test-helper-utils/mock-logger';
88

99
const mockPdfBuffer = Buffer.from([0x25, 0x50, 0x44, 0x46]);
1010
const mockMalformedPdfBuffer = Buffer.from([]);
@@ -13,7 +13,12 @@ const downloadError = new Error('Download error');
1313
const directoryType: FileInfo['type'] = 'd';
1414
const fileType: FileInfo['type'] = '-';
1515

16-
test('polls SFTP clients', async () => {
16+
beforeAll(() => {
17+
jest.useFakeTimers();
18+
jest.setSystemTime(new Date('2022-01-01 09:00'));
19+
});
20+
21+
test('polls SFTP folder', async () => {
1722
const sftpClient = mockDeep<SftpClient>({
1823
exists: async (path: string) => {
1924
const existsMappings: Record<string, FileInfo['type']> = {
@@ -56,7 +61,7 @@ test('polls SFTP clients', async () => {
5661
modifyTime: Date.now(),
5762
},
5863
{
59-
name: 'template-3.pdf',
64+
name: 'file-to-ignore.pdf',
6065
type: fileType,
6166
modifyTime: Date.now(),
6267
},
@@ -87,6 +92,11 @@ test('polls SFTP clients', async () => {
8792
type: fileType,
8893
modifyTime: Date.now(),
8994
},
95+
{
96+
name: 'extra-folder-to-ignore',
97+
type: directoryType,
98+
modifyTime: Date.now(),
99+
},
90100
],
91101
})[path] ?? [],
92102
});
@@ -100,19 +110,19 @@ test('polls SFTP clients', async () => {
100110
}),
101111
});
102112

103-
const mockLogger = mockDeep<Logger>();
113+
const mockLogger = createMockLogger();
104114
const s3Repository = mockDeep<S3Repository>();
105115

106116
const app = new App(
107117
sftpSupplierClientRepository,
108-
mockLogger,
118+
mockLogger.logger,
109119
s3Repository,
110120
'sftp-environment'
111121
);
112122

113123
await app.poll('supplier');
114124

115-
expect(s3Repository.putRawData).toHaveBeenCalledTimes(3);
125+
expect(s3Repository.putRawData).toHaveBeenCalledTimes(2);
116126
expect(s3Repository.putRawData).toHaveBeenCalledWith(
117127
mockPdfBuffer,
118128
'proofs/template-1-folder/template-1.pdf'
@@ -121,21 +131,66 @@ test('polls SFTP clients', async () => {
121131
mockPdfBuffer,
122132
'proofs/template-1-folder/template-2.pdf'
123133
);
124-
expect(s3Repository.putRawData).toHaveBeenCalledWith(
125-
mockPdfBuffer,
126-
'proofs/template-3.pdf'
127-
);
128134

129-
expect(mockLogger.error).toHaveBeenCalledWith('PDF file failed validation', {
130-
copyPath:
131-
'download-dir/sftp-environment/proofs/template-1-folder/invalid-file.pdf',
132-
pastePath: 'proofs/template-1-folder/invalid-file.pdf',
135+
expect(mockLogger.logMessages).toContainEqual({
136+
level: 'error',
137+
message: {
138+
description: 'PDF file failed validation',
139+
sftpPath:
140+
'download-dir/sftp-environment/proofs/template-1-folder/invalid-file.pdf',
141+
s3Path: 'proofs/template-1-folder/invalid-file.pdf',
142+
},
143+
timestamp: new Date('2022-01-01 09:00').toISOString(),
133144
});
134-
expect(mockLogger.error).toHaveBeenCalledWith('Failed to copy file', {
135-
copyPath:
145+
expect(mockLogger.logMessages).toContainEqual({
146+
level: 'error',
147+
description: 'Failed to process file',
148+
sftpPath:
136149
'download-dir/sftp-environment/proofs/template-1-folder/download-error.pdf',
137-
pastePath: 'proofs/template-1-folder/download-error.pdf',
138-
error: downloadError,
150+
s3Path: 'proofs/template-1-folder/download-error.pdf',
151+
timestamp: new Date('2022-01-01 09:00').toISOString(),
152+
stack: expect.stringContaining('Error: Download error'),
153+
message: 'Download error',
154+
});
155+
156+
expect(sftpClient.connect).toHaveBeenCalledTimes(1);
157+
expect(sftpClient.end).toHaveBeenCalledTimes(1);
158+
});
159+
160+
test('attempts to poll folder that does not exist', async () => {
161+
const mockSftpList = jest.fn();
162+
const sftpClient = mockDeep<SftpClient>({
163+
exists: async () => false as const,
164+
list: mockSftpList,
165+
});
166+
167+
const sftpSupplierClientRepository = mockDeep<SftpSupplierClientRepository>({
168+
getClient: async () => ({
169+
sftpClient,
170+
baseUploadDir: 'upload-dir',
171+
baseDownloadDir: 'download-dir',
172+
name: 'supplier',
173+
}),
174+
});
175+
176+
const mockLogger = createMockLogger();
177+
const s3Repository = mockDeep<S3Repository>();
178+
179+
const app = new App(
180+
sftpSupplierClientRepository,
181+
mockLogger.logger,
182+
s3Repository,
183+
'sftp-environment'
184+
);
185+
186+
await app.poll('supplier');
187+
188+
expect(sftpClient.list).not.toHaveBeenCalled();
189+
190+
expect(mockLogger.logMessages).toContainEqual({
191+
level: 'info',
192+
message: "Path 'download-dir/sftp-environment/proofs' does not exist",
193+
timestamp: new Date('2022-01-01 09:00').toISOString(),
139194
});
140195

141196
expect(sftpClient.connect).toHaveBeenCalledTimes(1);
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import type { Container } from '../container-poll';
22

3-
type EventBridgeEvent = {
3+
type PollEvent = {
44
supplier: string;
55
};
66

77
export const createHandler =
88
({ app }: Container) =>
9-
({ supplier }: EventBridgeEvent) =>
9+
({ supplier }: PollEvent) =>
1010
app.poll(supplier);

0 commit comments

Comments
 (0)