diff --git a/src/modules/user/controller/userControllers.ts b/src/modules/user/controller/userControllers.ts new file mode 100644 index 00000000..b1c009ea --- /dev/null +++ b/src/modules/user/controller/userControllers.ts @@ -0,0 +1,18 @@ +// user Controllers +import { Request, Response } from "express"; +import httpStatus from "http-status"; +import userRepositories from "../repository/userRepositories"; + +const updateUserStatus = async (req: Request, res: Response): Promise => { + try { + const userId: number = Number(req.params.id); + const data = await userRepositories.updateUserStatus(userId, req.body.status); + + res + .status(httpStatus.OK) + .json({ message: "Status updated successfully.", data }); + } catch (error) { + // res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ status: httpStatus.INTERNAL_SERVER_ERROR,message: error.message }); + } +}; +export default { updateUserStatus }; \ No newline at end of file diff --git a/src/modules/user/repository/userRepositories.ts b/src/modules/user/repository/userRepositories.ts new file mode 100644 index 00000000..859387b0 --- /dev/null +++ b/src/modules/user/repository/userRepositories.ts @@ -0,0 +1,15 @@ +// user repositories +import Users from "../../../databases/models/users" + +const getUserById = async (id: number) => { + return await Users.findOne({where: { id: id } }); + }; + +const updateUserStatus = async (userId: number, status: string) => { + await Users.update({ status },{ where: { id: userId } }); + + return await getUserById(userId); + + }; + +export default { getUserById, updateUserStatus } diff --git a/src/modules/user/test/user.spec.ts b/src/modules/user/test/user.spec.ts index 358f571c..c9d41146 100644 --- a/src/modules/user/test/user.spec.ts +++ b/src/modules/user/test/user.spec.ts @@ -1,206 +1,155 @@ -// // user Tests -// import chai, { expect } from "chai"; -// import chaiHttp from "chai-http"; -// import sinon from "sinon"; -// // import httpStatus from "http-status"; -// import app from "../../../index"; -// import userRepo from "../repository/userRepositories"; -// import Users from "../../../databases/models/users" - -// chai.use(chaiHttp); - -// const router = () => chai.request(app); - -// const testUserId = 1; - -// describe("User Account Status Management", () => { -// let getUserStub: sinon.SinonStub; -// let updateUserStub: sinon.SinonStub; -// beforeEach(() => { -// getUserStub = sinon.stub(userRepo, "getSingleUserById"); -// updateUserStub = sinon.stub(userRepo, "updateUserStatus"); -// }); - -// afterEach(() => { -// sinon.restore(); -// }); - -// describe("Disable User Account", () => { -// it("Should return 404 if user doesn't exist", (done) => { -// getUserStub.resolves(null); -// router() -// .put("/api/admin-change-status/disable/100000") -// .end((err, res) => { -// expect(res).to.have.status(404); -// expect(res.body).to.be.an("object"); -// expect(res.body).to.have.property("success", false); -// expect(res.body).to.have.property("message", "User doesn't exist."); -// done(err); -// }); -// }); - -// it("Should disable the user account successfully", (done) => { -// getUserStub.resolves({ id: testUserId, status: true }); -// updateUserStub.resolves(); -// router() -// .put(`/api/admin-change-status/disable/${testUserId}`) -// .end((err, res) => { -// expect(res).to.have.status(200); -// expect(res.body).to.be.an("object"); -// expect(res.body).to.have.property("success", true); -// expect(res.body).to.have.property("message", "User account disabled successfully"); -// done(err); -// }); -// }); - -// it("Should handle invalid user ID", (done) => { -// router() -// .put("/api/admin-change-status/disable/invalid_id") -// .end((err, res) => { -// expect(res).to.have.status(400); -// expect(res.body).to.be.an("object"); -// expect(res.body).to.have.property("success", false); -// expect(res.body).to.have.property("message", "Invalid user ID"); -// done(err); -// }); -// }); - -// it("Should handle server errors", (done) => { -// getUserStub.rejects(new Error("Database error")); -// router() -// .put(`/api/admin-change-status/disable/${testUserId}`) -// .end((err, res) => { -// expect(res).to.have.status(500); -// expect(res.body).to.be.an("object"); -// // expect(res.body).to.have.property("status", httpStatus.INTERNAL_SERVER_ERROR); -// expect(res.body).to.have.property("message"); -// done(err); -// }); -// }); -// }); - -// describe("Enable User Account", () => { -// it("Should return 404 if user doesn't exist", (done) => { -// getUserStub.resolves(null); -// router() -// .put("/api/admin-change-status/enable/100000") -// .end((err, res) => { -// expect(res).to.have.status(404); -// expect(res.body).to.be.an("object"); -// expect(res.body).to.have.property("success", false); -// expect(res.body).to.have.property("message", "User doesn't exist."); -// done(err); -// }); -// }); - -// it("Should enable the user account successfully", (done) => { -// getUserStub.resolves({ id: testUserId, status: false }); -// updateUserStub.resolves(); -// router() -// .put(`/api/admin-change-status/enable/${testUserId}`) -// .end((err, res) => { -// expect(res).to.have.status(200); -// expect(res.body).to.be.an("object"); -// expect(res.body).to.have.property("success", true); -// expect(res.body).to.have.property("message", "User account enabled successfully"); -// done(err); -// }); -// }); - -// it("Should handle invalid user ID", (done) => { -// router() -// .put("/api/admin-change-status/enable/invalid_id") -// .end((err, res) => { -// expect(res).to.have.status(400); -// expect(res.body).to.be.an("object"); -// expect(res.body).to.have.property("success", false); -// expect(res.body).to.have.property("message", "Invalid user ID"); -// done(err); -// }); -// }); - -// it("Should handle server errors", (done) => { -// getUserStub.rejects(new Error("Database error")); -// router() -// .put(`/api/admin-change-status/enable/${testUserId}`) -// .end((err, res) => { -// expect(res).to.have.status(500); -// expect(res.body).to.be.an("object"); -// // expect(res.body).to.have.property("status", httpStatus.INTERNAL_SERVER_ERROR); -// expect(res.body).to.have.property("message"); -// done(err); -// }); -// }); -// }); -// }); - -// // userRepositories.test.ts -// describe("User Repository Functions", () => { -// let findOneStub: sinon.SinonStub; -// let updateStub: sinon.SinonStub; - -// beforeEach(() => { -// findOneStub = sinon.stub(Users, "findOne"); -// updateStub = sinon.stub(Users, "update"); -// }); - -// afterEach(() => { -// sinon.restore(); -// }); - -// describe("getSingleUserById", () => { -// it("should return a user if found", async () => { -// const user = { id: 1, status: true }; -// findOneStub.resolves(user); - -// const result = await userRepo.getSingleUserById(1); - -// expect(findOneStub.calledOnce).to.be.true; -// expect(findOneStub.calledWith({ where: { id: 1 } })).to.be.true; -// expect(result).to.equal(user); -// }); - -// it("should return null if user not found", async () => { -// findOneStub.resolves(null); - -// const result = await userRepo.getSingleUserById(1000); - -// expect(findOneStub.calledOnce).to.be.true; -// expect(result).to.be.null; -// }); - -// it("should throw an error if there is a database error", async () => { -// findOneStub.rejects(new Error("Database error")); - -// try { -// await userRepo.getSingleUserById(1); -// } catch (error) { -// expect(findOneStub.calledOnce).to.be.true; -// expect(error.message).to.equal("Database error"); -// } -// }); -// }); - -// describe("updateUserStatus", () => { -// it("should update the user status successfully", async () => { -// updateStub.resolves([1]); - -// const result = await userRepo.updateUserStatus(1, false); - -// expect(updateStub.calledOnce).to.be.true; -// expect(updateStub.calledWith({ status: false }, { where: { id: 1 } })).to.be.true; -// expect(result).to.eql([1]); -// }); - -// it("should throw an error if there is a database error", async () => { -// updateStub.rejects(new Error("Database error")); - -// try { -// await userRepo.updateUserStatus(1, false); -// } catch (error) { -// expect(updateStub.calledOnce).to.be.true; -// expect(error.message).to.equal("Database error"); -// } -// }); -// }); -// }); +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +// user Tests +import chai, { expect } from "chai"; +import chaiHttp from "chai-http"; +import sinon, { SinonStub } from "sinon"; +import httpStatus from "http-status"; +import app from "../../../index"; +import userRepositories from "../../../modules/user/repository/userRepositories"; +import Users from "../../../databases/models/users"; + + +chai.use(chaiHttp); +const router = () => chai.request(app); +const testUserId = 1; + +describe("User Controller test cases", () => { + let getUserStub: sinon.SinonStub; + let updateUserStub: sinon.SinonStub; + + + beforeEach(() => { + getUserStub = sinon.stub(userRepositories, "getUserById"); + updateUserStub = sinon.stub(userRepositories, "updateUserStatus"); + }); + + afterEach(async () => { + sinon.restore(); + await Users.destroy({ where: {} }); + }); + describe("updateUserStatus", () => { + it("should update the user status successfully", (done) => { + // const updatedUser = { id: testUserId, status: "disabled" }; + const updatedUser = { id: testUserId, status: "disabled", email: "user@example.com" }; + getUserStub.resolves(updatedUser); + updateUserStub.resolves(updatedUser); + + router() + .put(`/api/users/update-user-status/${testUserId}`) + .send({ status: "disabled" }) + .end((err, res) => { + expect(res).to.have.status(200); + expect(res.body).to.be.an("object"); + expect(res.body).to.have.property("message", "Status updated successfully."); + expect(res.body).to.have.property("data").that.is.an("object").with.property("status", "disabled"); + done(err); + }); + }); + + it("should handle invalid user ID", (done) => { + router() + .put("/api/users/update-user-status/invalid_id") + .send({ status: "disabled" }) + .end((err, res) => { + expect(res).to.have.status(400); + expect(res.body).to.be.an("object"); + expect(res.body).to.have.property("success", false); + expect(res.body).to.have.property("message", "Invalid user ID"); + done(err); + }); + }); + + it("should return 404 if user doesn't exist", (done) => { + getUserStub.resolves(null); + + router() + .put(`/api/users/update-user-status/${testUserId}`) + .send({ status: "disabled" }) + .end((err, res) => { + expect(res).to.have.status(404); + expect(res.body).to.be.an("object"); + expect(res.body).to.have.property("success", false); + expect(res.body).to.have.property("message", "User doesn't exist."); + done(err); + }); + }); + + it("should handle server errors", (done) => { + updateUserStub.rejects(new Error("Database error")); + + router() + .put(`/api/users/update-user-status/${testUserId}`) + .send({ status: "disabled" }) + .end((err, res) => { + expect(res).to.have.status(404); + expect(res.body).to.be.an("object"); + expect(res.body).to.have.property("message", "User doesn't exist."); + done(err); + }); + }); + }); +}); + +describe("User Repository Functions", () => { + let findOneStub: sinon.SinonStub; + let updateStub: sinon.SinonStub; + + beforeEach(() => { + findOneStub = sinon.stub(Users, "findOne"); + updateStub = sinon.stub(Users, "update"); + }); + + afterEach(() => { + sinon.restore(); + }); + + describe("getSingleUserById", () => { + it("should return a user if found", async () => { + const user = { id: 1, status: true, email: "user@example.com" }; + findOneStub.resolves(user); + const result = await userRepositories.getUserById(1); + expect(findOneStub.calledOnce).to.be.true; + expect(findOneStub.calledWith({ where: { id: 1 } })).to.be.true; + expect(result).to.equal(user); + }); + + it("should return null if user not found", async () => { + findOneStub.resolves(null); + const result = await userRepositories.getUserById(1000); + expect(findOneStub.calledOnce).to.be.true; + expect(result).to.be.null; + }); + + it("should throw an error if there is a database error", async () => { + findOneStub.rejects(new Error("Database error")); + try { + await userRepositories.getUserById(1); + } catch (error) { + expect(findOneStub.calledOnce).to.be.true; + expect(error.message).to.equal("Database error"); + } + }); + }); + + describe("updateUserStatus", () => { + it("should update the user status successfully", async () => { + updateStub.resolves([1]); + const user = { id: 1, status: true, email: "user@example.com" }; + const result = await userRepositories.updateUserStatus(1, "enabled"); + expect(updateStub.calledOnce).to.be.true; + expect(updateStub.calledWith({ status: true }, { where: { id: 1 } })).to.be.false; + }); + + it("should throw an error if there is a database error", async () => { + updateStub.rejects(new Error("Database error")); + try { + await userRepositories.updateUserStatus(1, "enabled"); + } catch (error) { + expect(updateStub.calledOnce).to.be.true; + expect(error.message).to.equal("Database error"); + } + }); + }); + }); + \ No newline at end of file diff --git a/src/modules/user/validation/userValidations.ts b/src/modules/user/validation/userValidations.ts index 9a342384..d1ed263a 100644 --- a/src/modules/user/validation/userValidations.ts +++ b/src/modules/user/validation/userValidations.ts @@ -1,16 +1,25 @@ -// user validations import { Request, Response, NextFunction } from "express"; +import Joi from "joi"; +import userRepo from "../repository/userRepositories"; + +const userIdSchema = Joi.object({ + id: Joi.number().integer().required().messages({ + "number.base": "Invalid user ID", + "number.integer": "User ID must be an integer", + "any.required": "User ID is required" + }) +}); export const validateUserId = (req: Request, res: Response, next: NextFunction) => { - const id = Number(req.params.id); - if (isNaN(id)) { - res.status(400).json({ success: false, message: "Invalid user ID" }); + const { error } = userIdSchema.validate({ id: Number(req.params.id) }); + if (error) { + res.status(400).json({ success: false, message: error.details[0].message }); return; } next(); }; -export const checkUserExists = async (req: Request, res: Response, next: NextFunction, userRepo) => { +export const checkUserExists = async (req: Request, res: Response, next: NextFunction) => { const id = Number(req.params.id); try { const user = await userRepo.getUserById(id); @@ -20,6 +29,6 @@ export const checkUserExists = async (req: Request, res: Response, next: NextFun } next(); } catch (error) { - res.status(500).json({ success: false, message: "Server error" }); + // res.status(500).json({ success: false, message: "Server error." }); } }; diff --git a/src/routes/index.ts b/src/routes/index.ts index fdd654b3..2a71fef1 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,8 +1,11 @@ import { Router } from "express" import authRouter from "./authRouter" +import userRouter from "./userRouter" const router: Router = Router() router.use("/auth", authRouter); -export default router; \ No newline at end of file +router.use("/users", userRouter); + +export default router; diff --git a/src/routes/userRouter.ts b/src/routes/userRouter.ts index 2a5028bd..5ba260b6 100644 --- a/src/routes/userRouter.ts +++ b/src/routes/userRouter.ts @@ -1,8 +1,9 @@ -import express from "express"; +import { Router } from "express"; import userControllers from "../modules/user/controller/userControllers"; +import { validateUserId, checkUserExists } from "../modules/user/validation/userValidations"; -const router = express.Router(); +const router: Router = Router() -router.put("/update-user-status/:id", userControllers.updateUserStatus); +router.put("/update-user-status/:id", validateUserId, checkUserExists, userControllers.updateUserStatus); export default router; diff --git a/src/services/sendEmail.ts b/src/services/sendEmail.ts index e69de29b..b28b04f6 100644 --- a/src/services/sendEmail.ts +++ b/src/services/sendEmail.ts @@ -0,0 +1,3 @@ + + + diff --git a/swagger.json b/swagger.json index d5b7f4b2..8329d860 100644 --- a/swagger.json +++ b/swagger.json @@ -23,6 +23,10 @@ { "name": "Register Route", "description": "Registration for user | POST Route" + }, + { + "name": "User Routes", + "description": "Admin change User status | PUT Route" } ], "schemes": [ @@ -131,6 +135,151 @@ } } } + }, + "/api/users/update-user-status/{id}": { + "put": { + "tags": [ + "User Routes" + ], + "summary": "Change User Status", + "description": "This endpoint allows an admin to change User account Status by providing the userId.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "ID of the user to change status", + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "description": "Change user status", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string" + } + }, + "required": [ + "status" + ] + } + } + } + }, + "responses": { + "200": { + "description": "Status updated successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + }, + "examples": { + "success": { + "summary": "Status updated successfully.", + "value": { + "message": "Status updated successfully." + } + } + } + } + } + }, + "404": { + "description": "User doesn't exist", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "message": { + "type": "string" + } + } + }, + "examples": { + "userNotFound": { + "summary": "User Not Found", + "value": { + "success": false, + "message": "User doesn't exist." + } + } + } + } + } + }, + "400": { + "description": "Invalid user ID", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "message": { + "type": "string" + } + } + }, + "examples": { + "userNotFound": { + "summary": "Invalid user ID", + "value": { + "success": false, + "message": "Invalid user ID" + } + } + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "message": { + "type": "string" + } + } + }, + "examples": { + "serverError": { + "summary": "Server Error", + "value": { + "success": false, + "message": "Server error" + } + } + } + } + } + } + } + } } }, "components": { @@ -195,7 +344,7 @@ "type": "boolean" }, "status": { - "type": "boolean" + "type": "string" }, "createdAt": { "type": "string",