Skip to content

Commit

Permalink
[MS-780] feat: More tests
Browse files Browse the repository at this point in the history
  • Loading branch information
piotrgrundas committed Oct 16, 2024
1 parent 38f5bc5 commit 57892b7
Show file tree
Hide file tree
Showing 7 changed files with 294 additions and 35 deletions.
24 changes: 2 additions & 22 deletions src/emails-sender.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,17 @@
import "./instrument.emails-sender";

import * as Sentry from "@sentry/aws-serverless";
import {
type Context,
type SQSBatchResponse,
type SQSEvent,
type SQSRecord,
} from "aws-lambda";
import { type Context, type SQSBatchResponse, type SQSEvent } from "aws-lambda";

import { CONFIG } from "@/config";
import { parseRecord } from "@/lib/aws/serverless/utils";
import { TEMPLATES_MAP } from "@/lib/emails/const";
import { EmailParsePayloadError } from "@/lib/emails/errors";
import { type SerializedPayload } from "@/lib/emails/events/helpers";
import { getJSONFormatHeader } from "@/lib/saleor/apps/utils";
import { getEmailProvider } from "@/providers/email";
import { getLogger } from "@/providers/logger";

export const logger = getLogger();

const parseRecord = (record: SQSRecord) => {
try {
// Proxy events has invalid types.
const data = JSON.parse((record as any).Body);
return data as SerializedPayload;
} catch (error) {
logger.error("Failed to parse record payload.", { record, error });

throw new EmailParsePayloadError("Failed to parse record payload.", {
cause: { source: error as Error },
});
}
};

