Skip to content

Commit 2920b63

Browse files
committed
CCM-11532: refactor
1 parent 49a8114 commit 2920b63

File tree

8 files changed

+87
-92
lines changed

8 files changed

+87
-92
lines changed

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/test-team/global.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ declare global {
66
COGNITO_USER_POOL_CLIENT_ID: string;
77
COGNITO_USER_POOL_ID: string;
88
EVENT_CACHE_BUCKET_NAME: string;
9-
NEXT_PUBLIC_ENABLE_PROOFING: string;
109
PLAYWRIGHT_RUN_ID: string;
1110
REQUEST_PROOF_QUEUE_URL: string;
1211
SFTP_ENVIRONMENT: string;
@@ -16,6 +15,7 @@ declare global {
1615
TEMPLATES_QUARANTINE_BUCKET_NAME: string;
1716
TEMPLATES_DOWNLOAD_BUCKET_NAME: string;
1817
TEST_EMAIL_BUCKET_PREFIX: string;
18+
TEST_EMAIL_BUCKET_NAME: string;
1919
}
2020
}
2121

tests/test-team/helpers/email-helper.ts

Lines changed: 4 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
import {
2-
S3Client,
3-
ListObjectsV2Command,
4-
GetObjectCommand,
5-
} from '@aws-sdk/client-s3';
1+
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
62
import { Readable } from 'node:stream';
3+
import { S3Helper } from './s3-helper';
74

