From 8000c226a11e3c7a3dce34acda25ec0a2d2eae04 Mon Sep 17 00:00:00 2001 From: Ethan Ma Date: Fri, 20 Dec 2024 17:34:28 -0600 Subject: [PATCH] TestCasesforGet & POST and DELETE for ticket --- src/routes/paidEvents.ts | 142 +++++++++++++++++++---- tests/unit/mockPaidEventData.testdata.ts | 77 ++++++++++++ tests/unit/paidEvents.test.ts | 79 +++++++++++++ 3 files changed, 275 insertions(+), 23 deletions(-) create mode 100644 tests/unit/mockPaidEventData.testdata.ts create mode 100644 tests/unit/paidEvents.test.ts diff --git a/src/routes/paidEvents.ts b/src/routes/paidEvents.ts index a1a4fd5..1a701fa 100644 --- a/src/routes/paidEvents.ts +++ b/src/routes/paidEvents.ts @@ -5,12 +5,15 @@ import { QueryCommand, ScanCommand, ConditionalCheckFailedException, + PutItemCommand, + DeleteItemCommand, } from "@aws-sdk/client-dynamodb"; import { genericConfig } from "../config.js"; -import { unmarshall } from "@aws-sdk/util-dynamodb"; +import { marshall, unmarshall } from "@aws-sdk/util-dynamodb"; import { DatabaseFetchError } from "../errors/index.js"; import { z } from "zod"; import { zodToJsonSchema } from "zod-to-json-schema"; +import { AppRoles } from "../roles.js"; const dynamoclient = new DynamoDBClient({ region: genericConfig.AwsRegion, @@ -28,22 +31,28 @@ type EventUpdateRequest = { Body: { attribute: string; value: string }; }; -/*const updateJsonSchema = zodToJsonSchema( - z.object({ - event_id: z.number(), - event_name: z.string(), - eventCost: z.record(z.number()), - eventDetails: z.optional(z.string()), - eventImage: z.optional(z.string()), - eventProps: z.record(z.string(), z.string()), - event_capacity: z.number(), - event_sales_active_utc: z.string(), - event_time: z.number(), - member_price: z.optional(z.string()), - nonmember_price: z.optional(z.string()), - tickets_sold: z.number() - }) -)*/ +type EventDeleteRequest = { + Params: { id: string }; + Querystring: undefined; + Body: undefined; +}; + +export const postTicketSchema = z.object({ + event_id: z.string(), + event_name: z.string(), + eventCost: z.record(z.number()), + eventDetails: z.optional(z.string()), + eventImage: z.optional(z.string()), + eventProps: z.optional(z.record(z.string(), z.string())), + event_capacity: z.number(), + event_sales_active_utc: z.string(), + event_time: z.number(), + member_price: z.optional(z.string()), + nonmember_price: z.optional(z.string()), + tickets_sold: z.number(), +}); + +type TicketPostSchema = z.infer; const responseJsonSchema = zodToJsonSchema( z.object({ @@ -214,20 +223,18 @@ const paidEventsPlugin: FastifyPluginAsync = async (fastify, _options) => { }); } catch (e: unknown) { if (e instanceof Error) { - request.log.error("Failed to get from DynamoDB: " + e.toString()); + request.log.error("Failed to update to DynamoDB: " + e.toString()); } if (e instanceof ConditionalCheckFailedException) { request.log.error("Attribute does not exist"); } throw new DatabaseFetchError({ - message: "Failed to get event from Dynamo table.", + message: "Failed to update event in Dynamo table.", }); } }, ); - //Multiple attribute udpates... - fastify.put( "/merchEvents/:id", { @@ -274,13 +281,102 @@ const paidEventsPlugin: FastifyPluginAsync = async (fastify, _options) => { }); } catch (e: unknown) { if (e instanceof Error) { - request.log.error("Failed to get from DynamoDB: " + e.toString()); + request.log.error("Failed to update to DynamoDB: " + e.toString()); } if (e instanceof ConditionalCheckFailedException) { request.log.error("Attribute does not exist"); } throw new DatabaseFetchError({ - message: "Failed to get event from Dynamo table.", + message: "Failed to update event in Dynamo table.", + }); + } + }, + ); + + fastify.post<{ Body: TicketPostSchema }>( + "/ticketEvents", + { + schema: { + response: { 200: responseJsonSchema }, + }, + preValidation: async (request, reply) => { + await fastify.zodValidateBody(request, reply, postTicketSchema); + }, + /*onRequest: async (request, reply) => { + await fastify.authorize(request, reply, [AppRoles.EVENTS_MANAGER]); + },*/ //validation taken off + }, + async (request: FastifyRequest<{ Body: TicketPostSchema }>, reply) => { + const id = request.body.event_id; + try { + //Verify if event_id already exists + const response = await dynamoclient.send( + new QueryCommand({ + TableName: genericConfig.TicketMetadataTableName, + KeyConditionExpression: "event_id = :id", + ExpressionAttributeValues: { + ":id": { S: id }, + }, + }), + ); + if (response.Items?.length != 0) { + throw new Error("Event_id already exists"); + } + const entry = { + ...request.body, + }; + await dynamoclient.send( + new PutItemCommand({ + TableName: genericConfig.TicketMetadataTableName, + Item: marshall(entry), + }), + ); + reply.send({ + id: id, + resource: `/api/v1/paidEvents/ticketEvents/${id}`, + }); + } catch (e: unknown) { + if (e instanceof Error) { + request.log.error("Failed to post to DynamoDB: " + e.toString()); + } + throw new DatabaseFetchError({ + message: "Failed to post event to Dynamo table.", + }); + } + }, + ); + + fastify.delete( + "/ticketEvents/:id", + { + schema: { + response: { 200: responseJsonSchema }, + }, + onRequest: async (request, reply) => { + await fastify.authorize(request, reply, [AppRoles.EVENTS_MANAGER]); + }, //auth + }, + async (request: FastifyRequest, reply) => { + const id = request.params.id; + try { + await dynamoclient.send( + new DeleteItemCommand({ + TableName: genericConfig.TicketMetadataTableName, + Key: { + event_id: { S: id }, + }, + }), + ); + reply.send({ + id: id, + resource: `/api/v1/paidEvents/ticketEvents/${id}`, + }); + } catch (e: unknown) { + if (e instanceof Error) { + request.log.error("Failed to delete from DynamoDB: " + e.toString()); + } + throw new DatabaseFetchError({ + message: "Failed to delete event from Dynamo table.", }); } }, diff --git a/tests/unit/mockPaidEventData.testdata.ts b/tests/unit/mockPaidEventData.testdata.ts new file mode 100644 index 0000000..b00b84d --- /dev/null +++ b/tests/unit/mockPaidEventData.testdata.ts @@ -0,0 +1,77 @@ +import { unmarshall } from "@aws-sdk/util-dynamodb"; + +const dynamoTableData = [ + { + event_id: { + S: "test_barcrawl", + }, + eventCost: { + M: { + others: { + N: "100", + }, + paid: { + N: "0", + }, + }, + }, + eventDetails: { + S: "Join ACM", + }, + eventImage: { + S: "img/test.png", + }, + eventProps: { + M: { + end: { + N: "", + }, + host: { + S: "", + }, + location: { + S: "", + }, + }, + }, + event_capacity: { + N: "130", + }, + event_name: { + S: "ACM Fall 2023 Bar Crawl", + }, + event_sales_active_utc: { + N: "0", + }, + event_time: { + N: "1699578000", + }, + member_price: { + S: "price_1O6zHhDiGOXU9RuSvlrcIfOv", + }, + nonmember_price: { + S: "price_1O6zHhDiGOXU9RuSvlrcIfOv", + }, + tickets_sold: { + N: "0", + }, + }, +]; + +const dynamoTableDataUnmarshalled = dynamoTableData.map((x: any) => { + const temp = unmarshall(x); + return temp; +}); + +const dynamoTableDataUnmarshalledUpcomingOnly = dynamoTableData + .map((x: any) => { + const temp = unmarshall(x); + return temp; + }) + .filter((x: any) => x.title != "Event in the past."); + +export { + dynamoTableData, + dynamoTableDataUnmarshalled, + dynamoTableDataUnmarshalledUpcomingOnly, +}; diff --git a/tests/unit/paidEvents.test.ts b/tests/unit/paidEvents.test.ts new file mode 100644 index 0000000..267aac9 --- /dev/null +++ b/tests/unit/paidEvents.test.ts @@ -0,0 +1,79 @@ +import { afterAll, expect, test, beforeEach, vi } from "vitest"; +import { + ScanCommand, + DynamoDBClient, + QueryCommand, +} from "@aws-sdk/client-dynamodb"; +import { mockClient } from "aws-sdk-client-mock"; +import init from "../../src/index.js"; +import { + dynamoTableData, + dynamoTableDataUnmarshalled, +} from "./mockPaidEventData.testdata.js"; +import { secretObject } from "./secret.testdata.js"; + +const ddbMock = mockClient(DynamoDBClient); +const jwt_secret = secretObject["jwt_key"]; +vi.stubEnv("JwtSigningKey", jwt_secret); + +const app = await init(); +test("Test paidEvents up", async () => { + const response = await app.inject({ + method: "GET", + url: "/api/v1/paidEvents", + }); + expect(response.statusCode).toBe(200); + const responseDataJson = await response.json(); + expect(responseDataJson).toEqual({ Status: "Up" }); +}); + +test("Test paidEvents get ticketEvents", async () => { + ddbMock.on(ScanCommand).resolves({ + Items: dynamoTableData as any, + }); + const response = await app.inject({ + method: "GET", + url: "/api/v1/paidEvents/ticketEvents", + }); + expect(response.statusCode).toBe(200); + const responseDataJson = await response.json(); + expect(responseDataJson).toEqual(dynamoTableDataUnmarshalled); +}); + +test("Test paidEvents get ticketEvents by id", async () => { + ddbMock.on(QueryCommand).resolves({ + Items: dynamoTableData as any, + }); + const response = await app.inject({ + method: "GET", + url: "/api/v1/paidEvents/ticketEvents/test_barcrawl", + }); + expect(response.statusCode).toBe(200); + const responseDataJson = await response.json(); + expect(responseDataJson).toEqual(dynamoTableDataUnmarshalled[0]); //[0] since unmarshalled gives an array +}); + +test("Test dynamodb error handling", async () => { + ddbMock.onAnyCommand().rejects("Nope"); + const response = await app.inject({ + method: "GET", + url: "/api/v1/paidEvents/ticketEvents", + }); + expect(response.statusCode).toBe(500); + const responseDataJson = await response.json(); + expect(responseDataJson).toEqual({ + error: true, + name: "DatabaseFetchError", + id: 106, + message: "Failed to get events from Dynamo table.", + }); +}); + +afterAll(async () => { + await app.close(); + vi.useRealTimers(); +}); +beforeEach(() => { + ddbMock.reset(); + vi.useFakeTimers(); +});