Skip to content

Commit f1a154d

Browse files
committed
Tests seembroken, but wrote an sqs client
1 parent be5e9d9 commit f1a154d

9 files changed

Lines changed: 193 additions & 14 deletions

File tree

package-lock.json

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

packages/common/testing/src/mockEventBridgeEvent.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export const mockEventBridgeEvent = {
33
version: "0",
44
account: "123456789012",
55
time: new Date().toISOString(),
6-
region: "us-east-1",
6+
region: "eu-west-2",
77
resources: [],
88
source: "aws.events",
99
"detail-type": "Scheduled Event",
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/* eslint-disable no-undef */
2+
process.env.TABLE_NAME = "dummy_table";
3+
process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL = "dummy_notify_sqs";
4+
process.env.AWS_REGION = "eu-west-2";

packages/updatePrescriptionStatus/jest.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import defaultConfig from "../../jest.default.config"
33

44
const jestConfig: JestConfigWithTsJest = {
55
...defaultConfig,
6-
"rootDir": "./"
6+
"rootDir": "./",
7+
setupFiles: ["<rootDir>/.jest/setEnvVars.js"]
78
}
89

910
export default jestConfig

packages/updatePrescriptionStatus/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@aws-lambda-powertools/commons": "^2.17.0",
1818
"@aws-lambda-powertools/logger": "^2.18.0",
1919
"@aws-sdk/client-dynamodb": "^3.772.0",
20+
"@aws-sdk/client-sqs": "^3.787.0",
2021
"@aws-sdk/util-dynamodb": "^3.787.0",
2122
"@middy/core": "^6.1.6",
2223
"@middy/http-header-normalizer": "^6.1.6",

packages/updatePrescriptionStatus/src/updatePrescriptionStatus.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,12 @@ const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayPro
165165

166166
// This prescription was handled successfully,
167167
// so add a message to the notifications SQS
168-
pushPrescriptionToNotificationSQS(dataItems)
168+
try {
169+
await pushPrescriptionToNotificationSQS(dataItems, logger)
170+
} catch (err) {
171+
logger.error("Failed to push prescriptions to the notifications SQS", {err})
172+
// DO NOT throw an error here, since we want to still return the update!
173+
}
169174

170175
return response(201, responseEntries)
171176
}
Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,62 @@
1+
import {SQSClient, SendMessageBatchCommand} from "@aws-sdk/client-sqs"
2+
import {v4} from "uuid"
3+
14
import {Logger} from "@aws-lambda-powertools/logger"
25
import {DataItem} from "../updatePrescriptionStatus"
36

47
const sqsUrl = process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL
58

