From 62a917c7a8b5037f7d7d4e13d41c53347d788bfd Mon Sep 17 00:00:00 2001 From: Agusti Fernandez Pardo Date: Sat, 26 Mar 2022 02:16:46 +0100 Subject: [PATCH 1/2] renames team -> teams adds tests for users and teams --- __tests__/teams/[id]/team.id.test.edit.ts | 93 +++++++++++++++++++++ __tests__/teams/[id]/team.id.test.index.ts | 80 ++++++++++++++++++ __tests__/users/[id]/user.id.test.edit.ts | 95 ++++++++++++++++++++++ __tests__/users/[id]/user.id.test.index.ts | 80 ++++++++++++++++++ jest.config.ts | 4 +- pages/api/api-keys/[id]/delete.ts | 4 +- pages/api/{team => teams}/[id]/delete.ts | 0 pages/api/{team => teams}/[id]/edit.ts | 2 +- pages/api/{team => teams}/[id]/index.ts | 0 pages/api/{team => teams}/index.ts | 0 pages/api/{team => teams}/new.ts | 0 pages/api/users/[id]/edit.ts | 2 +- 12 files changed, 355 insertions(+), 5 deletions(-) create mode 100644 __tests__/teams/[id]/team.id.test.edit.ts create mode 100644 __tests__/teams/[id]/team.id.test.index.ts create mode 100644 __tests__/users/[id]/user.id.test.edit.ts create mode 100644 __tests__/users/[id]/user.id.test.index.ts rename pages/api/{team => teams}/[id]/delete.ts (100%) rename pages/api/{team => teams}/[id]/edit.ts (97%) rename pages/api/{team => teams}/[id]/index.ts (100%) rename pages/api/{team => teams}/index.ts (100%) rename pages/api/{team => teams}/new.ts (100%) diff --git a/__tests__/teams/[id]/team.id.test.edit.ts b/__tests__/teams/[id]/team.id.test.edit.ts new file mode 100644 index 0000000000..7c81bf5dde --- /dev/null +++ b/__tests__/teams/[id]/team.id.test.edit.ts @@ -0,0 +1,93 @@ +import handleTeamEdit from "@api/teams/[id]/edit"; +import { createMocks } from "node-mocks-http"; + +import prisma from "@calcom/prisma"; + +describe("PATCH /api/teams/[id]/edit with valid id and body updates a team", () => { + it("returns a message with the specified teams", async () => { + const { req, res } = createMocks({ + method: "PATCH", + query: { + id: "1", + }, + body: { + name: "Updated team", + slug: "updated-team", + }, + }); + const team = await prisma.team.findUnique({ where: { id: parseInt(req.query.id) } }); + await handleTeamEdit(req, res); + + expect(res._getStatusCode()).toBe(200); + // if (team) team.name = "Updated name"; + expect(JSON.parse(res._getData())).toStrictEqual({ data: team }); + }); +}); + +describe("PATCH /api/teams/[id]/edit with invalid id returns 404", () => { + it("returns a message with the specified teams", async () => { + const { req, res } = createMocks({ + method: "PATCH", + query: { + id: "0", + }, + body: { + name: "Updated name", + slug: "updated-slug", + }, + }); + const team = await prisma.team.findUnique({ where: { id: parseInt(req.query.id) } }); + await handleTeamEdit(req, res); + + expect(res._getStatusCode()).toBe(404); + expect(JSON.parse(res._getData())).toStrictEqual({ "error": { + "clientVersion": "3.10.0", + "code": "P2025", + "meta": { + "cause": "Record to update not found.", + }, + }, + "message": "Event type with ID 0 not found and wasn't updated", }); + }); +}); + +describe("PATCH /api/teams/[id]/edit with valid id and no body returns 400 error and zod validation errors", () => { + it("returns a message with the specified teams", async () => { + const { req, res } = createMocks({ + method: "PATCH", + query: { + id: "2", + }, + }); + await handleTeamEdit(req, res); + + expect(res._getStatusCode()).toBe(400); + + // Ugly parsing of zod validation errors, not for final production but works for testing + expect(JSON.parse(res._getData())).toStrictEqual([{"code": "invalid_type", "expected": "string", "message": "Required", "path": ["slug"], "received": "undefined"}, {"code": "invalid_type", "expected": "string", "message": "Required", "path": ["name"], "received": "undefined"}]); + }); +}); + +describe("POST /api/teams/[id]/edit fails, only PATCH allowed", () => { + it("returns a message with the specified teams", async () => { + const { req, res } = createMocks({ + method: "POST", // This POST method is not allowed + query: { + id: "1", + }, + body: { + name: "Updated name", + slug: "updated-slug", + }, + }); + await handleTeamEdit(req, res); + + expect(res._getStatusCode()).toBe(405); + expect(JSON.parse(res._getData())).toStrictEqual({ message: "Only PATCH Method allowed for updating teams" }); + }); +}); + +afterAll((done) => { + prisma.$disconnect().then(); + done(); +}); diff --git a/__tests__/teams/[id]/team.id.test.index.ts b/__tests__/teams/[id]/team.id.test.index.ts new file mode 100644 index 0000000000..73d6ac1eaf --- /dev/null +++ b/__tests__/teams/[id]/team.id.test.index.ts @@ -0,0 +1,80 @@ +import handleTeam from "@api/teams/[id]"; +import { createMocks } from "node-mocks-http"; + +import prisma from "@calcom/prisma"; + +describe("GET /api/teams/[id] with valid id as string returns an team-type", () => { + it("returns a message with the specified events", async () => { + const { req, res } = createMocks({ + method: "GET", + query: { + id: "1", + }, + }); + const team = await prisma.team.findUnique({ where: { id: 1 } }); + await handleTeam(req, res); + + expect(res._getStatusCode()).toBe(200); + expect(JSON.parse(res._getData())).toStrictEqual({ data: team }); + }); +}); + +// This can never happen under our normal nextjs setup where query is always a string | string[]. +// But seemed a good example for testing an error validation +describe("GET /api/teams/[id] errors if query id is number, requires a string", () => { + it("returns a message with the specified events", async () => { + const { req, res } = createMocks({ + method: "GET", + query: { + id: 1, // passing query as a number, which should fail as nextjs will try to parse it as a string + }, + }); + await handleTeam(req, res); + + expect(res._getStatusCode()).toBe(400); + expect(JSON.parse(res._getData())).toStrictEqual([ + { + code: "invalid_type", + expected: "string", + received: "number", + path: ["id"], + message: "Expected string, received number", + }, + ]); + }); +}); + +describe("GET /api/teams/[id] an id not present in db like 0, throws 404 not found", () => { + it("returns a message with the specified events", async () => { + const { req, res } = createMocks({ + method: "GET", + query: { + id: "0", // There's no team type with id 0 + }, + }); + await handleTeam(req, res); + + expect(res._getStatusCode()).toBe(404); + expect(JSON.parse(res._getData())).toStrictEqual({ message: "Event type not found" }); + }); +}); + +describe("POST /api/teams/[id] fails, only GET allowed", () => { + it("returns a message with the specified events", async () => { + const { req, res } = createMocks({ + method: "POST", // This POST method is not allowed + query: { + id: "1", + }, + }); + await handleTeam(req, res); + + expect(res._getStatusCode()).toBe(405); + expect(JSON.parse(res._getData())).toStrictEqual({ message: "Only GET Method allowed" }); + }); +}); + +afterAll((done) => { + prisma.$disconnect().then(); + done(); +}); diff --git a/__tests__/users/[id]/user.id.test.edit.ts b/__tests__/users/[id]/user.id.test.edit.ts new file mode 100644 index 0000000000..ee9206298c --- /dev/null +++ b/__tests__/users/[id]/user.id.test.edit.ts @@ -0,0 +1,95 @@ +import handleEventTypeEdit from "@api/event-types/[id]/edit"; +import { createMocks } from "node-mocks-http"; + +import prisma from "@calcom/prisma"; + +describe("PATCH /api/event-types/[id]/edit with valid id and body updates an event-type", () => { + it("returns a message with the specified events", async () => { + const { req, res } = createMocks({ + method: "PATCH", + query: { + id: "2", + }, + body: { + title: "Updated title", + slug: "updated-slug", + length: 1, + }, + }); + const event = await prisma.eventType.findUnique({ where: { id: parseInt(req.query.id) } }); + await handleEventTypeEdit(req, res); + + expect(res._getStatusCode()).toBe(200); + if (event) event.title = "Updated title"; + expect(JSON.parse(res._getData())).toStrictEqual({ data: event }); + }); +}); + +describe("PATCH /api/event-types/[id]/edit with invalid id returns 404", () => { + it("returns a message with the specified events", async () => { + const { req, res } = createMocks({ + method: "PATCH", + query: { + id: "0", + }, + body: { + title: "Updated title", + slug: "updated-slug", + length: 1, + }, + }); + const event = await prisma.eventType.findUnique({ where: { id: parseInt(req.query.id) } }); + await handleEventTypeEdit(req, res); + + expect(res._getStatusCode()).toBe(404); + if (event) event.title = "Updated title"; + expect(JSON.parse(res._getData())).toStrictEqual({ "error": { + "clientVersion": "3.10.0", + "code": "P2025", + "meta": { + "cause": "Record to update not found.", + }, + }, + "message": "Event type with ID 0 not found and wasn't updated", }); + }); +}); + +describe("PATCH /api/event-types/[id]/edit with valid id and no body returns 400 error and zod validation errors", () => { + it("returns a message with the specified events", async () => { + const { req, res } = createMocks({ + method: "PATCH", + query: { + id: "2", + }, + }); + await handleEventTypeEdit(req, res); + + expect(res._getStatusCode()).toBe(400); + expect(JSON.parse(res._getData())).toStrictEqual([{"code": "invalid_type", "expected": "string", "message": "Required", "path": ["title"], "received": "undefined"}, {"code": "invalid_type", "expected": "string", "message": "Required", "path": ["slug"], "received": "undefined"}, {"code": "invalid_type", "expected": "number", "message": "Required", "path": ["length"], "received": "undefined"}]); + }); +}); + +describe("POST /api/event-types/[id]/edit fails, only PATCH allowed", () => { + it("returns a message with the specified events", async () => { + const { req, res } = createMocks({ + method: "POST", // This POST method is not allowed + query: { + id: "1", + }, + body: { + title: "Updated title", + slug: "updated-slug", + length: 1, + }, + }); + await handleEventTypeEdit(req, res); + + expect(res._getStatusCode()).toBe(405); + expect(JSON.parse(res._getData())).toStrictEqual({ message: "Only PATCH Method allowed for updating event-types" }); + }); +}); + +afterAll((done) => { + prisma.$disconnect().then(); + done(); +}); diff --git a/__tests__/users/[id]/user.id.test.index.ts b/__tests__/users/[id]/user.id.test.index.ts new file mode 100644 index 0000000000..246a8bf0a8 --- /dev/null +++ b/__tests__/users/[id]/user.id.test.index.ts @@ -0,0 +1,80 @@ +import handleUser from "@api/users/[id]"; +import { createMocks } from "node-mocks-http"; + +import prisma from "@calcom/prisma"; + +describe("GET /api/users/[id] with valid id as string returns an user-type", () => { + it("returns a message with the specified events", async () => { + const { req, res } = createMocks({ + method: "GET", + query: { + id: "1", + }, + }); + const user = await prisma.user.findUnique({ where: { id: 1 } }); + await handleUser(req, res); + + expect(res._getStatusCode()).toBe(200); + expect(JSON.parse(res._getData())).toStrictEqual({ data: user }); + }); +}); + +// This can never happen under our normal nextjs setup where query is always a string | string[]. +// But seemed a good example for testing an error validation +describe("GET /api/users/[id] errors if query id is number, requires a string", () => { + it("returns a message with the specified events", async () => { + const { req, res } = createMocks({ + method: "GET", + query: { + id: 1, // passing query as a number, which should fail as nextjs will try to parse it as a string + }, + }); + await handleUser(req, res); + + expect(res._getStatusCode()).toBe(400); + expect(JSON.parse(res._getData())).toStrictEqual([ + { + code: "invalid_type", + expected: "string", + received: "number", + path: ["id"], + message: "Expected string, received number", + }, + ]); + }); +}); + +describe("GET /api/users/[id] an id not present in db like 0, throws 404 not found", () => { + it("returns a message with the specified events", async () => { + const { req, res } = createMocks({ + method: "GET", + query: { + id: "0", // There's no user type with id 0 + }, + }); + await handleUser(req, res); + + expect(res._getStatusCode()).toBe(404); + expect(JSON.parse(res._getData())).toStrictEqual({ message: "Event type not found" }); + }); +}); + +describe("POST /api/users/[id] fails, only GET allowed", () => { + it("returns a message with the specified events", async () => { + const { req, res } = createMocks({ + method: "POST", // This POST method is not allowed + query: { + id: "1", + }, + }); + await handleUser(req, res); + + expect(res._getStatusCode()).toBe(405); + expect(JSON.parse(res._getData())).toStrictEqual({ message: "Only GET Method allowed" }); + }); +}); + +afterAll((done) => { + prisma.$disconnect().then(); + done(); +}); diff --git a/jest.config.ts b/jest.config.ts index d6ad32763e..0baa4ba5b5 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -7,7 +7,9 @@ const config = { clearMocks: true, coverageDirectory: "./coverage", collectCoverage: true, - + "collectCoverageFrom": [ + "pages/api/**/*.ts" + ], // An array of regexp pattern strings used to skip coverage collection // coveragePathIgnorePatterns: [ // "/node_modules/" diff --git a/pages/api/api-keys/[id]/delete.ts b/pages/api/api-keys/[id]/delete.ts index 488712d91d..487ff496c0 100644 --- a/pages/api/api-keys/[id]/delete.ts +++ b/pages/api/api-keys/[id]/delete.ts @@ -1,7 +1,7 @@ import prisma from "@calcom/prisma"; import { NextApiRequest, NextApiResponse } from "next"; -import { schemaQueryId, withValidQueryIdString } from "@lib/validations/queryIdString"; +import { schemaQueryIdAsString, withValidQueryIdString } from "@lib/validations/queryIdString"; type ResponseData = { message?: string; @@ -10,7 +10,7 @@ type ResponseData = { export async function apiKey(req: NextApiRequest, res: NextApiResponse) { const { query, method } = req; - const safe = await schemaQueryId.safeParse(query); + const safe = await schemaQueryIdAsString.safeParse(query); if (safe.success) { if (method === "DELETE") { // DELETE WILL DELETE THE EVENT TYPE diff --git a/pages/api/team/[id]/delete.ts b/pages/api/teams/[id]/delete.ts similarity index 100% rename from pages/api/team/[id]/delete.ts rename to pages/api/teams/[id]/delete.ts diff --git a/pages/api/team/[id]/edit.ts b/pages/api/teams/[id]/edit.ts similarity index 97% rename from pages/api/team/[id]/edit.ts rename to pages/api/teams/[id]/edit.ts index ac0fdc1517..daaf27657a 100644 --- a/pages/api/team/[id]/edit.ts +++ b/pages/api/teams/[id]/edit.ts @@ -30,7 +30,7 @@ export async function editTeam(req: NextApiRequest, res: NextApiResponse Date: Sat, 26 Mar 2022 02:22:28 +0100 Subject: [PATCH 2/2] fixes tests by passing stringifyISODates to createdDate and emailVerified --- __tests__/users/[id]/user.id.test.index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/__tests__/users/[id]/user.id.test.index.ts b/__tests__/users/[id]/user.id.test.index.ts index 246a8bf0a8..5d833747d4 100644 --- a/__tests__/users/[id]/user.id.test.index.ts +++ b/__tests__/users/[id]/user.id.test.index.ts @@ -2,6 +2,7 @@ import handleUser from "@api/users/[id]"; import { createMocks } from "node-mocks-http"; import prisma from "@calcom/prisma"; +import { stringifyISODate } from "@lib/utils/stringifyISODate"; describe("GET /api/users/[id] with valid id as string returns an user-type", () => { it("returns a message with the specified events", async () => { @@ -15,7 +16,7 @@ describe("GET /api/users/[id] with valid id as string returns an user-type", () await handleUser(req, res); expect(res._getStatusCode()).toBe(200); - expect(JSON.parse(res._getData())).toStrictEqual({ data: user }); + expect(JSON.parse(res._getData())).toEqual({ data: {...user, createdDate: stringifyISODate(user?.createdDate), emailVerified: stringifyISODate(user?.emailVerified)} }); }); });