Skip to content

Commit

Permalink
Merge pull request #65 from atlp-rwanda/ft-update-user-status
Browse files Browse the repository at this point in the history
feat-implement-user-status-update
  • Loading branch information
faid-terence authored May 7, 2024
2 parents 70e33ad + 2584fce commit 4617c75
Show file tree
Hide file tree
Showing 16 changed files with 565 additions and 2 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ env:
HOST: ${{secrets.HOST}}
AUTH_EMAIL: ${{secrets.AUTH_EMAIL}}
AUTH_PASSWORD: ${{secrets.AUTH_PASSWORD}}
JWT_SECRET: ${{secrets.JWT_SECRET}}

jobs:
build-lint-test-coverage:
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"axios": "^1.6.8",
"bcrypt": "^5.1.1",
"class-validator": "^0.14.1",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"cross-env": "^7.0.3",
"dotenv": "^16.4.5",
Expand All @@ -34,6 +35,7 @@
"highlight.js": "^11.9.0",
"jsend": "^1.1.0",
"jsonwebtoken": "^9.0.2",
"mailgen": "^2.0.28",
"morgan": "^1.10.0",
"nodemailer": "^6.9.13",
"nodemon": "^3.1.0",
Expand All @@ -54,6 +56,7 @@
"@eslint/js": "^9.1.1",
"@types/bcrypt": "^5.0.2",
"@types/body-parser": "^1.19.5",
"@types/cookie-parser": "^1.4.7",
"@types/cors": "^2.8.17",
"@types/dotenv": "^8.2.0",
"@types/eslint": "^8.56.10",
Expand Down
91 changes: 91 additions & 0 deletions src/__test__/isAllowed.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { NextFunction, Request, Response } from "express";
import { checkUserStatus } from "../middlewares/isAllowed";
import { dbConnection } from '../startups/dbConnection';
import { getConnection } from 'typeorm';
import { User } from '../entities/User';
import { responseError } from '../utils/response.utils';
import { v4 as uuid } from 'uuid';

jest.mock('../utils/response.utils');

let reqMock: Partial<Request>;
let resMock: Partial<Response>;
let nextMock: NextFunction;

const activeUserId = uuid();
const suspendedUserId = uuid();

beforeAll(async () => {
const connection = await dbConnection();

const userRepository = connection?.getRepository(User);

const activeUser = new User();
activeUser.id = activeUserId;
activeUser.firstName = 'John2';
activeUser.lastName = 'Doe';
activeUser.email = '[email protected]';
activeUser.password = 'password';
activeUser.gender = 'Male';
activeUser.phoneNumber = '12347';
activeUser.photoUrl = 'https://example.com/photo.jpg';

await userRepository?.save(activeUser);

const suspendedUser = new User();
suspendedUser.id = suspendedUserId;
suspendedUser.firstName = 'John2';
suspendedUser.lastName = 'Doe';
suspendedUser.email = '[email protected]';
suspendedUser.password = 'password';
suspendedUser.gender = 'Male';
suspendedUser.status = 'suspended';
suspendedUser.phoneNumber = '12349';
suspendedUser.photoUrl = 'https://example.com/photo.jpg';

await userRepository?.save(suspendedUser);
});

afterAll(async () => {
const connection = getConnection();
const userRepository = connection.getRepository(User);

Check warning on line 51 in src/__test__/isAllowed.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

'userRepository' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 51 in src/__test__/isAllowed.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

'userRepository' is assigned a value but never used

// Close the connection to the test database
await connection.close();
});

