Skip to content

Commit

Permalink
Merge pull request #29 from fterh/notify-errors
Browse files Browse the repository at this point in the history
Notify user of errors
  • Loading branch information
fterh authored Feb 12, 2020
2 parents ee9d60f + c25f5e3 commit b35f936
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 23 deletions.
52 changes: 38 additions & 14 deletions functions/receipt.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,61 @@
"use strict";

import { S3 } from "aws-sdk";
import { S3Event } from "aws-lambda";
import { simpleParser } from "mailparser";
import { email, operationalDomain } from "../lib/env";
import extractEmailAliases from "../lib/extractEmailAliases";
import processAliases from "../lib/processAliases";
import sendEmail from "../lib/sendEmail";

export const handler = async (event: S3Event) => {
console.log(`Received incoming email; key=${event.Records[0].s3.object.key}`);
const getRecordData = async (
request: S3.GetObjectRequest,
s3: S3
): Promise<Buffer> => {
console.log("Fetching email from S3 storage");
return (await s3.getObject(request).promise()).Body as Buffer;
};

const deleteRecord = async (
request: S3.DeleteObjectRequest,
s3: S3
): Promise<void> => {
console.log("Deleting email from S3 storage");
await s3.deleteObject(request).promise();
console.log("Deleted email from S3 storage");
};

const notifyUserOfError = async (err: any): Promise<void> => {
if (err instanceof Error) {
console.error(err, err.stack);
} else {
console.error(err);
}

const s3 = new S3({
apiVersion: "2006-03-01",
region: process.env.AWSREGION // TODO check if this should be AWS_REGION instead
await sendEmail({
from: `heimdall@${operationalDomain}`,
to: email,
subject: "Oops, something went wrong!",
text: `An error has occurred:\n\n${err}\n\nCheck the logs for more information.`
});
};

export const handler = async (event: S3Event) => {
const record = event.Records[0];
console.log(`Received incoming email (key=${record.s3.object.key})`);

const s3 = new S3();
const request = {
Bucket: record.s3.bucket.name,
Key: record.s3.object.key
};

try {
const data = (await s3.getObject(request).promise()).Body as Buffer;
const data = await getRecordData(request, s3);
const email = await simpleParser(data);
const aliases = extractEmailAliases(email);
await processAliases(aliases, email);

// Delete the email from S3 only after successfully processing it
console.log("Deleting email from S3 storage");
await s3.deleteObject(request).promise();
console.log("Deleted email from S3 storage");
await deleteRecord(request, s3);
} catch (err) {
console.error(err);
return err;
await notifyUserOfError(err);
}
};
1 change: 0 additions & 1 deletion lib/__mocks__/processAliases.ts

This file was deleted.

43 changes: 35 additions & 8 deletions tests/functions/receipt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import path from "path";
import { S3EventRecord } from "aws-lambda";
import * as AWSMock from "aws-sdk-mock";
import { handler } from "../../functions/receipt";
import { email, operationalDomain } from "../../lib/env";
import processAliases from "../../lib/processAliases";
import sendEmail from "../../lib/sendEmail";

type Callback = (err: any, data: any) => void;

Expand All @@ -12,13 +15,24 @@ jest.mock("mailparser", () => {
}
};
});
jest.mock("../../lib/commands");
jest.mock("../../lib/extractEmailAliases");
jest.mock("../../lib/forwardInboundOrOutbound");
jest.mock("../../lib/processAliases");
jest.mock("../../lib/sendEmail");

const _processAliases = processAliases as jest.Mock;
const _sendEmail = sendEmail as jest.Mock;

const deleteObjectSpy = jest.fn();

beforeEach(() => {
// Fix: https://github.com/dwyl/aws-sdk-mock/issues/145
AWSMock.setSDK(path.resolve(__dirname, "../../node_modules/aws-sdk"));

AWSMock.mock("S3", "getObject", { Body: Buffer.from("test data") });
AWSMock.mock("S3", "deleteObject", (params: any, callback: Callback) => {
deleteObjectSpy();
callback(null, null);
});
});

afterEach(() => {
Expand Down Expand Up @@ -62,13 +76,26 @@ const testS3EventRecord: S3EventRecord = {
};

it("should delete the email after successfully processing it", async () => {
const spy = jest.fn();
AWSMock.mock("S3", "getObject", { Body: Buffer.from("test data") });
AWSMock.mock("S3", "deleteObject", (params: any, callback: Callback) => {
spy();
callback(null, null);
await handler({ Records: [testS3EventRecord] });

expect(deleteObjectSpy).toHaveBeenCalledTimes(1);
});

it("should not delete the email and also notify the user in the event of errors", async () => {
_processAliases.mockImplementation(async () => {
throw new Error();
});

await handler({ Records: [testS3EventRecord] });
expect(spy).toHaveBeenCalledTimes(1);

expect(deleteObjectSpy).not.toBeCalled();
expect(_sendEmail).toHaveBeenCalledTimes(1);
expect(_sendEmail.mock.calls[0][0].from).toBe(
`heimdall@${operationalDomain}`
);
expect(_sendEmail.mock.calls[0][0].to).toBe(email);
expect(_sendEmail.mock.calls[0][0].subject).toBe(
"Oops, something went wrong!"
);
expect(_sendEmail.mock.calls[0][0].text).toContain("An error has occurred:");
});

0 comments on commit b35f936

Please sign in to comment.