Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature(CB2-14229): Update atf-report-gen to remove snowball SQS pattern #130

Merged
merged 4 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 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 } 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: any, context?: Context, callback?: Callback): Promise<SQSBatchResponse> => {
Daniel-Searle marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -39,11 +40,15 @@ const reportGen: Handler = async (event: any, context?: Context, callback?: Call
await sendATFReport.sendATFReport(generationServiceResponse, visit);
console.debug("All emails sent, terminating lambda");
Daniel-Searle marked this conversation as resolved.
Show resolved Hide resolved
}

} 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);
});
});
});
Loading