Skip to content

Commit 606f13d

Browse files
authored
Merge branch 'main' into feature/CCM-14499_pin_github_actions_to_shas
2 parents 581339e + badeeeb commit 606f13d

83 files changed

Lines changed: 1300 additions & 729 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
code,description
2+
DL_PDMV_001,Letter rejected by PDM
3+
DL_PDMV_002,Timeout waiting for letter storage
4+
DL_CLIV_003,Attachment contains a virus
5+
DL_INTE_001,Request rejected by Core API
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
resource "aws_glue_catalog_table" "failure_code_lookup" {
2+
name = "failure_code_lookup"
3+
description = "Lookup table for failure code descriptions"
4+
database_name = aws_glue_catalog_database.reporting.name
5+
6+
table_type = "EXTERNAL_TABLE"
7+
8+
storage_descriptor {
9+
location = "s3://${module.s3bucket_reporting.bucket}/reference-data/failure_codes/"
10+
11+
input_format = "org.apache.hadoop.mapred.TextInputFormat"
12+
output_format = "org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat"
13+
14+
ser_de_info {
15+
name = "csv"
16+
serialization_library = "org.apache.hadoop.hive.serde2.OpenCSVSerde"
17+
18+
parameters = {
19+
"separatorChar" = ","
20+
"skip.header.line.count" = "1"
21+
}
22+
}
23+
24+
columns {
25+
name = "code"
26+
type = "string"
27+
}
28+
29+
columns {
30+
name = "description"
31+
type = "string"
32+
}
33+
}
34+
35+
parameters = {
36+
EXTERNAL = "TRUE"
37+
classification = "csv"
38+
}
39+
}