85
async function streamToString(stream: Readable): Promise<string> {
96
return await new Promise((resolve, reject) => {
@@ -21,50 +18,15 @@ export class EmailHelper {
2118

2219
constructor() {}
2320

24-
async getAllS3Items(prefix: string) {
25-
const { Contents = [], ContinuationToken } = await this.s3Client.send(
26-
new ListObjectsV2Command({
27-
Bucket: this.testEmailBucketName,
28-
Prefix: prefix,
29-
})
30-
);
31-
32-
let nextToken = ContinuationToken;
33-
let s3Items = Contents;
34-
35-
while (nextToken) {
36-
const {
37-
Contents: newContents = [],
38-
ContinuationToken: newContinuationToken,
39-
} = await this.s3Client.send(
40-
new ListObjectsV2Command({
41-
Bucket: this.testEmailBucketName,
42-
Prefix: prefix,
43-
ContinuationToken: nextToken,
44-
})
45-
);
46-
47-
s3Items = [...s3Items, ...newContents];
48-
nextToken = newContinuationToken;
49-
}
50-
51-
return s3Items;
52-
}
53-
5421
async getEmailForTemplateId(
5522
prefix: string,
5623
templateId: string,
5724
dateCutoff: Date,
5825
extraTextToSearch: string
5926
) {
60-
const s3Items = await this.getAllS3Items(prefix);
27+
const s3Items = await S3Helper.listAll(this.testEmailBucketName, prefix);
6128

62-
const sortedKeys = s3Items
63-
.filter(({ LastModified }) => (LastModified ?? 0) > dateCutoff)
64-
.sort(
65-
(a, b) =>
66-
(b.LastModified?.getTime() ?? 0) - (a.LastModified?.getTime() ?? 0)
67-
);
29+
const sortedKeys = S3Helper.filterAndSort(s3Items, dateCutoff);
6830

6931
// SES does not tell us what the S3 keys are going to be for received emails,
7032
// so we have to search all recent ones to ensure we get the right one and

tests/test-team/helpers/events/event-cache-helper.ts

Lines changed: 33 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { z } from 'zod';
22
import {
3-
ListObjectsV2Command,
43
S3Client,
54
SelectObjectContentCommand,
65
SelectObjectContentEventStream,
7-
_Object,
86
} from '@aws-sdk/client-s3';
97
import {
108
$TemplateCompletedEventV1,
119
$TemplateDeletedEventV1,
1210
$TemplateDraftedEventV1,
1311
} from '@nhsdigital/nhs-notify-event-schemas-template-management';
12+
import { differenceInSeconds, addHours } from 'date-fns';
13+
import { S3Helper } from '../s3-helper';
1414

1515
const $NHSNotifyTemplateEvent = z.discriminatedUnion('type', [
1616
$TemplateCompletedEventV1,
@@ -32,57 +32,21 @@ export class EventCacheHelper {
3232
return [];
3333
}
3434

35-
const files = await this.getAllS3items(this.buildEventCachePrefix(from));
35+
const files = await Promise.all(
36+
this.filePaths(from).map((path) => {
37+
return S3Helper.listAll(this.bucketName, path);
38+
})
39+
);
3640

37-
const filteredFiles = this.filterAndSortFiles(files, from);
41+
const filteredFiles = S3Helper.filterAndSort(files.flat(), from);
3842

3943
const eventPromises = filteredFiles.map((file) =>
4044
this.queryFileForEvents(file.Key!, templateIds)
4145
);
4246

4347
const results = await Promise.all(eventPromises);
4448

45-
return results
46-
.flat()
47-
.sort(
48-
(a, b) =>
49-
new Date(a.data.updatedAt).getTime() -
50-
new Date(b.data.updatedAt).getTime()
51-
);
52-
}
53-
54-
private filterAndSortFiles(files: _Object[], from: Date): _Object[] {
55-
return files
56-
.filter(
57-
({ LastModified }) => (LastModified?.getTime() ?? 0) > from.getTime()
58-
)
59-
.sort(
60-
(a, b) =>
61-
(b.LastModified?.getTime() ?? 0) - (a.LastModified?.getTime() ?? 0)
62-
);
63-
}
64-
65-
private async getAllS3items(prefix: string): Promise<_Object[]> {
66-
const allItems: _Object[] = [];
67-
let continuationToken: string | undefined;
68-
69-
do {
70-
const command = new ListObjectsV2Command({
71-
Bucket: this.bucketName,
72-
Prefix: prefix,
73-
ContinuationToken: continuationToken,
74-
});
75-
76-
const response = await this.s3.send(command);
77-
78-
if (response.Contents) {
79-
allItems.push(...response.Contents);
80-
}
81-
82-
continuationToken = response.NextContinuationToken;
83-
} while (continuationToken);
84-
85-
return allItems;
49+
return results.flat();
8650
}
8751

8852
private async queryFileForEvents(
@@ -109,10 +73,10 @@ export class EventCacheHelper {
10973
return [];
11074
}
11175

112-
return await this.processS3SelectResponse(fileName, response.Payload);
76+
return await this.parse(fileName, response.Payload);
11377
}
11478

115-
private async processS3SelectResponse(
79+
private async parse(
11680
fileName: string,
11781
payload: AsyncIterable<SelectObjectContentEventStream>
11882
): Promise<NHSNotifyTemplateEvent[]> {
@@ -152,6 +116,27 @@ export class EventCacheHelper {
152116
return events;
153117
}
154118

119+
/*
120+
* Get files paths for the current hour
121+
* and next hour if the different in seconds is greater than toleranceInSeconds
122+
*
123+
* The way firehose stores files is yyyy/mm/dd/hh.
124+
* On a boundary of 15:59:58 you'll find files in both 15 and 16 hour folders
125+
*/
126+
private filePaths(start: Date, toleranceInSeconds = 30): string[] {
127+
const paths = [this.getEventCachePrefix(start)];
128+
129+
const end = addHours(start, 1);
130+
131+
const difference = differenceInSeconds(end, start);
132+
133+
if (difference >= toleranceInSeconds) {
134+
paths.push(this.getEventCachePrefix(end));
135+
}
136+
137+
return paths;
138+
}
139+
155140
private buildS3Query(templateIds: string[]): string {
156141
const likeConditions = templateIds
157142
.map((id) => `s.Message LIKE '%${id}%'`)
@@ -160,7 +145,7 @@ export class EventCacheHelper {
160145
return `SELECT * FROM S3Object s WHERE ${likeConditions}`;
161146
}
162147

163-
private buildEventCachePrefix(date: Date): string {
148+
private getEventCachePrefix(date: Date): string {
164149
return date
165150
.toISOString()
166151
.slice(0, 13)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { _Object, ListObjectsV2Command, S3Client } from '@aws-sdk/client-s3';
2+
3+
export class S3Helper {
4+
private static readonly client = new S3Client({ region: 'eu-west-2' });
5+
6+
static async listAll(bucket: string, prefix: string): Promise<_Object[]> {
7+
const allItems: _Object[] = [];
8+
let continuationToken: string | undefined;
9+
10+
do {
11+
const command = new ListObjectsV2Command({
12+
Bucket: bucket,
13+
Prefix: prefix,
14+
ContinuationToken: continuationToken,
15+
});
16+
17+
const response = await this.client.send(command);
18+
19+
if (response.Contents) {
20+
allItems.push(...response.Contents);
21+
}
22+
23+
continuationToken = response.NextContinuationToken;
24+
} while (continuationToken);
25+
26+
return allItems;
27+
}
28+
29+
static filterAndSort(files: _Object[], from: Date): _Object[] {
30+
return S3Helper.sort(S3Helper.filter([...files], from));
31+
}
32+
33+
static filter(files: _Object[], from: Date): _Object[] {
34+
return files.filter(
35+
({ LastModified }) => (LastModified?.getTime() ?? 0) > from.getTime()
36+
);
37+
}
38+
39+
static sort(files: _Object[]): _Object[] {
40+
return files.sort(
41+
(a, b) =>
42+
(b.LastModified?.getTime() ?? 0) - (a.LastModified?.getTime() ?? 0)
43+
);
44+
}
45+
}

tests/test-team/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"@playwright/test": "^1.51.1",
1414
"async-mutex": "^0.5.0",
1515
"aws-amplify": "^6.13.6",
16+
"date-fns": "^4.1.0",
1617
"eslint-config-airbnb": "^19.0.4",
1718
"eslint-config-airbnb-base": "^15.0.0",
1819
"eslint-plugin-playwright": "^2.2.2",

tests/test-team/template-mgmt-event-tests/letter-templates.event.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ test.describe('Event publishing - Letters', () => {
242242
}),
243243
})
244244
);
245-
}).toPass({ timeout: 120_000, intervals: [1000, 3000, 5000, 10_000] });
245+
}).toPass({ timeout: 90_000, intervals: [1000, 3000, 5000, 10_000] });
246246
});
247247

248248
test('Expect Deleted.v1 event when deleting templates', async ({

tests/test-team/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"compilerOptions": {
3+
"baseUrl": ".",
34
"paths": {
45
"@nhsdigital/nhs-notify-event-schemas-template-management": [
56
"../../packages/event-schemas/src/index.ts"

0 commit comments

Comments
 (0)