describe('Middleware - checkUserStatus', () => {
beforeEach(() => {
reqMock = {};
resMock = {
status: jest.fn().mockReturnThis(),
json: jest.fn()
};
nextMock = jest.fn();
});

Check warning on line 66 in src/__test__/isAllowed.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

Trailing spaces not allowed
it('should return 401 if user is not authenticated', async () => {
await checkUserStatus(reqMock as Request, resMock as Response, nextMock);
expect(responseError).toHaveBeenCalledWith(resMock, 401, 'Authentication required');
});

it('should return 401 if user is not found', async () => {
reqMock = { user: { id: uuid() } };

await checkUserStatus(reqMock as Request, resMock as Response, nextMock);

expect(responseError).toHaveBeenCalledWith(resMock, 401, 'User not found');
});

it('should pass if user status is active', async () => {
reqMock = { user: { id: activeUserId } };
await checkUserStatus(reqMock as Request, resMock as Response, nextMock);
expect(nextMock).toHaveBeenCalled();
});

Check warning on line 85 in src/__test__/isAllowed.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

Trailing spaces not allowed
it('should return 403 if user status is suspended', async () => {
reqMock = { user: { id: suspendedUserId } };
await checkUserStatus(reqMock as Request, resMock as Response, nextMock);
expect(responseError).toHaveBeenCalledWith(resMock, 403, 'You have been suspended. Please contact our support team.');
});
});
3 changes: 3 additions & 0 deletions src/__test__/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ beforeAll(async () => {
const connectionOptions = await getConnectionOptions();

await createConnection({ ...connectionOptions, name: 'testConnection' });

});



