Skip to content

Commit

Permalink
Merge pull request #40 from atlp-rwanda/ft-crud-Roles-#187419177
Browse files Browse the repository at this point in the history
#187419177 Feature(CRUD Roles):   Admin can Crud roles and assign roles to the users
  • Loading branch information
teerenzo authored May 2, 2024
2 parents 9f8f786 + dd8a041 commit aa23d2c
Show file tree
Hide file tree
Showing 36 changed files with 780 additions and 191 deletions.
1 change: 1 addition & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:

- name: Running test
env:
IS_REMOTE: true
DB_CONNECTION: ${{ secrets.DB_CONNECTION }}
TEST_DB: ${{ secrets.TEST_DB }}
SMTP_HOST: ${{ secrets.SMTP_HOST }}
Expand Down
155 changes: 154 additions & 1 deletion __test__/user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import * as mailServices from "../src/services/mail.service";
import sequelize, { connect } from "../src/config/dbConnection";
// import * as twoFAService from "../src/utils/2fa";
import { profile } from "console";
import bcrypt from "bcrypt";
import { roleService } from "../src/services/role.service";
import { Role } from "../src/sequelize/models/roles";
import exp from "constants";

const userData: any = {
name: "yvanna5",
Expand All @@ -15,12 +19,12 @@ const userData: any = {
password: "test12345",
};


const dummySeller = {
name: "dummy1234",
username: "username1234",
email: "[email protected]",
password: "1234567890",
role: "seller",
};
const userTestData = {
newPassword: "Test@123",
Expand Down Expand Up @@ -55,6 +59,25 @@ describe("Testing user Routes", () => {
try {
await connect();
await sequelize.query('TRUNCATE TABLE profiles, users CASCADE');
const testAdmin = {
name: "admin",
username: "admin",
email: "[email protected]",
password: await bcrypt.hash("password", 10),
roleId: 3
}

await Role.destroy({ where: {}});
const resetId = await sequelize.query('ALTER SEQUENCE "Roles_id_seq" RESTART WITH 1');

await Role.bulkCreate([
{ name: "buyer" },
{ name: "seller" },
{ name: "admin" },
])

await User.create(testAdmin);

const dummy = await request(app).post("/api/v1/users/register").send(dummySeller);
} catch (error) {
console.error('Error connecting to the database:', error);
Expand All @@ -63,14 +86,17 @@ describe("Testing user Routes", () => {


let token:any;
let adminToken:any;
describe("Testing user authentication", () => {
test("should return 201 and create a new user when registering successfully", async () => {
const response = await request(app)
.post("/api/v1/users/register")
.send(userData);

expect(response.status).toBe(201);
}, 20000);


test("should return 409 when registering with an existing email", async () => {
User.create(userData);
const response = await request(app)
Expand Down Expand Up @@ -166,6 +192,32 @@ describe("Testing user Routes", () => {
spyonOne.mockRestore();
}, 20000);

test("should login an Admin", async () =>{
const response = await request(app).post("/api/v1/users/login").send({
email: "[email protected]",
password: "password"
})
adminToken = response.body.token;
});

test("should update dummyseller's role to seller", async () => {
const logDummySeller = await request(app).post("/api/v1/users/login").send({
email: dummySeller.email,
password: dummySeller.password,
});
expect(logDummySeller.status).toBe(200);
const dummySellerId = logDummySeller.body.userInfo.id;

const response = await request(app)
.patch(`/api/v1/users/${dummySellerId}/role`)
.send({
roleId: 2,
})
.set("Authorization", "Bearer " + adminToken);
expect(response.status).toBe(200);

});

test("Should send otp verification code", async () => {
const spy = jest.spyOn(mailServices, "sendEmailService");
const response = await request(app).post("/api/v1/users/login").send({
Expand Down Expand Up @@ -264,8 +316,109 @@ describe("Testing user authentication", () => {
const response = await request(app).get("/api/v1/users/auth/google/callback");
expect(response.status).toBe(302);
});



})

describe("Admin should be able to CRUD roles", () => {
let testRoleName = "testrole";
let newRoleId: any;
test("should return 201 when a new role is created", async () => {
const response = await request(app)
.post("/api/v1/roles")
.send({
name: testRoleName,
})
.set("Authorization", "Bearer " + adminToken);
expect(response.status).toBe(201);
newRoleId = response.body.role.id;

});

test("should return 400 when a role with the same name is created", async () => {
const response = await request(app)
.post("/api/v1/roles")
.send({
name: testRoleName,
})
.set("Authorization", "Bearer " + adminToken);
expect(response.status).toBe(400);
});

test("should return 404 when deleting a role which doesn't exist", async () => {
const response = await request(app)
.delete("/api/v1/roles/1000")
.set("Authorization", "Bearer " + adminToken);
expect(response.status).toBe(404);
})

test("should return 200 when all roles are fetched", async () => {
const response = await request(app)
.get("/api/v1/roles")
expect(response.status).toBe(200);
});

test("should return 200 when a role is updated", async () => {
const response = await request(app)
.patch("/api/v1/roles/" + newRoleId)
.send({
name: "testRoled",
})
.set("Authorization", "Bearer " + adminToken);
expect(response.status).toBe(200);
});
test("should return 400 role already exists when a role is updated with an existing name", async () => {
const response = await request(app)
.patch("/api/v1/roles/" + newRoleId)
.send({
name: "buyer"
})
.set("Authorization", "Bearer " + adminToken);
expect(response.status).toBe(400);
expect(response.body.message).toBe('Role already exists')
});

test("should return 401 Unauthorized when trying to create or update role without Auth", async () =>{
const response = await request(app)
.patch("/api/v1/roles/" + newRoleId)
.send({
name: "testRoled",
})
expect(response.status).toBe(401);
})

test("should return 200 when a role is deleted", async () => {
const response = await request(app)
.delete("/api/v1/roles/" + newRoleId)
.set("Authorization", "Bearer " + adminToken);
expect(response.status).toBe(200);
});
});

test("should return 409 when updating a role for user who doesn't exist", async () => {

const response = await request(app)
.patch("/api/v1/users/1000/role")
.send({
roleId: 2,
})
.set("Authorization", "Bearer " + adminToken);


expect(response.status).toBe(409);
});

test("should return 409 when updating a role for a role that doesn't exist", async () => {
const response = await request(app)
.patch("/api/v1/users/1/role")
.send({
roleId: 1000,
})
.set("Authorization", "Bearer " + adminToken);
expect(response.status).toBe(409);
});

afterAll(async () => {
try {
await sequelize.query('TRUNCATE TABLE profiles, users CASCADE');
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"seed": "npx sequelize-cli db:seed:all",
"undo:migrate" : "npx sequelize-cli db:migrate:undo:all",
"lint:fix": "npx eslint --fix .",
"test": "cross-env NODE_ENV=test jest --detectOpenHandles --coverage",
"test": "cross-env NODE_ENV=test npm run migrate && jest --detectOpenHandles --coverage",
"prepare": "husky",
"prettier": "prettier . --write"
},
Expand Down Expand Up @@ -100,3 +100,4 @@
"swagger-ui-express": "^5.0.0"
}
}

64 changes: 64 additions & 0 deletions src/controllers/roleControllers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Request, Response } from 'express';
import { roleService } from '../services/role.service';
import { Optional } from 'sequelize';
import { IRole } from '../sequelize/models/roles';

export const roleController = {
createRole: async (req: Request, res: Response) => {
const { name, permissions } = req.body;

const existingRole = await roleService.findRoleByName(name);
if (existingRole) {
return res.status(400).json({ message: 'Role already exists' });
}

const role = await roleService.createRole({ name, permissions });
res.status(201).json({
message: 'Role created successfully',
role: role
});
},

getRoles: async (req: Request, res: Response) => {

const roles = await roleService.getRoles();
if (roles.length === 0) {
return res.status(404).json({ message: 'No roles found' });
}
res.status(200).json({
message: 'Roles fetched successfully',
count: roles.length,
roles: roles
});
},

updateRole: async (req: Request, res: Response) => {
const { id } = req.params;
const {name} = req.body
const existingRole = await roleService.findRoleByName(name);
if (existingRole) {
return res.status(400).json({ message: 'Role already exists' });
}
const updatedRoleData: Optional<IRole, "id"> = req.body;
const updatedRole = await roleService.updateRole(Number(id), updatedRoleData);
if (!updatedRole) {
return res.status(404).json({ message: `Role with id: ${id} not found` });
}
res.status(200).json({
message: 'Role updated successfully',
updatedRole: updatedRole
});
},

deleteRole: async (req: Request, res: Response) => {
const { id } = req.params;
const deleted = await roleService.deleteRole(Number(id));
if (!deleted) {
return res.status(404).json({ message: 'Role not found' });
}
res.status(200).json({
status: 200,
message: 'Role deleted successfully'
});
}
};
34 changes: 31 additions & 3 deletions src/controllers/userControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { verifyOtpTemplate } from "../email-templates/verifyotp";

import { getProfileServices, updateProfileServices } from "../services/user.service";
import uploadFile from "../utils/handleUpload";
import { updateUserRoleService } from "../services/user.service";
export const fetchAllUsers = async (req: Request, res: Response) => {
try {
const users = await userService.getAllUsers();
Expand Down Expand Up @@ -53,7 +54,8 @@ export const userLogin = async (req: Request, res: Response) => {
message: " User email or password is incorrect!",
});
} else {
if (user.role.includes("seller")) {
// @ts-ignore
if (user.userRole.name === "seller") {
const token = Math.floor(Math.random() * 90000 + 10000);
//@ts-ignore
await Token.create({ token: token, userId: user.id });
Expand All @@ -63,9 +65,18 @@ export const userLogin = async (req: Request, res: Response) => {
message: "OTP verification code has been sent ,please use it to verify that it was you",
});
} else {
const userInfo = {

id: user.id,
email: user.email,
roleId: user.userRole?.id,
roleName: user.userRole!.name

}
return res.status(200).json({
status: 200,
message: "Logged in",
userInfo: userInfo,
token: accessToken,
});
}
Expand All @@ -77,8 +88,8 @@ export const createUserController = async (req: Request, res: Response) => {
const { name, email, username, password, role } = req.body;

try {
const { name, email, username, password, role } = req.body;
const user = await createUserService(name, email, username, password, role);
const { name, email, username, password } = req.body;
const user = await createUserService(name, email, username, password);
if (!user || user == null) {
return res.status(409).json({
status: 409,
Expand Down Expand Up @@ -272,3 +283,20 @@ export const updateProfileController = async (req: Request, res: Response) => {
res.status(500).json({ error: 'Internal server error' });
}
}

export const updateUserRole = async (req: Request, res: Response) => {
const { roleId } = req.body;
const userId = parseInt(req.params.id);

try {

const userToUpdate = await updateUserRoleService(userId, roleId);

res.status(200).json({
message: 'User role updated successfully',
});
}
catch (error: any) {
res.status(500).json({ message: 'Role or User Not Found' });
}
};
Loading

0 comments on commit aa23d2c

Please sign in to comment.