6-
export function pushPrescriptionToNotificationSQS(data: Array<DataItem>, logger: Logger) {
9+
// The AWS_REGION is always defined in lambda environments
10+
const sqs = new SQSClient({region: process.env.AWS_REGION})
11+
12+
// Returns the original array, chunked in batches of up to <size>
13+
function chunkArray<T>(arr: Array<T>, size: number): Array<Array<T>> {
14+
const chunks: Array<Array<T>> = []
15+
for (let i = 0; i < arr.length; i += size) {
16+
chunks.push(arr.slice(i, i + size))
17+
}
18+
return chunks
19+
}
20+
21+
/**
22+
* Pushes an array of DataItems to the notifications SQS queue
23+
* Uses SendMessageBatch to send up to 10 at a time
24+
*
25+
* @param data - Array of DataItems to send to SQS
26+
* @param logger - Logger instance
27+
*/
28+
export async function pushPrescriptionToNotificationSQS(data: Array<DataItem>, logger: Logger) {
729
logger.info("Pushing data items up to the notifications SQS", {data, sqsUrl})
30+
31+
if (!sqsUrl) {
32+
logger.error("Notifications SQS URL not found in environment variables")
33+
throw new Error("Notifications SQS URL not configured")
34+
}
35+
36+
// SQS batch calls are limited to 10 messages per request, so chunk the data
37+
const batches = chunkArray(data, 10)
38+
39+
for (const batch of batches) {
40+
// Create SQS entries. Each message is required to have an unique Id string.
41+
// TODO: I'm creating a new UUID here, but am I safe to use the lineID? It looks like it should be unique
42+
const entries = batch.map((item) => ({
43+
Id: v4().toUpperCase(),
44+
// Id: item.LineItemID || v4().toUpperCase(),
45+
MessageBody: JSON.stringify(item)
46+
}))
47+
48+
const params = {
49+
QueueUrl: sqsUrl,
50+
Entries: entries
51+
}
52+
53+
try {
54+
const command = new SendMessageBatchCommand(params)
55+
const result = await sqs.send(command)
56+
logger.info("Successfully sent a batch of prescriptions to the notifications SQS", {result})
57+
} catch (error) {
58+
logger.error("Failed to send a batch of prescriptions to the notifications SQS", {error})
59+
throw error
60+
}
61+
}
862
}

packages/updatePrescriptionStatus/tests/testHandler.test.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
generateExpectedItems,
1818
generateMockEvent,
1919
mockDynamoDBClient,
20+
mockSQSClient,
2021
TASK_VALUES
2122
} from "./utils/testUtils"
2223

@@ -36,7 +37,8 @@ import {
3637
} from "../src/utils/responses"
3738
import {QueryCommand, TransactionCanceledException, TransactWriteItemsCommand} from "@aws-sdk/client-dynamodb"
3839

39-
const {mockSend} = mockDynamoDBClient()
40+
const {mockSend: dynamoDBMockSend} = mockDynamoDBClient()
41+
const {mockSend: sqsMockSend} = mockSQSClient()
4042
const {handler, logger} = await import("../src/updatePrescriptionStatus")
4143
const LAMBDA_TIMEOUT_MS = 9500 // 9.5 sec
4244

