diff --git a/src/functions/item/__tests__/create.test.js b/src/functions/item/__tests__/create.test.js new file mode 100644 index 0000000..220e8c0 --- /dev/null +++ b/src/functions/item/__tests__/create.test.js @@ -0,0 +1,66 @@ +import { describe, expect, it } from "@jest/globals"; + +import { handler } from "../create"; + +describe("CREATE Item", () => { + it("receives an empty body", async () => { + const response = await handler({ + headers: { + "Content-Type": "application/json", + }, + body: null, + }); + + expect(response).toStrictEqual({ + statusCode: 400, + headers: { "Content-Type": "application/json" }, + body: '{"issues":[{"code":"invalid_type","expected":"object","received":"null","path":[],"message":"Expected object, received null"}],"name":"ZodError"}', + }); + }); + + it("creates a new item", async () => { + const event = { + headers: { + "Content-Type": "application/json", + }, + pathParameters: { listId: "1" }, + body: JSON.stringify({ + name: "My Wedding Item", + description: "This is my wedding gift item.", + price: 15, + }), + }; + const response = await handler(event); + + expect(response.statusCode).toBe(200); + + const body = JSON.parse(response.body); + + expect(body.id).toMatch(/^[A-Z0-9]{26}$/); + expect(body.listId).toBe("1"); + expect(body.name).toBe("My Wedding Item"); + expect(body.description).toBe("This is my wedding gift item."); + expect(body.price).toBe(15); + + const dateRegExp = + /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3}Z$/; + expect(body.createdAt).toMatch(dateRegExp); + expect(body.updatedAt).toMatch(dateRegExp); + }); + + it("does not received all required properties", async () => { + const event = { + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ param: "1" }), + }; + const response = await handler(event); + + expect(response).toStrictEqual({ + statusCode: 400, + headers: { "Content-Type": "application/json" }, + body: '{"issues":[{"code":"invalid_type","expected":"string","received":"undefined","path":["name"],"message":"Required"},{"code":"invalid_type","expected":"string","received":"undefined","path":["description"],"message":"Required"},{"code":"invalid_type","expected":"number","received":"undefined","path":["price"],"message":"Required"}],"name":"ZodError"}', + }); + }); +}); diff --git a/src/functions/item/create.ts b/src/functions/item/create.ts new file mode 100644 index 0000000..cce87fe --- /dev/null +++ b/src/functions/item/create.ts @@ -0,0 +1,36 @@ +import middy from "@middy/core"; +import httpErrorHandler from "@middy/http-error-handler"; +import httpJsonBodyParser from "@middy/http-json-body-parser"; +import { APIGatewayProxyEventV2, Handler } from "aws-lambda"; +import { z } from "zod"; + +import { Item } from "../../models/table"; +import { ValidationError } from "../../utils/validationError"; + +const createItemHandler: Handler = async (event) => { + const itemSchema = z.object({ + name: z.string(), + description: z.string(), + price: z.number(), + }) satisfies z.ZodType; + + const item = itemSchema.safeParse(event.body); + if (!item.success) { + throw new ValidationError(400, item.error); + } + + const createdItem = await Item.create({ + listId: event.pathParameters?.listId, + ...item.data, + }); + + return { + statusCode: 200, + body: JSON.stringify(createdItem), + }; +}; + +export const handler = middy() + .use(httpErrorHandler({ logger: false })) + .use(httpJsonBodyParser()) + .handler(createItemHandler);