Skip to content

Commit bc42881

Browse files
CCM-16553: Code review comments
1 parent cd934bd commit bc42881

12 files changed

Lines changed: 1253 additions & 111 deletions

package-lock.json

Lines changed: 1146 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/jest.config.ts

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,14 @@
1-
import type { Config } from 'jest';
1+
import { baseJestConfig } from '../jest.config.base';
22

3-
const config: Config = {
4-
preset: 'ts-jest',
5-
testEnvironment: 'node',
3+
const config = {
4+
...baseJestConfig,
65
roots: ['<rootDir>/nft-event-generator'],
7-
testMatch: ['**/__tests__/**/*.test.ts', '**/?(*.)+(spec|test).ts'],
8-
transform: {
9-
'^.+\\.ts$': [
10-
'ts-jest',
11-
{
12-
tsconfig: {
13-
esModuleInterop: true,
14-
allowSyntheticDefaultImports: true,
15-
allowImportingTsExtensions: true,
16-
module: 'commonjs',
17-
target: 'ES2020',
18-
moduleResolution: 'node',
19-
noEmit: true,
20-
typeRoots: ['../node_modules/@types'],
21-
},
22-
diagnostics: {
23-
ignoreCodes: [1343], // Ignore TS1343: import.meta errors
24-
},
25-
},
26-
],
27-
},
286
modulePaths: ['<rootDir>/nft-event-generator/src'],
7+
moduleNameMapper: {
8+
// Map local subpath aliases that would otherwise resolve to the 'utils'
9+
// workspace package before the local nft-event-generator source is checked.
10+
'^utils/(.+)$': '<rootDir>/nft-event-generator/src/utils/$1',
11+
},
2912
collectCoverageFrom: [
3013
'nft-event-generator/src/**/*.{ts,js}',
3114
'!nft-event-generator/src/**/*.d.ts',
@@ -35,21 +18,14 @@ const config: Config = {
3518
'!nft-event-generator/src/cli.ts',
3619
],
3720
coverageDirectory: 'nft-event-generator/coverage',
38-
coveragePathIgnorePatterns: ['/node_modules/', '/__tests__/'],
3921
coverageThreshold: {
4022
global: {
41-
branches: 60,
42-
functions: 60,
43-
lines: 60,
44-
statements: 60,
23+
branches: 80,
24+
functions: 80,
25+
lines: 80,
26+
statements: 80,
4527
},
4628
},
47-
moduleFileExtensions: ['ts', 'js', 'json'],
48-
moduleNameMapper: {
49-
'^(.*)\\.ts$': '$1',
50-
},
51-
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
52-
testTimeout: 10_000,
5329
};
5430

5531
export default config;

scripts/nft-event-generator/README.md

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -99,20 +99,6 @@ npm start -- paper-letter-opt-out-event --environment pr293 --csvFile ./opt-outs
9999

100100
---
101101

102-
## Running via Make
103-
104-
To run this script from anywhere in the repository:
105-
106-
```shell
107-
make perf-test
108-
```
109-
110-
The make command runs the following script (configured in `package.json`):
111-
112-
```shell
113-
"start:nft": "npm start -- supplier-api-letter-event --environment nft --numberOfEvents 2 --interval 2000"
114-
```
115-
116102
## Help
117103

118104
To see all available options for a subcommand:
Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import {
2+
EventBridgeClient,
23
PutEventsCommand,
34
PutEventsResultEntry,
45
} from '@aws-sdk/client-eventbridge';
56
import { PublishableEvent } from 'destinations/destination-client';
67
import { mock } from 'jest-mock-extended';
7-
import { sendEventsToEventBus } from 'destinations/send-events-to-event-bus';
8-
/* eslint-disable no-console */
8+
import {
9+
EventBusDestinationClient,
10+
sendEventsToEventBus,
11+
} from 'destinations/send-events-to-event-bus';
12+
913
const environment = 'dev';
1014

1115
const sampleEvent = {
@@ -15,33 +19,39 @@ const sampleEvent = {
1519
time: '2023-06-20T12:00:00.000Z',
1620
};
1721

18-
const mockEventBridgeClient = { send: jest.fn() };
22+
// Factory no longer references any outer variable
1923
jest.mock('@aws-sdk/client-eventbridge', () => {
2024
const originalModule = jest.requireActual('@aws-sdk/client-eventbridge');
21-
2225
return {
2326
__esModule: true,
2427
...originalModule,
25-
EventBridgeClient: jest.fn(() => mockEventBridgeClient),
28+
EventBridgeClient: jest.fn(),
2629
};
2730
});
2831

32+
const mockSend = jest.fn();
33+
2934
const successEntry = mock<PutEventsResultEntry>({ ErrorCode: undefined });
3035
const successfulSendResponse = { Entries: [successEntry] };
3136

3237
describe('sendEventsToEventBus', () => {
3338
beforeEach(() => {
34-
mockEventBridgeClient.send.mockReset();
39+
mockSend.mockReset();
40+
// Wire up the mock implementation here, after all declarations are initialised
41+
jest
42+
.mocked(EventBridgeClient)
43+
.mockImplementation(
44+
() => ({ send: mockSend }) as unknown as EventBridgeClient,
45+
);
3546
});
3647

3748
it('should send the expected request to EventBridge', async () => {
38-
mockEventBridgeClient.send.mockResolvedValue(successfulSendResponse);
49+
mockSend.mockResolvedValue(successfulSendResponse);
3950

4051
await sendEventsToEventBus(environment, [sampleEvent], 5);
4152

42-
expect(mockEventBridgeClient.send).toHaveBeenCalled();
43-
const putEventsCommand: PutEventsCommand =
44-
mockEventBridgeClient.send.mock.calls[0][0];
53+
expect(mockSend).toHaveBeenCalled();
54+
const putEventsCommand: PutEventsCommand = mockSend.mock.calls[0][0];
4555

4656
expect(putEventsCommand.input.Entries).toHaveLength(1);
4757
const entry = putEventsCommand.input.Entries![0];
@@ -56,19 +66,17 @@ describe('sendEventsToEventBus', () => {
5666
{ length: 52 },
5767
() => sampleEvent,
5868
);
59-
mockEventBridgeClient.send.mockResolvedValue(successfulSendResponse);
69+
mockSend.mockResolvedValue(successfulSendResponse);
6070

6171
await sendEventsToEventBus(environment, events, 5);
6272

6373
// Batch size is 10, so 52 events = 6 batches.
64-
expect(mockEventBridgeClient.send).toHaveBeenCalledTimes(6);
74+
expect(mockSend).toHaveBeenCalledTimes(6);
6575
});
6676

6777
it('should continue sending batches if an error is raised', async () => {
68-
mockEventBridgeClient.send.mockRejectedValueOnce(
69-
new Error('Something went wrong!'),
70-
);
71-
mockEventBridgeClient.send.mockResolvedValue(successfulSendResponse);
78+
mockSend.mockRejectedValueOnce(new Error('Something went wrong!'));
79+
mockSend.mockResolvedValue(successfulSendResponse);
7280

7381
const events: PublishableEvent[] = Array.from(
7482
{ length: 30 },
@@ -78,19 +86,44 @@ describe('sendEventsToEventBus', () => {
7886
await sendEventsToEventBus(environment, events, 5);
7987

8088
// Batch size is 10, so 30 events = 3 batches.
81-
expect(mockEventBridgeClient.send).toHaveBeenCalledTimes(3);
89+
expect(mockSend).toHaveBeenCalledTimes(3);
8290
});
8391

8492
it('should warn when some events fail to publish', async () => {
93+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
8594
const failedEntry = mock<PutEventsResultEntry>({
8695
ErrorCode: 'InternalFailure',
8796
});
88-
mockEventBridgeClient.send.mockResolvedValue({
97+
mockSend.mockResolvedValue({
8998
Entries: [failedEntry],
9099
});
91100

92101
await sendEventsToEventBus(environment, [sampleEvent], 5);
93102

94-
expect(console.warn).toHaveBeenCalled();
103+
expect(warnSpy).toHaveBeenCalled();
104+
warnSpy.mockRestore();
105+
});
106+
});
107+
108+
describe('EventBusDestinationClient', () => {
109+
beforeEach(() => {
110+
mockSend.mockReset();
111+
jest
112+
.mocked(EventBridgeClient)
113+
.mockImplementation(
114+
() => ({ send: mockSend }) as unknown as EventBridgeClient,
115+
);
116+
});
117+
118+
it('should delegate sendEvents to sendEventsToEventBus', async () => {
119+
mockSend.mockResolvedValue(successfulSendResponse);
120+
121+
const client = new EventBusDestinationClient(environment);
122+
await client.sendEvents([sampleEvent], 5);
123+
124+
expect(mockSend).toHaveBeenCalledTimes(1);
125+
const putEventsCommand: PutEventsCommand = mockSend.mock.calls[0][0];
126+
const entry = putEventsCommand.input.Entries![0];
127+
expect(entry.EventBusName).toBe(`nhs-${environment}-dl`);
95128
});
96129
});

scripts/nft-event-generator/src/__tests__/generators/generate-paper-letter-opt-out-events.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe('generatePaperLetterOptOutEvents', () => {
3131
'customer/037f5f76-352c-445f-89a7-c3d18776ce86/message/3COesqsClaLyf0WNuLuhz1RDbWs/plan/3COezubdtrUFJlDOV4ucAQ93Akr',
3232
id: expect.any(String),
3333
time: expect.any(String),
34-
source: '/nhs/england/notify/comms-mgr-test/test/data-plane/messaging',
34+
source: `/nhs/england/notify/comms-mgr-${environment}/${environment}/data-plane/messaging`,
3535
data: {
3636
messageReference: sampleRows[0].messageReference,
3737
clientId: sampleRows[0].senderId,

scripts/nft-event-generator/src/destinations/destination-client.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
/**
22
* Minimum shape required by all destination clients.
3-
* Each publishable event must carry an `id` so the sending implementation
4-
* can use it as a unique identifier within a batch.
3+
* Each publishable event must carry the defined properties, where the `id`
4+
* is used as a unique identifier within a batch.
55
*/
6-
export type PublishableEvent = { id: string };
6+
export type PublishableEvent = {
7+
id: string;
8+
source: string;
9+
type: string;
10+
time: string;
11+
};
712

813
/**
914
* Common interface for all event destinations (SQS, EventBridge, etc.).

scripts/nft-event-generator/src/destinations/send-events-to-event-bus.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
DestinationClient,
77
PublishableEvent,
88
} from 'destinations/destination-client';
9+
import { sleep } from 'utils';
910

1011
/* eslint-disable no-console */
1112

@@ -32,13 +33,6 @@ function batchEvents(events: PublishableEvent[]): PublishableEvent[][] {
3233
return batches;
3334
}
3435

35-
// Wait for X milliseconds
36-
function wait(interval: number) {
37-
return new Promise((resolve) => {
38-
setTimeout(resolve, interval);
39-
});
40-
}
41-
4236
export async function sendEventsToEventBus(
4337
environment: string,
4438
events: PublishableEvent[],
@@ -59,7 +53,7 @@ export async function sendEventsToEventBus(
5953
currentBatch += 1;
6054

6155
const entries = batch.map((event) => {
62-
const cloudEvent = event as unknown as CloudEventEnvelope;
56+
const cloudEvent = event as CloudEventEnvelope;
6357
return {
6458
EventBusName: eventBusName,
6559
Source: cloudEvent.source,
@@ -91,7 +85,7 @@ export async function sendEventsToEventBus(
9185

9286
// Wait before sending the next batch, but skip waiting after the last batch
9387
if (batch !== batches.at(-1)) {
94-
await wait(interval);
88+
await sleep(interval / 1000);
9589
}
9690
}
9791

scripts/nft-event-generator/src/generators/paper-letter-opt-out-events.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ function generatePaperLetterOptOutEvent(
2929
'customer/037f5f76-352c-445f-89a7-c3d18776ce86/message/3COesqsClaLyf0WNuLuhz1RDbWs/plan/3COezubdtrUFJlDOV4ucAQ93Akr',
3030
id: randomUUID(),
3131
time: new Date().toISOString(),
32-
source: '/nhs/england/notify/comms-mgr-test/test/data-plane/messaging',
32+
source: `/nhs/england/notify/comms-mgr-${environment}/${environment}/data-plane/messaging`,
3333
data: {
3434
messageReference,
3535
clientId: senderId,

scripts/nft-event-generator/src/generators/supplier-api-letter-events.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ function generateSupplierApiLetterEvent(
4545
time?: string;
4646
subject?: string;
4747
messageReference?: string;
48+
/* istanbul ignore next */
4849
} = {},
4950
): LetterEvent {
5051
const now = new Date();
@@ -96,18 +97,14 @@ export function generateSupplierApiLetterEvents({
9697
subject,
9798
time,
9899
}: GenerateEventsParams): LetterEvent[] {
99-
const events: LetterEvent[] = [];
100-
101-
for (let i = 0; i < numberOfEvents; i++) {
102-
events.push(
103-
generateSupplierApiLetterEvent(environment, status, {
104-
id,
105-
time,
106-
subject,
107-
messageReference,
108-
}),
109-
);
110-
}
100+
const events = Array.from({ length: numberOfEvents }, () =>
101+
generateSupplierApiLetterEvent(environment, status, {
102+
id,
103+
time,
104+
subject,
105+
messageReference,
106+
}),
107+
);
111108

112109
console.group('Event generation:');
113110
console.log(`Total events generated:\t\t${numberOfEvents}`);

scripts/nft-event-generator/src/utils/csv-reader.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,9 @@ export type PaperLetterOptOutRow = {
99
export function readCsvFile(filePath: string): PaperLetterOptOutRow[] {
1010
const fileContent = readFileSync(filePath, 'utf8');
1111

12-
const records = parse(fileContent, {
13-
columns: false,
12+
return parse(fileContent, {
13+
columns: ['messageReference', 'senderId'],
1414
skip_empty_lines: true,
1515
trim: true,
1616
});
17-
18-
return records.map((row) => ({
19-
messageReference: row[0],
20-
senderId: row[1],
21-
}));
2217
}

0 commit comments

Comments
 (0)