infrastructure/terraform/components/dl/module_sqs_report_generator.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ module "sqs_report_generator" {
1010

1111
sqs_kms_key_arn = module.kms.key_arn
1212

13-
visibility_timeout_seconds = var.sqs_visibility_timeout_seconds
13+
visibility_timeout_seconds = 360
1414

1515
create_dlq = true
1616

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Auto-generated CSV containing failure code definitions
2+
# Source: src/digital-letters-events/failure-codes.ts
3+
# Build: make build / make generate (runs generate-dependencies)
4+
resource "aws_s3_object" "failure_codes" {
5+
bucket = module.s3bucket_reporting.bucket
6+
key = "reference-data/failure_codes/failure_codes.csv"
7+
source = "${path.module}/data/failure_codes.csv"
8+
content_type = "text/csv"
9+
etag = filemd5("${path.module}/data/failure_codes.csv")
10+
11+
tags = merge(
12+
local.default_tags,
13+
{
14+
Name = "${local.csi}-failure-codes-csv"
15+
}
16+
)
17+
}

infrastructure/terraform/components/dl/scripts/sql/reports/daily_report.sql

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,35 @@ WITH vars AS (
77
e.time,
88
CASE
99
WHEN e.type LIKE '%.item.dequeued.%'
10-
OR e.type LIKE '%.queue.digital.letter.read.%' THEN 'Digital'
11-
WHEN e.type LIKE '%.print.letter.transitioned.%' THEN 'Print' ELSE NULL
10+
OR e.type LIKE '%.queue.digital.letter.read.%'
11+
OR e.type LIKE '%.pdm.resource.submission.rejected.%'
12+
OR e.type LIKE '%.pdm.resource.retries.exceeded.%'
13+
OR e.type LIKE '%.messages.request.rejected.%' THEN 'Digital'
14+
WHEN e.type LIKE '%.print.letter.transitioned.%'
15+
OR e.type LIKE '%.print.file.quarantined.%' THEN 'Print' ELSE NULL
1216
END as communicationtype,
1317
CASE
1418
WHEN e.type LIKE '%.item.dequeued.%' THEN 'Unread'
1519
WHEN e.type LIKE '%.queue.digital.letter.read.%' THEN 'Read'
20+
WHEN e.type LIKE '%.pdm.resource.submission.rejected.%' THEN 'Failed'
21+
WHEN e.type LIKE '%.pdm.resource.retries.exceeded.%' THEN 'Failed'
22+
WHEN e.type LIKE '%.messages.request.rejected.%' THEN 'Failed'
23+
WHEN e.type LIKE '%.print.file.quarantined.%' THEN 'Failed'
1624
WHEN e.letterstatus = 'RETURNED' THEN 'Returned'
1725
WHEN e.letterstatus = 'FAILED' THEN 'Failed'
1826
WHEN e.letterstatus = 'DISPATCHED' THEN 'Dispatched'
1927
WHEN e.letterstatus = 'REJECTED' THEN 'Rejected' ELSE NULL
20-
END as status
28+
END as status,
29+
e.reasoncode,
30+
COALESCE(
31+
CASE WHEN e.type LIKE '%.messages.request.rejected.%' THEN e.reasontext END,
32+
fcl.description,
33+
e.reasontext,
34+
e.reasoncode
35+
) as reasontext
2136
FROM event_record e
2237
CROSS JOIN vars v
38+
LEFT JOIN failure_code_lookup fcl ON e.reasoncode = fcl.code
2339
WHERE e.senderid = v.senderid
2440
AND e.__year = year(v.dt)
2541
AND e.__month = month(v.dt)
@@ -31,25 +47,31 @@ WITH vars AS (
3147
ORDER BY te.time DESC,
3248
CASE
3349
-- Digital Priority Order
50+
WHEN te.communicationtype = 'Digital' AND te.status = 'Failed' THEN 3
3451
WHEN te.status = 'Read' THEN 2
3552
WHEN te.status = 'Unread' THEN 1
3653
-- Print Priority Order
3754
WHEN te.status = 'Returned' THEN 4
38-
WHEN te.status = 'Failed' THEN 3
55+
WHEN te.communicationtype = 'Print' AND te.status = 'Failed' THEN 3
3956
WHEN te.status = 'Dispatched' THEN 2
4057
WHEN te.status = 'Rejected' THEN 1 ELSE 0
4158
END DESC
4259
) AS "row_number",
4360
te.messagereference,
4461
te.time,
4562
te.communicationtype,
46-
te.status
63+
te.status,
64+
te.reasoncode,
65+
te.reasontext
4766
FROM "translated_events" AS te
48-
where te.status IS NOT NULL
67+
WHERE te.status IS NOT NULL
68+
AND te.communicationtype IS NOT NULL
4969
)
5070
SELECT oe.messagereference as "Message Reference",
5171
oe.time as "Time",
5272
oe.communicationtype as "Communication Type",
53-
oe.status as "Status"
73+
oe.status as "Status",
74+
oe.reasoncode as "Reason Code",
75+
oe.reasontext as "Reason"
5476
FROM "ordered_events" AS oe
5577
WHERE oe.row_number = 1

lambdas/core-notifier-lambda/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"dependencies": {
3-
"axios": "^1.13.5",
3+
"axios": "1.14.0",
44
"digital-letters-events": "^0.0.1",
55
"sender-management": "^0.0.1",
66
"utils": "^0.0.1"

lambdas/core-notifier-lambda/src/__tests__/apis/sqs-handler.test.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import { NotifyMessageProcessor } from 'app/notify-message-processor';
55
import { ISenderManagement } from 'sender-management';
66
import { SqsHandlerDependencies, createHandler } from 'apis/sqs-handler';
77
import { parseSqsRecord } from 'app/parse-sqs-message';
8-
import { InvalidPdmResourceAvailableEvent } from 'domain/invalid-pdm-resource-available-event';
98
import { RequestNotifyError } from 'domain/request-notify-error';
109
import { validPdmEvent, validSender } from '__tests__/constants';
1110
import {
11+
InvalidEvent,
1212
MessageRequestRejected,
1313
MessageRequestSkipped,
1414
MessageRequestSubmitted,
@@ -195,14 +195,14 @@ describe('createHandler', () => {
195195
});
196196
});
197197

198-
describe('when parseSqsRecord throws InvalidPdmResourceAvailableEvent', () => {
198+
describe('when parseSqsRecord throws InvalidEvent', () => {
199199
it('marks the message as failed for retry', async () => {
200200
const sqsEvent = createSqsEvent(1);
201201
const handler = createHandler(dependencies);
202202
const { messageId } = sqsEvent.Records[0];
203203

204204
mockParseSqsRecord.mockImplementationOnce(() => {
205-
throw new InvalidPdmResourceAvailableEvent(messageId);
205+
throw new InvalidEvent('Some validation errors');
206206
});
207207

208208
const result = await handler(sqsEvent);
@@ -211,7 +211,7 @@ describe('createHandler', () => {
211211
batchItemFailures: [{ itemIdentifier: messageId }],
212212
});
213213
expect(mockLogger.warn).toHaveBeenCalledWith({
214-
error: 'Unable to parse PDMResourceAvailable event from SQS message',
214+
error: 'Unable to parse event',
215215
description: 'Failed processing message',
216216
messageId,
217217
senderId: undefined,
@@ -256,11 +256,13 @@ describe('createHandler', () => {
256256
const handler = createHandler(dependencies);
257257
const { messageId } = sqsEvent.Records[0];
258258
const errorCode = 'VALIDATION_ERROR';
259+
const failureReason = 'Request validation failed';
259260
const correlationId = 'corr-123';
260261
const error = new RequestNotifyError(
261262
new Error('Validation failed'),
262263
correlationId,
263264
errorCode,
265+
failureReason,
264266
);
265267
// Add messageReference property dynamically to trigger the terminal error path
266268
(error as any).messageReference = messageReference;

lambdas/core-notifier-lambda/src/__tests__/app/notify-api-client.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,11 @@ describe('sendRequest', () => {
211211
mockRequest1.data.attributes.messageReference,
212212
),
213213
).rejects.toMatchObject({
214-
errorCode: 'CM_MISSING_ROUTING_PLAN_TEMPLATE',
214+
cause: error,
215215
correlationId: 'request-item-id_request-item-plan-id',
216+
errorCode: 'CM_MISSING_ROUTING_PLAN_TEMPLATE',
217+
failureReason:
218+
'The templates required to use the routing plan were not found.',
216219
});
217220
},
218221
);

lambdas/core-notifier-lambda/src/__tests__/app/parse-sqs-message.test.ts

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import type { SQSRecord } from 'aws-lambda';
22
import { mock } from 'jest-mock-extended';
33
import { Logger } from 'utils';
44
import { parseSqsRecord } from 'app/parse-sqs-message';
5-
import { InvalidPdmResourceAvailableEvent } from 'domain/invalid-pdm-resource-available-event';
65
import { validPdmEvent } from '__tests__/constants';
6+
import { InvalidEvent } from 'digital-letters-events';
77

88
const mockLogger = mock<Logger>();
9+
const mockChildLogger = mock<Logger>();
910

1011
describe('parseSqsRecord', () => {
1112
const messageId = 'test-message-id-123';
@@ -27,6 +28,10 @@ describe('parseSqsRecord', () => {
2728
awsRegion: 'eu-west-2',
2829
});
2930

31+
beforeAll(() => {
32+
mockLogger.child.mockReturnValue(mockChildLogger);
33+
});
34+
3035
beforeEach(() => {
3136
jest.clearAllMocks();
3237
});
@@ -38,13 +43,12 @@ describe('parseSqsRecord', () => {
3843
const result = parseSqsRecord(sqsRecord, mockLogger);
3944

4045
expect(result).toEqual(validPdmEvent);
41-
expect(mockLogger.info).toHaveBeenCalledWith({
46+
expect(mockLogger.child).toHaveBeenCalledWith({ messageId });
47+
expect(mockChildLogger.info).toHaveBeenCalledWith({
4248
description: 'Parsing SQS Record',
43-
messageId,
4449
});
45-
expect(mockLogger.info).toHaveBeenCalledWith({
50+
expect(mockChildLogger.info).toHaveBeenCalledWith({
4651
description: 'Parsed valid PDMResourceAvailable Event',
47-
messageId,
4852
messageReference: validPdmEvent.data.messageReference,
4953
senderId: validPdmEvent.data.senderId,
5054
resourceId: validPdmEvent.data.resourceId,
@@ -53,20 +57,16 @@ describe('parseSqsRecord', () => {
5357
});
5458

5559
describe('when SQS record contains an invalid PDMResourceAvailable event', () => {
56-
it('logs error and throws InvalidPdmResourceAvailableEvent', () => {
60+
it('logs error and throws InvalidEvent', () => {
5761
const invalidEvent = { ...validPdmEvent, data: {} };
5862
const sqsRecord = createSqsRecord(invalidEvent);
5963

60-
expect(() => parseSqsRecord(sqsRecord, mockLogger)).toThrow(
61-
InvalidPdmResourceAvailableEvent,
62-
);
63-
64-
expect(mockLogger.error).toHaveBeenCalledWith(
64+
expect(() => parseSqsRecord(sqsRecord, mockLogger)).toThrow(InvalidEvent);
65+
expect(mockLogger.child).toHaveBeenCalledWith({ messageId });
66+
expect(mockChildLogger.error).toHaveBeenCalledWith(
6567
expect.objectContaining({
66-
description:
67-
'The SQS message does not contain a valid PDMResourceAvailable event',
68-
messageId,
69-
error: expect.any(Array),
68+
description: 'Error parsing PDMResourceAvailable event',
69+
err: expect.any(Array),
7070
}),
7171
);
7272
});
@@ -92,9 +92,8 @@ describe('parseSqsRecord', () => {
9292
};
9393

9494
expect(() => parseSqsRecord(sqsRecord, mockLogger)).toThrow(SyntaxError);
95-
expect(mockLogger.info).toHaveBeenCalledWith({
95+
expect(mockChildLogger.info).toHaveBeenCalledWith({
9696
description: 'Parsing SQS Record',
97-
messageId,
9897
});
9998
});
10099
});

0 commit comments

Comments
 (0)