@@ -75,7 +77,7 @@ describe("Integration tests for updatePrescriptionStatus handler", () => {
7577
expect(response.statusCode).toEqual(201)
7678
expect(JSON.parse(response.body)).toEqual(responseSingleItem)
7779

78-
expect(mockSend).toHaveBeenCalledWith(
80+
expect(dynamoDBMockSend).toHaveBeenCalledWith(
7981
expect.objectContaining(expectedItems)
8082
)
8183
})
@@ -101,7 +103,7 @@ describe("Integration tests for updatePrescriptionStatus handler", () => {
101103
expect(JSON.parse(response.body)).toEqual(responseSingleItem)
102104

103105
expect(expectedItems.input.TransactItems[0].Put.Item.RepeatNo).toEqual(undefined)
104-
expect(mockSend).toHaveBeenCalledWith(
106+
expect(dynamoDBMockSend).toHaveBeenCalledWith(
105107
expect.objectContaining(expectedItems)
106108
)
107109
})
@@ -126,7 +128,7 @@ describe("Integration tests for updatePrescriptionStatus handler", () => {
126128
expect(JSON.parse(response.body)).toEqual(responseSingleItem)
127129

128130
expect(expectedItems.input.TransactItems[0].Put.Item.RepeatNo).toEqual(1)
129-
expect(mockSend).toHaveBeenCalledWith(
131+
expect(dynamoDBMockSend).toHaveBeenCalledWith(
130132
expect.objectContaining(expectedItems)
131133
)
132134
})
@@ -141,7 +143,7 @@ describe("Integration tests for updatePrescriptionStatus handler", () => {
141143
expect(response.statusCode).toEqual(201)
142144
expect(JSON.parse(response.body)).toEqual(responseMultipleItems)
143145

144-
expect(mockSend).toHaveBeenCalledWith(
146+
expect(dynamoDBMockSend).toHaveBeenCalledWith(
145147
expect.objectContaining(expectedItems)
146148
)
147149
})
@@ -194,7 +196,7 @@ describe("Integration tests for updatePrescriptionStatus handler", () => {
194196

195197
it("when dynamo call fails, expect 500 status code and internal server error message", async () => {
196198
const event = generateMockEvent(requestDispatched)
197-
mockSend.mockRejectedValue(new Error() as never)
199+
dynamoDBMockSend.mockRejectedValue(new Error() as never)
198200

199201
const response: APIGatewayProxyResult = await handler(event, {})
200202

@@ -203,7 +205,7 @@ describe("Integration tests for updatePrescriptionStatus handler", () => {
203205
})
204206

205207
it("when data store update times out, expect 504 status code and relevant error message", async () => {
206-
mockSend.mockImplementation((command) => new Promise((resolve) => {
208+
dynamoDBMockSend.mockImplementation((command) => new Promise((resolve) => {
207209
if (!(command instanceof TransactWriteItemsCommand)) {
208210
resolve(false)
209211
}
@@ -279,7 +281,7 @@ describe("Integration tests for updatePrescriptionStatus handler", () => {
279281
const body = generateBody()
280282
const mockEvent: APIGatewayProxyEvent = generateMockEvent(body)
281283

282-
mockSend.mockRejectedValue(
284+
dynamoDBMockSend.mockRejectedValue(
283285
new TransactionCanceledException({
284286
message:
285287
"DynamoDB transaction cancelled due to conditional check failure.",
@@ -324,7 +326,7 @@ describe("Integration tests for updatePrescriptionStatus handler", () => {
324326
requestDuplicateItems
325327
)
326328

327-
mockSend.mockRejectedValue(
329+
dynamoDBMockSend.mockRejectedValue(
328330
new TransactionCanceledException({
329331
message:
330332
"DynamoDB transaction cancelled due to conditional check failure.",
@@ -375,7 +377,7 @@ describe("Integration tests for updatePrescriptionStatus handler", () => {
375377
const mockEvent: APIGatewayProxyEvent = generateMockEvent(body)
376378
const loggerSpy = jest.spyOn(logger, "info")
377379

378-
mockSend.mockImplementation(
380+
dynamoDBMockSend.mockImplementation(
379381
async (command) => {
380382
if (command instanceof QueryCommand) {
381383
return new Object({Items: [
@@ -407,4 +409,17 @@ describe("Integration tests for updatePrescriptionStatus handler", () => {
407409
}
408410
)
409411
})
412+
413+
it("when the notification SQS push fails, the response still succeeds", async () => {
414+
// TODO: I'm not convinced this is working...
415+
sqsMockSend.mockImplementation(
416+
async () => {
417+
throw new Error("Test error")
418+
}
419+
)
420+
421+
const event: APIGatewayProxyEvent = generateMockEvent(requestDispatched)
422+
const response: APIGatewayProxyResult = await handler(event, {})
423+
expect(response.statusCode).toBe(201)
424+
})
410425
})

packages/updatePrescriptionStatus/tests/utils/testUtils.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import {APIGatewayProxyEvent} from "aws-lambda"
44
import {jest} from "@jest/globals"
55
import * as dynamo from "@aws-sdk/client-dynamodb"
6+
import * as sqs from "@aws-sdk/client-sqs"
67

78
import {
89
LINE_ITEM_ID_CODESYSTEM,
@@ -180,3 +181,17 @@ export function mockDynamoDBClient() {
180181
})
181182
return {mockSend}
182183
}
184+
185+
// Similarly mock the SQS client
186+
export function mockSQSClient() {
187+
const mockSend = jest.fn()
188+
jest.unstable_mockModule("@aws-sdk/client-sqs", () => {
189+
return {
190+
...sqs,
191+
SQSClient: jest.fn().mockImplementation(() => ({
192+
send: mockSend
193+
}))
194+
}
195+
})
196+
return {mockSend}
197+
}

0 commit comments

Comments
 (0)