export const handler = Sentry.wrapHandler(
async (event: SQSEvent, context: Context) => {
const failures: string[] = [];
Expand Down
56 changes: 56 additions & 0 deletions src/lib/aws/serverless/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { type SQSRecord } from "aws-lambda";
import { describe, expect, test, vi } from "vitest";

import { EmailParsePayloadError } from "@/lib/emails/errors";
import { getLogger } from "@/providers/logger"; // Mock the logger provider

import { parseRecord } from "./utils";

describe("utils", () => {
describe("parseRecord", () => {
vi.mock("@/providers/logger", () => {
const mockLogger = {
error: vi.fn(),
};

return {
getLogger: () => mockLogger,
};
});

test("should parse valid record and return data", () => {
// given
const data = {
event: "some_event",
data: { key: "value" },
};
const validRecord = {
Body: JSON.stringify(data),
} as any as SQSRecord;

// when
const result = parseRecord(validRecord);

// when
expect(result).toEqual(data);
});

test("should log and throw error when parsing fails", () => {
// given
const invalidRecord = {
Body: "{invalidJson",
} as any as SQSRecord;
const logger = getLogger();

// when & then
expect(() => parseRecord(invalidRecord)).toThrow(EmailParsePayloadError);
expect(logger.error).toHaveBeenCalledWith(
"Failed to parse record payload.",
expect.objectContaining({
record: invalidRecord,
error: expect.any(Error),
})
);
});
});
});
23 changes: 23 additions & 0 deletions src/lib/aws/serverless/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { type SQSRecord } from "aws-lambda";

import { EmailParsePayloadError } from "@/lib/emails/errors";
import { type SerializedPayload } from "@/lib/emails/events/helpers";
import { getLogger } from "@/providers/logger";

const logger = getLogger();

export const parseRecord = (record: SQSRecord) => {
try {
// Proxy events has invalid types.
const data = JSON.parse((record as any).Body);

return data as SerializedPayload;
} catch (error) {
logger.error("Failed to parse record payload.", { record, error });

// TODO: Should be non transient error
throw new EmailParsePayloadError("Failed to parse record payload.", {
cause: { source: error as Error },
});
}
};
108 changes: 108 additions & 0 deletions src/lib/zod/env.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { describe, expect, test } from "vitest";
import { z } from "zod";

import { envBool, envToStrList } from "./env";

describe("env", () => {
describe("envBool", () => {
test('should return true for "true"', () => {
// given
const input = "true";

// when
const result = envBool.parse(input);

// then
expect(result).toBe(true);
});

test('should return false for "false"', () => {
// given
const input = "false";

// when
const result = envBool.parse(input);

// then
expect(result).toBe(false);
});

test("should return false for an empty string", () => {
// given
const input = "";

// when
const result = envBool.parse(input);

// then
expect(result).toBe(false);
});

test("should throw an error for invalid values", () => {
// given
const input = "invalid";

// when / then
expect(() => envBool.parse(input)).toThrow(z.ZodError);
});
});

describe("envToStrList", () => {
test("should return an array of strings for a valid comma-separated string", () => {
// given
const input = "value1,value2,value3";

// when
const result = envToStrList(input);

// then
expect(result).toEqual(["value1", "value2", "value3"]);
});

test("should return an empty array when env is undefined and defaultEmpty is false", () => {
// given
const input = undefined;
const defaultEmpty = false;

// when
const result = envToStrList(input, defaultEmpty);

// then
expect(result).toEqual([]);
});

test("should return undefined when env is undefined and defaultEmpty is true", () => {
// given
const input = undefined;
const defaultEmpty = true;

// when
const result = envToStrList(input, defaultEmpty);

// then
expect(result).toBeUndefined();
});

test("should filter out empty values in a comma-separated string", () => {
// given
const input = "value1,,value3";

// when
const result = envToStrList(input);

// then
expect(result).toEqual(["value1", "value3"]);
});

test("should return an empty array when env is an empty string", () => {
// given
const input = "";

// when
const result = envToStrList(input);

// then
expect(result).toEqual([]);
});
});
});
104 changes: 104 additions & 0 deletions src/lib/zod/util.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { describe, expect, test } from "vitest";
import { z } from "zod";

import { prepareConfig } from "./util";

describe("utils", () => {
describe("prepareConfig", () => {
test("should return parsed config for valid input", () => {
// given
const schema = z.object({
key: z.string(),
});
const input = { key: "value" };

// when
const result = prepareConfig({ schema, input });

// then
expect(result).toEqual({ key: "value" });
});

test("should return parsed config from process.env", () => {
// given
const schema = z.object({
ENV_KEY: z.string(),
});
process.env.ENV_KEY = "env_value";

// when
const result = prepareConfig({ schema });

// then
expect(result).toEqual({ ENV_KEY: "env_value" });
});

test("should throw an error for invalid input", () => {
// given
const schema = z.object({
key: z.string(),
});
const input = { key: 123 }; // Invalid input (number instead of string)

// when / then
expect(() =>
prepareConfig({ schema, input, name: "TestConfig" })
).toThrow(
"Invalid TestConfig CONFIG\n\nkey: Expected string, received number"
);
});

test("should return empty object when serverOnly is true and window is defined", () => {
// given
const schema = z.object({
key: z.string(),
});
const input = { key: "value" };
global.window = {} as any; // Simulate client-side environment

// when
const result = prepareConfig({ schema, input, serverOnly: true });

// then
expect(result).toEqual({});

// @ts-ignore
delete global.window; // Clean up global window after test
});

test("should throw an error with multiple validation issues", () => {
// given
const schema = z.object({
key1: z.string(),
key2: z.number(),
});
const input = { key1: 123, key2: "invalid" }; // Both are invalid

// when / then
expect(() =>
prepareConfig({ schema, input, name: "MultiErrorConfig" })
).toThrow(
"Invalid MultiErrorConfig CONFIG\n\nkey1: Expected string, received number\nkey2: Expected number, received string"
);
});

test("should merge process.env and input values", () => {
// given
process.env.ENV_KEY = "env_value";
const schema = z.object({
ENV_KEY: z.string(),
inputKey: z.string(),
});
const input = { inputKey: "input_value" };

// when
const result = prepareConfig({ schema, input });

// then
expect(result).toEqual({
ENV_KEY: "env_value",
inputKey: "input_value",
});
});
});
});
13 changes: 0 additions & 13 deletions src/lib/zod/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,3 @@ export const prepareConfig = <Schema extends AnyZodSchema = AnyZodSchema>({

return parsedConfig.data;
};

export const envToStrList = (
env: string | undefined,
defaultEmpty = false
): string[] | undefined => {
const parsed = env?.split(",").filter(Boolean);

if (!parsed && !defaultEmpty) {
return [];
}

return parsed;
};
1 change: 1 addition & 0 deletions vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default defineConfig({
"src/graphql/schema.ts",
"src/**/*/generated.ts",
"src/**/*/types.ts",
"src/**/*/const.ts",
"src/**/*/tailwind.ts",
"src/emails-sender-proxy.ts",
"src/**/*/*.d.ts",
Expand Down

0 comments on commit 57892b7

Please sign in to comment.