afterAll(async () => {
const connection = getConnection('testConnection');
const userRepository = connection.getRepository(User);
Expand Down
165 changes: 165 additions & 0 deletions src/__test__/userStatus.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import request from 'supertest';
import jwt from 'jsonwebtoken';
import { app, server } from '../index';
import {getRepository } from 'typeorm';
import { getConnection } from 'typeorm';
import { dbConnection } from '../startups/dbConnection';
import { User } from '../entities/User';
import { v4 as uuid } from 'uuid';

const adminUserId = uuid();

const jwtSecretKey = process.env.JWT_SECRET || "";

beforeAll(async () => {
const connection = await dbConnection();

const userRepository = connection?.getRepository(User);

const adminUser = new User();
adminUser.id = adminUserId;
adminUser.firstName = 'remjsa';
adminUser.lastName = 'djkchd';
adminUser.email = '[email protected]';
adminUser.password = 'passwordadmin';
adminUser.userType = 'Admin';
adminUser.gender = 'Male';
adminUser.phoneNumber = '126380996347';
adminUser.photoUrl = 'https://example.com/photo.jpg';

await userRepository?.save(adminUser);

adminUser.role = 'ADMIN';
adminUser.verified = true;
await userRepository?.save(adminUser);
});

afterAll(async () => {
const connection = getConnection();
const userRepository = connection.getRepository(User);

Check warning on line 39 in src/__test__/userStatus.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

'userRepository' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 39 in src/__test__/userStatus.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

'userRepository' is assigned a value but never used

// Close the connection to the test database
await connection.close();
server.close();
});

const data = {
id: adminUserId,
email: "[email protected]"
};

const testUser = {
firstName: 'John',
lastName: 'Doe',
email: '[email protected]',
password: 'password',
gender: 'Male',
phoneNumber: '4223567890',
photoUrl: 'https://example.com/photo.jpg',
};

describe('POST /user/deactivate', () => {
it('should deactivate a user', async () => {
await request(app)
.post('/user/register')
.send(testUser);

Check warning on line 66 in src/__test__/userStatus.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

Trailing spaces not allowed
const token = jwt.sign(data,jwtSecretKey);

const response = await request(app)
.post(`/user/deactivate`)
.set("Cookie", `token=${token}`)
.send({email: `${testUser.email}`});
expect(response.status).toBe(200);
expect(response.body.message).toBe('User deactivated successfully');
});

it('should return 404 when email is not submitted', async ()=> {

Check warning on line 78 in src/__test__/userStatus.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

Trailing spaces not allowed
const token = jwt.sign(data,jwtSecretKey);
const response = await request(app)
.post(`/user/deactivate`)
.set("Cookie", `token=${token}`)

expect(response.status).toBe(404);
expect(response.body.error).toBe('Email is needed');
})
it('should return message "User is already suspended" if user is already suspended', async () => {

const token = jwt.sign(data,jwtSecretKey);
const response = await request(app)
.post(`/user/deactivate`)
.set("Cookie", `token=${token}`)
.send({email: `${testUser.email}`});

expect(response.status).toBe(200);
expect(response.body.message).toBe('User is already suspended');
});

it('should return 404 if user not found when deactivating', async () => {

const token = jwt.sign(data,jwtSecretKey);
const response = await request(app)
.post(`/user/deactivate`)
.set("Cookie", `token=${token}`)
.send({email: "[email protected]"});

expect(response.status).toBe(404);
expect(response.body.error).toBe('User not found');
});

});

describe('POST /user/activate', () => {
it('should activate a user', async () => {
const token = jwt.sign(data,jwtSecretKey);

const response = await request(app)
.post(`/user/activate`)
.set("Cookie", `token=${token}`)
.send({email: `${testUser.email}`});

expect(response.status).toBe(200);
expect(response.body.message).toBe('User activated successfully');

});

it('should return 404 when email is not submitted', async ()=> {
const token = jwt.sign(data,jwtSecretKey);
const response = await request(app)
.post(`/user/activate`)
.set("Cookie", `token=${token}`)

expect(response.status).toBe(404);
expect(response.body.error).toBe('Email is needed');
})

it('should return message "User is already active" if user is already active', async () => {
const token = jwt.sign(data,jwtSecretKey);
const response = await request(app)
.post(`/user/activate`)
.set("Cookie", `token=${token}`)
.send({email: `${testUser.email}`});

expect(response.status).toBe(200);
expect(response.body.message).toBe('User is already active');

const userRepository = getRepository(User);
const user = await userRepository.findOne({ where: { email: testUser.email } });
if (user) {
await userRepository.remove(user);
}
});

it('should return 404 if user not found when activating', async () => {
const token = jwt.sign(data,jwtSecretKey);
const response = await request(app)
.post('/user/activate')
.set("Cookie", `token=${token}`)
.send({email: "[email protected]"});

expect(response.status).toBe(404);
expect(response.body.error).toBe('User not found');
});

});
11 changes: 11 additions & 0 deletions src/controllers/authController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
} from '../services';
import { userPasswordResetService } from '../services/userServices/userPasswordResetService';
import { sendPasswordResetLinkService } from '../services/userServices/sendResetPasswordLinkService';
import { activateUserService } from '../services/updateUserStatus/activateUserService';
import { deactivateUserService } from '../services/updateUserStatus/deactivateUserService';

export const userRegistration = async (req: Request, res: Response) => {
await userRegistrationService(req, res);
Expand Down Expand Up @@ -44,3 +46,12 @@ export const userPasswordReset = async (req: Request, res: Response) => {
export const sendPasswordResetLink = async (req: Request, res: Response) => {
await sendPasswordResetLinkService(req, res);
}

export async function activateUser(req: Request, res: Response) {
await activateUserService(req,res);
}

export async function disactivateUser(req: Request, res: Response) {
await deactivateUserService(req,res);
}

3 changes: 1 addition & 2 deletions src/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@

export * from './authController';
export * from './authController';
Empty file.
19 changes: 19 additions & 0 deletions src/helper/verify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import jwt from 'jsonwebtoken';
import dotenv from 'dotenv';

dotenv.config();

const jwtSecretKey = process.env.JWT_SECRET;

if (!jwtSecretKey) {
throw new Error('JWT_SECRET is not defined in the environment variables.');
}

export const verifiedToken = (token: string): any => {
try {
return jwt.verify(token, jwtSecretKey);
} catch (err) {
console.error(err);
return null;
}
};
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import dotenv from 'dotenv';
import router from './routes';
import { addDocumentation } from './startups/docs';
import 'reflect-metadata';
import cookieParser from 'cookie-parser';

import { CustomError, errorHandler } from './middlewares/errorHandler';
import morgan from 'morgan';
Expand All @@ -14,6 +15,7 @@ export const app = express();
const port = process.env.PORT || 8000;
app.use(express.json());

app.use(cookieParser());
app.use(cors({ origin: '*' }));
app.use(router);
addDocumentation(app);
Expand Down
Loading

0 comments on commit 4617c75

Please sign in to comment.