From 1c7315b4434f02dbe6480d7fd89af31eaaf97022 Mon Sep 17 00:00:00 2001 From: subnub <44621867+subnub@users.noreply.github.com> Date: Mon, 24 Feb 2025 00:36:43 -0500 Subject: [PATCH] start user tests, improved user error messages --- backend/controllers/user-controller.ts | 5 - backend/middleware/user/user-middleware.ts | 1 + backend/middleware/utils/middleware-utils.ts | 4 +- backend/models/user-model.ts | 5 +- backend/services/user-service/user-service.ts | 11 + backend/utils/ConflictError.ts | 11 + src/components/LoginPage/LoginPage.tsx | 13 +- tests/controller/user-controller.test.js | 204 ++++++++++++++++++ 8 files changed, 244 insertions(+), 10 deletions(-) create mode 100644 backend/utils/ConflictError.ts create mode 100644 tests/controller/user-controller.test.js diff --git a/backend/controllers/user-controller.ts b/backend/controllers/user-controller.ts index 65856bd..4620355 100644 --- a/backend/controllers/user-controller.ts +++ b/backend/controllers/user-controller.ts @@ -127,11 +127,6 @@ class UserController { }; createUser = async (req: RequestType, res: Response, next: NextFunction) => { - if (env.createAcctBlocked) { - res.status(401).send(); - return; - } - try { const currentUUID = req.headers.uuid as string; diff --git a/backend/middleware/user/user-middleware.ts b/backend/middleware/user/user-middleware.ts index f0bd65d..34b4076 100644 --- a/backend/middleware/user/user-middleware.ts +++ b/backend/middleware/user/user-middleware.ts @@ -30,6 +30,7 @@ export const createAccountValidationRules = [ .withMessage("Email is required") .isString() .withMessage("Email must be a string") + .isLength({ min: 1, max: 256 }) .isEmail() .withMessage("Email is invalid"), body("password") diff --git a/backend/middleware/utils/middleware-utils.ts b/backend/middleware/utils/middleware-utils.ts index 36bb919..55c7cea 100644 --- a/backend/middleware/utils/middleware-utils.ts +++ b/backend/middleware/utils/middleware-utils.ts @@ -6,6 +6,7 @@ import NotFoundError from "../../utils/NotFoundError"; import InternalServerError from "../../utils/InternalServerError"; import ForbiddenError from "../../utils/ForbiddenError"; import NotValidDataError from "../../utils/NotValidDataError"; +import ConflictError from "../../utils/ConflictError"; export const middlewareValidationFunction = ( req: Request, @@ -32,7 +33,8 @@ export const middlewareErrorHandler = ( error instanceof ForbiddenError || error instanceof NotFoundError || error instanceof InternalServerError || - error instanceof NotValidDataError + error instanceof NotValidDataError || + error instanceof ConflictError ) { return res.status(error.code).send(error.message); } diff --git a/backend/models/user-model.ts b/backend/models/user-model.ts index 2e46a61..1b9087a 100644 --- a/backend/models/user-model.ts +++ b/backend/models/user-model.ts @@ -4,6 +4,7 @@ import crypto from "crypto"; import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import env from "../enviroment/env"; +import NotAuthorizedError from "../utils/NotAuthorizedError"; const userSchema = new mongoose.Schema( { @@ -131,13 +132,13 @@ userSchema.statics.findByCreds = async (email: string, password: string) => { const user = await User.findOne({ email }); if (!user) { - throw new Error("User not found"); + throw new NotAuthorizedError("User not found"); } const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { - throw new Error("Incorrect password"); + throw new NotAuthorizedError("Incorrect password"); } return user; diff --git a/backend/services/user-service/user-service.ts b/backend/services/user-service/user-service.ts index 13b80af..06a7e35 100644 --- a/backend/services/user-service/user-service.ts +++ b/backend/services/user-service/user-service.ts @@ -9,6 +9,7 @@ import jwt from "jsonwebtoken"; import sendVerificationEmail from "../../utils/sendVerificationEmail"; import sendPasswordResetEmail from "../../utils/sendPasswordResetEmail"; import ForbiddenError from "../../utils/ForbiddenError"; +import ConflictError from "../../utils/ConflictError"; type UserDataType = { email: string; @@ -85,6 +86,16 @@ class UserService { }; create = async (userData: any, uuid: string | undefined) => { + if (env.createAcctBlocked) { + throw new ForbiddenError("Account Creation Blocked"); + } + + const userExistsLookedUp = await User.findOne({ email: userData.email }); + + if (userExistsLookedUp) { + throw new ConflictError("Email Already Exists"); + } + const user = new User({ email: userData.email, password: userData.password, diff --git a/backend/utils/ConflictError.ts b/backend/utils/ConflictError.ts new file mode 100644 index 0000000..2b886f9 --- /dev/null +++ b/backend/utils/ConflictError.ts @@ -0,0 +1,11 @@ +class ConflictError extends Error { + code: number; + + constructor(args: any) { + super(args); + + this.code = 409; + } +} + +export default ConflictError; diff --git a/src/components/LoginPage/LoginPage.tsx b/src/components/LoginPage/LoginPage.tsx index 4551e48..e05d366 100755 --- a/src/components/LoginPage/LoginPage.tsx +++ b/src/components/LoginPage/LoginPage.tsx @@ -13,6 +13,7 @@ import AlertIcon from "../../icons/AlertIcon"; import Spinner from "../Spinner/Spinner"; import { toast, ToastContainer } from "react-toastify"; import Swal from "sweetalert2"; +import { AxiosError } from "axios"; const LoginPage = () => { const [email, setEmail] = useState(""); @@ -55,9 +56,13 @@ const LoginPage = () => { navigate("/home"); setLoadingLogin(false); } catch (e) { + if (e instanceof AxiosError && e.response?.status === 401) { + setError("Incorrect email or password"); + } else { + setError("Login Error"); + } console.log("Login Error", e); setLoadingLogin(false); - setError("Login Failed"); } }; @@ -75,9 +80,13 @@ const LoginPage = () => { navigate("/home"); setLoadingLogin(false); } catch (e) { + if (e instanceof AxiosError && e.response?.status === 409) { + setError("Email Already Exists"); + } else { + setError("Create Account Error"); + } console.log("Create Account Error", e); setLoadingLogin(false); - setError("Create Account Failed"); } }; diff --git a/tests/controller/user-controller.test.js b/tests/controller/user-controller.test.js new file mode 100644 index 0000000..b921c0e --- /dev/null +++ b/tests/controller/user-controller.test.js @@ -0,0 +1,204 @@ +const request = require("supertest"); +const app = require("../utils/express-app"); +const mongoose = require("mongoose"); +const { MongoMemoryServer } = require("mongodb-memory-server"); +const getEnvVariables = require("../../dist-backend/enviroment/get-env-variables"); +const getKey = require("../../dist-backend/key/get-key").default; +getEnvVariables(); +const env = require("../../dist-backend/enviroment/env"); +const { envFileFix } = require("../utils/db-setup"); +const { ObjectId } = require("mongodb"); + +let mongoServer; +let authToken; +let authToken2; +let file; +let file2; +let user; +let user2; + +describe("File Controller", () => { + beforeAll(async () => { + envFileFix(env); + await getKey(); + mongoServer = await MongoMemoryServer.create(); + await mongoose.connect(mongoServer.getUri(), { useNewUrlParser: true }); + }); + + beforeEach(async () => { + await mongoose.model("fs.files").deleteMany({}); + await mongoose.model("Folder").deleteMany({}); + await mongoose.model("User").deleteMany({}); + + user = await request(app) + .post("/user-service/create") + .send({ + email: "test@test.com", + password: "test1234", + }) + .set("uuid", 12314123123); + + user2 = await request(app) + .post("/user-service/create") + .send({ + email: "test@test2.com", + password: "test1234", + }) + .set("uuid", 12314123124); + + authToken = user.headers["set-cookie"] + .map((cookie) => cookie.split(";")[0]) + .join("; "); + + authToken2 = user2.headers["set-cookie"] + .map((cookie) => cookie.split(";")[0]) + .join("; "); + }); + + afterAll(async () => { + await mongoose.disconnect(); + await mongoServer.stop(); + }); + + describe("User info: GET /user-service/user", () => { + test("Should return user info", async () => { + const userResponse = await request(app) + .get(`/user-service/user`) + .set("Cookie", authToken); + + expect(userResponse.status).toBe(200); + expect(userResponse.body.email).toBe(user.body.user.email); + }); + test("Should return 401 if not authorized", async () => { + const userResponse = await request(app) + .get(`/user-service/user`) + .set("Cookie", "access-token=test"); + + expect(userResponse.status).toBe(401); + }); + }); + describe("User login: POST /user-service/login", () => { + test("Should login user", async () => { + const userResponse = await request(app).post("/user-service/login").send({ + email: user.body.user.email, + password: "test1234", + }); + + expect(userResponse.status).toBe(200); + expect(userResponse.body.user.email).toBe(user.body.user.email); + }); + test("Should return 401 if incorrect password", async () => { + const userResponse = await request(app).post("/user-service/login").send({ + email: user.body.user.email, + password: "test12345", + }); + + expect(userResponse.status).toBe(401); + }); + test("Should return 401 if incorrect email", async () => { + const userResponse = await request(app).post("/user-service/login").send({ + email: "notexist@test.com", + password: "test1234", + }); + expect(userResponse.status).toBe(401); + }); + }); + describe("Create User: POST /user-service/create", () => { + test("Should create user", async () => { + const userResponse = await request(app) + .post("/user-service/create") + .send({ + email: "newuser@test.com", + password: "test1234", + }); + + expect(userResponse.status).toBe(201); + + const userDbCheck = await mongoose.model("User").findOne({ + _id: userResponse.body.user._id, + }); + + expect(userDbCheck.email).toBe(userResponse.body.user.email); + }); + test("Should return 400 if no email", async () => { + const userResponse = await request(app) + .post("/user-service/create") + .send({ + password: "test1234", + }); + + expect(userResponse.status).toBe(400); + }); + test("Should return 400 if no password", async () => { + const userResponse = await request(app) + .post("/user-service/create") + .send({ + email: "newuser@test.com", + }); + + expect(userResponse.status).toBe(400); + }); + test("Should return 400 if email length is less than 1", async () => { + const userResponse = await request(app) + .post("/user-service/create") + .send({ + email: "", + password: "test1234", + }); + + expect(userResponse.status).toBe(400); + }); + test("Should return 400 if email length is greater than 256", async () => { + const userResponse = await request(app) + .post("/user-service/create") + .send({ + email: "a" * 257, + password: "test1234", + }); + + expect(userResponse.status).toBe(400); + }); + test("Should return 400 if password length is less than 6", async () => { + const userResponse = await request(app) + .post("/user-service/create") + .send({ + email: "newuser@test.com", + password: "", + }); + + expect(userResponse.status).toBe(400); + }); + test("Should return 400 if password length is greater than 256", async () => { + const userResponse = await request(app) + .post("/user-service/create") + .send({ + email: "newuser@test.com", + password: "a" * 257, + }); + + expect(userResponse.status).toBe(400); + }); + test("Should return 409 if email already exists", async () => { + const userResponse = await request(app) + .post("/user-service/create") + .send({ + email: "test@test.com", + password: "test1234", + }); + + expect(userResponse.status).toBe(409); + }); + test("Should return 403 if create account is blocked", async () => { + env.createAcctBlocked = true; + + const userResponse = await request(app) + .post("/user-service/create") + .send({ + email: "newemail@test.com", + password: "test1234", + }); + + expect(userResponse.status).toBe(403); + }); + }); +});