Skip to content

Commit

Permalink
Feature(CB2-14229): Update atf-report-gen to remove snowball SQS patt…
Browse files Browse the repository at this point in the history
…ern (#130)

* feat(CB2-14255): snowball sqs

* feat(CB2-14229): snowball sqs

* feat(CB2-14229): snowball sqs

* feat(CB2-14229): pr comments
  • Loading branch information
Daniel-Searle authored Nov 11, 2024
1 parent c9ef8fa commit ef6a383
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 82 deletions.
20 changes: 11 additions & 9 deletions src/functions/reportGen.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { LambdaClient } from "@aws-sdk/client-lambda";
import { PutObjectRequest } from "@aws-sdk/client-s3";
import { unmarshall } from "@aws-sdk/util-dynamodb";
import { Callback, Context, Handler } from "aws-lambda";
import { Callback, Context, Handler, SQSBatchItemFailure, SQSBatchResponse, SQSEvent } from "aws-lambda";
import { ERRORS } from "../assets/enum";
import { ActivitiesService } from "../services/ActivitiesService";
import { LambdaService } from "../services/LambdaService";
Expand All @@ -16,18 +15,20 @@ import { ActivitySchema } from "@dvsa/cvs-type-definitions/types/v1/activity";
* @param context - λ Context
* @param callback - callback function
*/
const reportGen: Handler = async (event: any, context?: Context, callback?: Callback): Promise<void | PutObjectRequest[]> => {
const reportGen: Handler = async (event: SQSEvent, context?: Context, callback?: Callback): Promise<SQSBatchResponse> => {
if (!event || !event.Records || !Array.isArray(event.Records) || !event.Records.length) {
console.error("ERROR: event is not defined.");
throw new Error(ERRORS.EVENT_IS_EMPTY);
}
const batchItemFailures: SQSBatchItemFailure[] = [];

const lambdaService = new LambdaService(new LambdaClient({}));
const reportService: ReportGenerationService = new ReportGenerationService(new TestResultsService(lambdaService), new ActivitiesService(lambdaService));
const sendATFReport: SendATFReport = new SendATFReport();

console.debug("Services injected, looping over sqs events");
try {
for (const record of event.Records) {
for (const record of event.Records) {
try {
const recordBody = JSON.parse(record?.body);
const visit: ActivitySchema = unmarshall(recordBody?.dynamodb.NewImage) as ActivitySchema;

Expand All @@ -37,13 +38,14 @@ const reportGen: Handler = async (event: any, context?: Context, callback?: Call
const generationServiceResponse = await reportService.generateATFReport(visit);
console.debug(`Report generated: ${JSON.stringify(generationServiceResponse)}`);
await sendATFReport.sendATFReport(generationServiceResponse, visit);
console.debug("All emails sent, terminating lambda");
}

} catch (error) {
console.error(error);
batchItemFailures.push({ itemIdentifier: record.messageId });
}
} catch(error) {
console.error(error);
throw error;
}
return { batchItemFailures };
};

export { reportGen };
155 changes: 82 additions & 73 deletions tests/unit/reportGenFunction.unitTest.ts
Original file line number Diff line number Diff line change
@@ -1,106 +1,115 @@
const mockProcessRecord = jest.fn();

import { reportGen } from "../../src/functions/reportGen";
import { ReportGenerationService } from "../../src/services/ReportGenerationService";
import { SendATFReport } from "../../src/services/SendATFReport";
// import mockConfig from "../util/mockConfig";
import { reportGen } from "../../src/functions/reportGen";
import { ERRORS } from "../../src/assets/enum";

const mockProcessRecord = jest.fn();

jest.mock("../../src/services/ReportGenerationService");
jest.mock("../../src/services/SendATFReport");

const mockPayload = '{"eventID":"f9e63bf29bd6adf174e308201a97259f","eventName":"MODIFY","eventVersion":"1.1","eventSource":"aws:dynamodb","awsRegion":"eu-west-1","dynamodb":{"ApproximateCreationDateTime":1711549645,"Keys":{"id":{"S":"6e4bd304-446e-4678-8289-dasdasjkl"}},"NewImage":{"testerStaffId":{"S":"132"},"testStationPNumber":{"S":"87-1369564"},"testerEmail":{"S":"tester@dvsa.gov.uk1111"},"testStationType":{"S":"gvts"},"testStationEmail":{"S":"teststationname@dvsa.gov.uk"},"startTime":{"S":"2022-01-01T10:00:40.561Z"},"endTime":{"S":"2022-01-01T10:00:40.561Z"},"id":{"S":"6e4bd304-446e-4678-8289-dasdasjkl"},"testStationName":{"S":"Rowe, Wunsch and Wisoky"},"activityType":{"S":"visit"},"activityDay":{"S":"2022-01-01"},"testerName":{"S":"namey mcname"}},"OldImage":{"testerStaffId":{"S":"132"},"testStationPNumber":{"S":"87-1369564"},"testerEmail":{"S":"tester@dvsa.gov.uk1111"},"testStationType":{"S":"gvts"},"testStationEmail":{"S":"teststationname@dvsa.gov.uk"},"startTime":{"S":"2022-01-01T10:00:40.561Z"},"endTime":{"S":"2022-01-01T10:00:40.561Z"},"id":{"S":"6e4bd304-446e-4678-8289-dasdasjkl"},"testStationName":{"S":"Rowe, Wunsch and Wisoky"},"activityType":{"S":"visit"},"activityDay":{"S":"2022-01-01"},"testerName":{"S":"231232132"}},"SequenceNumber":"1234","SizeBytes":704,"StreamViewType":"NEW_AND_OLD_IMAGES"},"eventSourceARN":"arn:aws::eu--1::/cvs---//:32:37.491"}';
const mockSQSPayload = {
messageId: "059f36b4-87a3-44ab-83d2-661975830a7d",
receiptHandle: "AQEBwJnKyrHigUMZj6rYigCgxlaS3SLy0a",
body: JSON.stringify({
dynamodb: {
NewImage: {
testerStaffId: { S: "132" },
testStationPNumber: { S: "87-1369564" },
testerEmail: { S: "tester@dvsa.gov.uk1111" },
testStationType: { S: "gvts" },
testStationEmail: { S: "teststationname@dvsa.gov.uk" },
startTime: { S: "2022-01-01T10:00:40.561Z" },
endTime: { S: "2022-01-01T10:00:40.561Z" },
id: { S: "6e4bd304-446e-4678-8289-dasdasjkl" },
testStationName: { S: "Rowe, Wunsch and Wisoky" },
activityType: { S: "visit" },
activityDay: { S: "2022-01-01" },
testerName: { S: "Jon Mcdonald" }
}
}
}),
attributes: {
ApproximateReceiveCount: "1",
SentTimestamp: "1545082649183",
SenderId: "AIDAIENQZJOLO23YVJ4VO",
ApproximateFirstReceiveTimestamp: "1545082649185"
},
messageAttributes: {},
md5OfBody: "e4e68fb7bd0e697a0ae8f1bb342846b3",
eventSource: "aws:sqs",
eventSourceARN: "arn:aws:sqs:region:123456789012:queue",
awsRegion: "eu-west-1"
};

describe("Retro Gen Function", () => {
describe("Report Generation Lambda Function", () => {
beforeAll(() => jest.setTimeout(60000));
afterAll(() => {
jest.setTimeout(5000);
return new Promise((r) => setTimeout(r, 0));
});
// const ctx = mockContext();
// mockConfig();

const ctx = {};
context("Receiving an empty event (of various types)", () => {
it("should throw errors (event = {})", async () => {
expect.assertions(1);
try {
await reportGen({}, ctx as any, () => {
return;
});
} catch (e) {
expect(e.message).toEqual("Event is empty");
}

describe("When Receiving an invalid event", () => {
it("should throw an error when it is empty", async () => {
await expect(reportGen({}, ctx as any, () => {
return;
})).rejects.toThrow(ERRORS.EVENT_IS_EMPTY);
});
it("should throw errors (event = null)", async () => {
expect.assertions(1);
try {
await reportGen(null, ctx as any, () => {
return;
});
} catch (e) {
expect(e.message).toEqual("Event is empty");
}

it("should throw an error when the event is null", async () => {
await expect(reportGen(null, ctx as any, () => {
return;
})).rejects.toThrow(ERRORS.EVENT_IS_EMPTY);
});
it("should throw errors (event has no records)", async () => {
expect.assertions(1);
try {
await reportGen({ something: true }, ctx as any, () => {
return;
});
} catch (e) {
expect(e.message).toEqual("Event is empty");
}

it("should throw an error when the event has no records", async () => {
await expect(reportGen({ something: true }, ctx as any, () => {
return;
})).rejects.toThrow(ERRORS.EVENT_IS_EMPTY);
});
it("should throw errors (event Records is not array)", async () => {
expect.assertions(1);
try {
await reportGen({ Records: true }, ctx as any, () => {
return;
});
} catch (e) {
expect(e.message).toEqual("Event is empty");
}

it("should throw an error when the event records is not array", async () => {
await expect(reportGen({ Records: true }, ctx as any, () => {
return;
})).rejects.toThrow(ERRORS.EVENT_IS_EMPTY);
});
it("should throw errors (event Records array is empty)", async () => {
expect.assertions(1);
try {
await reportGen({ Records: [] }, ctx as any, () => {
return;
});
} catch (e) {
expect(e.message).toEqual("Event is empty");
}

it("should throw an error when the event records array is empty", async () => {
await expect(reportGen({ Records: [] }, ctx as any, () => {
return;
})).rejects.toThrow(ERRORS.EVENT_IS_EMPTY);
});
});

context("Inner services fail", () => {
describe("Inner services fail", () => {
afterEach(() => {
jest.restoreAllMocks();
});

it("Should throw an error (generateATFReport fails)", async () => {
it("Should add to batchItemFailures when generateATFReport fails", async () => {
ReportGenerationService.prototype.generateATFReport = jest.fn().mockRejectedValue(new Error("Oh no!"));
mockProcessRecord.mockReturnValueOnce("All good");
expect.assertions(1);
try {
await reportGen({ Records: [{ body: mockPayload }] }, ctx as any, () => {
return;
});
} catch (e) {
expect(e.message).toEqual("Oh no!");
}

const result = await reportGen({ Records: [mockSQSPayload] }, ctx as any, () => {
return;
});

expect(result.batchItemFailures).toHaveLength(1);
expect(result.batchItemFailures[0].itemIdentifier).toBe(mockSQSPayload.messageId);
});
it("Should throw an error (bucket upload fails)", async () => {

it("Should add to batchItemFailures when bucket upload fails", async () => {
ReportGenerationService.prototype.generateATFReport = jest.fn().mockResolvedValue("Looking good");
SendATFReport.prototype.sendATFReport = jest.fn().mockRejectedValue(new Error("Oh dear"));
mockProcessRecord.mockReturnValueOnce("All good");
expect.assertions(1);
try {
await reportGen({ Records: [{ body: mockPayload }] }, ctx as any, () => {
return;
});
} catch (e) {
expect(e.message).toEqual("Oh dear");
}

const result = await reportGen({ Records: [mockSQSPayload] }, ctx as any, () => {
return;
});
expect(result.batchItemFailures).toHaveLength(1);
expect(result.batchItemFailures[0].itemIdentifier).toBe(mockSQSPayload.messageId);
});
});
});

0 comments on commit ef6a383

Please sign in to comment.