Skip to content

Commit

Permalink
test: update email route test
Browse files Browse the repository at this point in the history
  • Loading branch information
leviszaboo committed Jul 14, 2024
1 parent 00176a7 commit 8423abb
Show file tree
Hide file tree
Showing 15 changed files with 823 additions and 707 deletions.
2 changes: 1 addition & 1 deletion scripts/run-integration.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ docker compose -f docker-compose.yml -f docker-compose.test.yml up -d

if [ "$#" -eq "0" ]
then
vitest run -c ./vitest.config.integration.ts
vitest -c ./vitest.config.integration.ts
else
vitest -c ./vitest.config.integration.ts --ui
fi
1 change: 0 additions & 1 deletion v1/controller/__tests__/user/getUserByIdHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { User } from "../../../types/user.types";

describe("getUserByIdHandler", () => {
type GetUserByIdMockRequest = Request<GetUserByIdInput["params"]>;

let request: Partial<GetUserByIdMockRequest>;
let response: Partial<Response>;
let next = vi.fn();
Expand Down
8 changes: 7 additions & 1 deletion v1/errors/global/ConflictError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ interface ConflictError extends Error {
}

class ConflictError extends Error {
constructor(message: string) {
constructor(message?: string) {
super(message);
this.name = "ConflictError";
this.statusCode = 409;

Object.setPrototypeOf(this, ConflictError.prototype);

if (Error.captureStackTrace) {
Error.captureStackTrace(this, ConflictError);
}
}
}

Expand Down
16 changes: 13 additions & 3 deletions v1/schema/user.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const loginUserSchema = object({
body: object({
email: string({
required_error: "Email is a required field.",
}).email("Please enter a valid email adress"),
}).email("Please enter a valid email address."),
password: string({
required_error: "Password is a required field.",
}).min(8, "Password must be at least 8 characters long."),
Expand Down Expand Up @@ -71,7 +71,7 @@ export const createUserSchema = object({
body: object({
email: string({
required_error: "Email is a required field.",
}).email("Please enter a valid email adress"),
}).email("Please enter a valid email address."),
password: string({
required_error: "Password is a required field.",
}).min(8, "Password must be at least 8 characters long."),
Expand All @@ -94,7 +94,17 @@ export const updateEmailSchema = object({
body: object({
newEmail: string({
required_error: "New email is a required field.",
}).email("Please enter a valid email adress"),
}).email("Please enter a valid email address."),
}),
params: object({
userId: string({
required_error: "User ID is required.",
}).regex(
new RegExp(
/^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/,
),
"ID must be a valid UUID.",
),
}),
});

Expand Down
32 changes: 20 additions & 12 deletions v1/tests/helpers/reset-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,24 @@ import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();

export default async function resetDb() {

Check failure on line 5 in v1/tests/helpers/reset-db.ts

View workflow job for this annotation

GitHub Actions / test

v1/tests/users/main.test.ts

PrismaClientKnownRequestError: Invalid `prisma.users.delete()` invocation: An operation failed because it depends on one or more records that were required but not found. Record to delete does not exist. ❯ _n.handleRequestError node_modules/.prisma/client/runtime/library.js:122:6927 ❯ _n.handleAndLogRequestError node_modules/.prisma/client/runtime/library.js:122:6235 ❯ _n.request node_modules/.prisma/client/runtime/library.js:122:5919 ❯ l node_modules/.prisma/client/runtime/library.js:131:9116 ❯ Module.resetDb v1/tests/helpers/reset-db.ts:5:3 ❯ async /home/runner/work/***-app/***-app/v1/tests/helpers/setup.ts:27:3 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { code: 'P2025', clientVersion: '5.16.2', meta: { modelName: 'users', cause: 'Record to delete does not exist.' }, batchRequestIdx: undefined }
await prisma.$transaction([
prisma.users.deleteMany({
where: {
email: "[email protected]",
},
}),
prisma.blacklist.deleteMany({
where: {
token: "test-token",
},
}),
]);
await prisma.users.delete({
where: {
email: "[email protected]",
},
});

await prisma.users.delete({
where: {
user_id: "00000000-0000-0000-0000-000000000001",
},
});

await prisma.users.create({
data: {
email: "[email protected]",
password_hash: "testpassword",
user_id: "00000000-0000-0000-0000-000000000001",
email_verified: false,
},
});
}
10 changes: 8 additions & 2 deletions v1/tests/helpers/setup.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import resetDb from "./reset-db.js";
import { beforeAll } from "vitest";
import { beforeAll, afterAll, afterEach } from "vitest";
import { createServer } from "../../utils/express.utils.js";
import { UserInput } from "../../types/user.types.js";
import { UserInput, User } from "../../types/user.types.js";
import { Config } from "../../utils/options.js";

export const exampleUser: UserInput = {
email: "[email protected]",
password: "testpassword",
};

export const exampleUser2: User = {
userId: "00000000-0000-0000-0000-000000000001",
email: "[email protected]",
emailVerified: false,
};

export const app = createServer();

export const apiKey = Config.API_KEY;
Expand Down
16 changes: 4 additions & 12 deletions v1/tests/users/getUser.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, expectTypeOf, it } from "vitest";
import request from "supertest";
import { exampleUser, apiKey, appId, app } from "../helpers/setup";
import { exampleUser2, apiKey, appId, app } from "../helpers/setup";
import { Endpoints } from "../../utils/options";
import { User } from "../../types/user.types";
import { ZodIssue } from "zod";
Expand All @@ -10,16 +10,8 @@ export const getUserRouteTest = () =>
const endpoint = Endpoints.GET_USER;

it("should respond with a `200` status code and user info when a valid api key and app id is present", async () => {
const loginResponse = await request(app)
.post(Endpoints.LOGIN)
.send(exampleUser)
.set("x-gator-api-key", apiKey)
.set("x-gator-app-id", appId);

const loginBody = loginResponse.body;

const { status, body } = await request(app)
.get(endpoint.replace(":userId", loginBody.userId))
.get(endpoint.replace(":userId", exampleUser2.userId))
.set("x-gator-api-key", apiKey)
.set("x-gator-app-id", appId);

Expand All @@ -28,8 +20,8 @@ export const getUserRouteTest = () =>
expectTypeOf(body).toMatchTypeOf<User>();

expect(body).toMatchObject({
userId: loginBody.userId,
email: exampleUser.email,
userId: exampleUser2.userId,
email: exampleUser2.email,
emailVerified: false,
});
});
Expand Down
27 changes: 25 additions & 2 deletions v1/tests/users/login.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { describe, expect, expectTypeOf, it } from "vitest";
import { describe, expect, expectTypeOf, it, vi } from "vitest";
import request from "supertest";
import { AuthResponse } from "../../types/user.types";
import { exampleUser, apiKey, appId, app } from "../helpers/setup";
import * as UserService from "../../service/user.service";
import { Endpoints } from "../../utils/options";
import { ZodIssue } from "zod";

Expand Down Expand Up @@ -67,10 +68,32 @@ export const loginRouteTest = () =>
expect(body).toMatchObject([
{
code: "invalid_string",
message: "Please enter a valid email adress",
message: "Please enter a valid email address.",
path: ["body", "email"],
validation: "email",
},
]);
});

it("should respond with a `500` status code when an unexpected error occurs", async () => {
const loginUserSpy = vi.spyOn(UserService, "loginUser");

const err = new Error("Something went wrong");

loginUserSpy.mockRejectedValue(err);

const { status, body } = await request(app)
.post(endpoint)
.send(exampleUser)
.set("x-gator-api-key", apiKey)
.set("x-gator-app-id", appId);

expect(status).toBe(500);

expect(body).toMatchObject({
error: {
message: "An unexpected error occurred. Please try again later.",
},
});
});
});
3 changes: 3 additions & 0 deletions v1/tests/users/main.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { testAuthMiddleware } from "../middleware/authenticate";
import { signUpRouteTest } from "./signup";
import { loginRouteTest } from "./login";
import { updateEmailRouteTest } from "./updateEmail";
import { forIn } from "lodash";

import { Endpoints } from "../../utils/options";
Expand All @@ -17,3 +18,5 @@ signUpRouteTest();
loginRouteTest();

getUserRouteTest();

updateEmailRouteTest();
2 changes: 1 addition & 1 deletion v1/tests/users/signup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const signUpRouteTest = () =>
expect(body).toMatchObject([
{
code: "invalid_string",
message: "Please enter a valid email adress",
message: "Please enter a valid email address.",
path: ["body", "email"],
validation: "email",
},
Expand Down
89 changes: 89 additions & 0 deletions v1/tests/users/updateEmail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { describe, expect, expectTypeOf, it } from "vitest";
import request from "supertest";
import {
exampleUser2,
exampleUser,
apiKey,
appId,
app,
} from "../helpers/setup";
import { Endpoints } from "../../utils/options";
import { ZodIssue } from "zod";

export const updateEmailRouteTest = () =>
describe("[PUT] /api/v1/users/:userId/update-email", () => {
const endpoint = Endpoints.UPDATE_EMAIL;

it("should respond with a `204` status code when a valid api key and app id is present", async () => {
const { status } = await request(app)
.put(endpoint.replace(":userId", exampleUser2.userId))
.send({ newEmail: "[email protected]" })
.set("x-gator-api-key", apiKey)
.set("x-gator-app-id", appId);

expect(status).toBe(204);
});

it("should respond with a `404` status code when the user does not exist", async () => {
const invalidUserId = "00000000-0000-0000-0000-000000000000";
const { status, body } = await request(app)
.put(endpoint.replace(":userId", invalidUserId))
.send({ newEmail: "[email protected]" })
.set("x-gator-api-key", apiKey)
.set("x-gator-app-id", appId);

expect(status).toBe(404);

expect(body).toMatchObject({
error: {
name: "UserNotFoundError",
message: `User not found with ID: ${invalidUserId}`,
errorCode: "USER_NOT_FOUND",
},
});
});

it("should respond with a `400` status code and Zod errors when the request body is invalid", async () => {
const { status, body } = await request(app)
.put(endpoint.replace(":userId", exampleUser2.userId))
.send({ newEmail: "invalid-email" })
.set("x-gator-api-key", apiKey)
.set("x-gator-app-id", appId);

expect(status).toBe(400);

expectTypeOf(body).toMatchTypeOf<ZodIssue[]>();

expect(body).toMatchObject([
{
validation: "email",
code: "invalid_string",
message: "Please enter a valid email address.",
path: ["body", "newEmail"],
},
]);
});

it("should respond with a `400` status code and Zod errors when the user id is invalid", async () => {
const { status, body } = await request(app)
.put(endpoint.replace(":userId", "invalid-id"))
.send({ newEmail: "[email protected]" })
.set("x-gator-api-key", apiKey)
.set("x-gator-app-id", appId);

console.log(status, body);

expect(status).toBe(400);

expectTypeOf(body).toMatchTypeOf<ZodIssue[]>();

expect(body).toMatchObject([
{
validation: "regex",
code: "invalid_string",
message: "ID must be a valid UUID.",
path: ["params", "userId"],
},
]);
});
});
1 change: 0 additions & 1 deletion v1/utils/express.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { Server } from "http";
import { disconnectPostgres } from "../db/cleanup";
import { exit } from "process";
import logger from "../utils/logger";
import { RequestHandler } from "express";
import * as Errors from "../errors";

export const shutdownSignals: NodeJS.Signals[] = [
Expand Down
1 change: 0 additions & 1 deletion v1/utils/jwt.utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import jwt, { JwtPayload } from "jsonwebtoken";
import { Response } from "express";
import config from "config";
import { checkBlackListedToken } from "../service/token.service";
import { Config } from "./options";

Expand Down
8 changes: 4 additions & 4 deletions v1/utils/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ export enum Endpoints {
SIGNUP = "/api/v1/users/sign-up",
LOGIN = "/api/v1/users/login",
GET_USER = "/api/v1/users/:userId",
UPDATE_EMAIL = "/api/v1/users/update-email",
UPDATE_PASSWORD = "/api/v1/users/update-password",
SEND_VERIFICATION_EMAIL = "/api/v1/users/send-verification-email",
VERIFY_EMAIL = "/api/v1/users/verify-email",
UPDATE_EMAIL = "/api/v1/users/:userId/update-email",
UPDATE_PASSWORD = "/api/v1/users/:userId/update-password",
SEND_VERIFICATION_EMAIL = "/api/v1/users/:userId/send-verification-email",
VERIFY_EMAIL = "/api/v1/users/:userId/verify-email",
DELETE_USER = "/api/v1/users/:userId",
REISSUE_TOKEN = "/api/v1/tokens/reissue-token",
INVALIDATE_TOKEN = "/api/v1/tokens/invalidate-token",
Expand Down
Loading

0 comments on commit 8423abb

Please sign in to comment.