Skip to content

Commit

Permalink
Merge pull request #65 from atlp-rwanda/ft-verify-registered-user-#18…
Browse files Browse the repository at this point in the history
…7419164

Ft: auth (new user needs to verify one's account before login) #187419164
  • Loading branch information
teerenzo authored May 29, 2024
2 parents d6670df + 387960e commit 04225b4
Show file tree
Hide file tree
Showing 16 changed files with 386 additions and 19 deletions.
3 changes: 3 additions & 0 deletions __test__/cart.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,22 @@ describe("testing cart", () => {
name: "admin123",
username: "admin123",
email: "[email protected]",
isVerified:true,
password: await bcrypt.hash("password", 10),
roleId: 3,
};

const testBuyer = {
name: "buyer123",
username: "buyer123",
isVerified:true,
email: "[email protected]",
password: await bcrypt.hash("password", 10),
};
const testSeller = {
name: "seller123",
username: "seller123",
isVerified:true,
email: "[email protected]",
password: await bcrypt.hash("password", 10),
};
Expand Down
3 changes: 3 additions & 0 deletions __test__/payment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ describe("test stripe api payment", () => {
const testAdmin = {
name: "admin123",
username: "admin123",
isVerified:true,
email: "[email protected]",
password: await bcrypt.hash("password", 10),
roleId: 3,
Expand All @@ -37,13 +38,15 @@ describe("test stripe api payment", () => {
const testBuyer = {
name: "buyer123",
username: "buyer123",
isVerified:true,
email: "[email protected]",
password: await bcrypt.hash("password", 10),
};

const testSeller = {
name: "seller123",
username: "seller123",
isVerified:true,
email: "[email protected]",
password: await bcrypt.hash("password", 10),
};
Expand Down
48 changes: 40 additions & 8 deletions __test__/product.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@ import User from "../src/sequelize/models/users";
import bcrypt from "bcryptjs";
import { Role } from "../src/sequelize/models/roles";
import redisClient from "../src/config/redis";
import { response } from "express";
import { placeOrder } from "../src/services/payment.service";
import Cart from "../src/sequelize/models/Cart";
import CartItem from "../src/sequelize/models/CartItem";
import OrderItem from "../src/sequelize/models/orderItems";
import * as userService from "../src/services/user.service"
import { generateVerificationToken } from "../src/utils/generateResetToken";

jest.mock("../src/services/mail.service", () => ({
sendEmailService: jest.fn(),
Expand All @@ -25,6 +24,7 @@ jest.mock("../src/services/mail.service", () => ({
const userData: any = {
name: "yvanna",
username: "testuser",
isVerified:true,
email: "[email protected]",
role:"seller",
password: "test1234",
Expand All @@ -41,6 +41,7 @@ const product:any = {
name: "pens",
images: ["image1.jpg", "image2.jpg", "image3.jpg", "image4.jpg"],
stockQuantity: 8,

price: 5000,
discount: 3.5,
categoryID: 1,
Expand All @@ -49,6 +50,7 @@ const product:any = {
const dummyBuyer = {
name: "test user",
username: "testUser",
isVerified:true,
email: "[email protected]",
password: "soleil00",
}
Expand Down Expand Up @@ -82,8 +84,7 @@ describe("Testing product Routes", () => {
])

await User.create(testAdmin);

const dummy = await request(app).post("/api/v1/users/register").send(dummySeller);
// await User.create(dummySeller);
await Product.destroy({});
await Category.destroy({truncate:true});
} catch (error) {
Expand All @@ -97,6 +98,7 @@ describe("Testing product Routes", () => {
await sequelize.close();
await redisClient.quit()
});

test("should return 201 and create a new user when registering successfully", async () => {
const response = await request(app)
.post("/api/v1/users/register")
Expand All @@ -110,7 +112,20 @@ describe("Testing product Routes", () => {
.send(dummyBuyer);
expect(response.status).toBe(201);
})
test('should return 201 and register a dummy buyer user', async () => {
const response = await request(app)
.post("/api/v1/users/register")
.send(dummySeller);
expect(response.status).toBe(201);
})
let buyerToken: any;
it("It should verify user account.",async()=>{
const token = generateVerificationToken('[email protected]', 60);
const response = await request(app)
.get(`/api/v1/users/verify-user?token=${token}`)
expect(response.status).toBe(200)
expect(response.body.message).toBe('User verified successfully.')
},60000)

test("should login an buyer", async () =>{
const response = await request(app).post("/api/v1/users/login").send({
Expand All @@ -127,21 +142,37 @@ describe("Testing product Routes", () => {
.send(product)
expect(response.status).toBe(401);
},2000);


it("It should verify user account.",async()=>{
const token = generateVerificationToken('[email protected]', 60);
const response = await request(app)
.get(`/api/v1/users/verify-user?token=${token}`)
expect(response.status).toBe(200)
expect(response.body.message).toBe('User verified successfully.')
},60000)
test("should login an Admin", async () =>{
const response = await request(app).post("/api/v1/users/login").send({
email: "[email protected]",
password: "password"
password: "password"
})
adminToken = response.body.token;
expect(response.status).toBe(200)
});
it("It should verify user account.",async()=>{
const token = generateVerificationToken(dummySeller.email, 60);
const response = await request(app)
.get(`/api/v1/users/verify-user?token=${token}`)
expect(response.status).toBe(200)
expect(response.body.message).toBe('User verified successfully.')
},60000)

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);
// expect(logDummySeller.status).toBe(200);
expect(logDummySeller.body.message).toBe('Logged in')
token = logDummySeller.body.token;

const seller = await userService.getUserByEmail(dummySeller.email)
Expand All @@ -154,6 +185,7 @@ describe("Testing product Routes", () => {
})
.set("Authorization", "Bearer " + adminToken);
expect(response.status).toBe(200);
expect(response.body.message).toBe('User role updated successfully');

});

Expand Down Expand Up @@ -482,7 +514,7 @@ test('It should return status 200 for removed category',async() =>{
})
it("changing product availability of product which does not exist", async ()=>{
const response = await request(app)
.patch(`/api/v1/products/${91}/status`)
.patch(`/api/v1/products/${4444444}/status`)
.set("Authorization", "Bearer " + token);
expect(response.body.message).toBe('Product not found')
})
Expand Down
52 changes: 50 additions & 2 deletions __test__/user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { QueryTypes } from "sequelize";
// import redisClient from "../src/config/redis";
import Redis from "ioredis";
import { env } from "../src/utils/env";
import { generateResetToken } from "../src/utils/generateResetToken";
import { generateResetToken, generateVerificationToken } from "../src/utils/generateResetToken";

let redisClient: any;

Expand All @@ -43,6 +43,7 @@ const dummySeller = {
};
const userTestData = {
newPassword: "Test@123",
isVerified:true,
confirmPassword: "Test@123",
wrongPassword: "Test456",
};
Expand Down Expand Up @@ -114,6 +115,14 @@ describe("Testing user Routes", () => {
expect(response.status).toBe(201);
}, 20000);

it("It should verify user account.",async()=>{
const token = generateVerificationToken(userData.email, 60);
const response = await request(app)
.get(`/api/v1/users/verify-user?token=${token}`)
expect(response.status).toBe(200)
expect(response.body.message).toBe('User verified successfully.')
},60000)


test("should return 409 when registering with an existing email", async () => {
User.create(userData);
Expand Down Expand Up @@ -143,6 +152,7 @@ describe("Testing user Routes", () => {
password: userData.password,
});
expect(response.status).toBe(200);
expect(response.body.message).toBe("Logged in");
token = response.body.token;
});

Expand Down Expand Up @@ -209,21 +219,36 @@ describe("Testing user Routes", () => {
expect(response.body.status).toBe(401);
spyonOne.mockRestore();
}, 20000);
it("It should verify user account.",async()=>{
const token = generateVerificationToken('[email protected]', 60);
const response = await request(app)
.get(`/api/v1/users/verify-user?token=${token}`)
expect(response.status).toBe(200)
expect(response.body.message).toBe('User verified successfully.')
},60000)

test("should login an Admin", async () =>{
const response = await request(app).post("/api/v1/users/login").send({
email: "[email protected]",
password: "password"
password: "password"
})
adminToken = response.body.token;
});
it("It should verify user account.",async()=>{
const token = generateVerificationToken(dummySeller.email, 60);
const response = await request(app)
.get(`/api/v1/users/verify-user?token=${token}`)
expect(response.status).toBe(200)
expect(response.body.message).toBe('User verified successfully.')
},60000)

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);
expect(logDummySeller.body.message).toBe("Logged in");
const seller = await userServices.getUserByEmail(dummySeller.email);
const dummySellerId = seller?.id;

Expand All @@ -235,6 +260,7 @@ describe("Testing user Routes", () => {
.set("Authorization", "Bearer " + adminToken);

expect(response.status).toBe(200);
// expect(response.body.message).toBe('User role updated successfully');

});

Expand Down Expand Up @@ -563,6 +589,28 @@ describe('Patch /api/v1/users/reset-password', () => {
},60000);
});

describe("Verifying user account",()=>{
it("It should verify user account.",async()=>{
await User.create(userData)
const token = generateVerificationToken(userData.email, 60);
const response = await request(app)
.get(`/api/v1/users/verify-user?token=${token}`)
expect(response.status).toBe(200)
expect(response.body.message).toBe('User verified successfully.')
},60000)

it("It should send a verification link.",async()=>{
const response = await request(app)
.post('/api/v1/users/verify-user-email')
.send({
email:userData.email
})
expect(response.status).toBe(409)
expect(response.body.message).toBe("User is already verified.")
},60000)

})

afterAll(async () => {
try {
await sequelize.query('TRUNCATE TABLE profiles, users CASCADE');
Expand Down
30 changes: 29 additions & 1 deletion src/controllers/userControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export const createUserController = async (req: Request, res: Response) => {
}
return res.status(201).json({
status: 201,
message: "User successfully created."
message: "User successfully created. check your inbox to verify your email"
});
} catch (err: any) {
if (err.name === "UnauthorizedError" && err.message === "User already exists") {
Expand Down Expand Up @@ -194,6 +194,7 @@ export const handleSuccess = async (req: Request, res: Response) => {
name: user.displayName,
email: user.emails[0].value,
username: user.name.familyName,
isVerified:true,
//@ts-ignore
password: null
});
Expand Down Expand Up @@ -405,3 +406,30 @@ export const resetPasswordController = async (req: Request, res: Response): Prom
return res.status(500).json({ message: 'Internal server error.' });
}
};

export const verifyUserEmailController = async (req: Request, res: Response) => {
try {
// Extract email from the request body
const {email} = req.body;

const { error } = Emailschema.validate(req.body);
if (error) {
const cleanErrorMessage = error.details.map(detail => detail.message.replace(/['"]/g, '').trim()).join(', ')
return res.status(400).json({ message: cleanErrorMessage });
}
const result = await userService.verifyUserEmail(email);
res.status(result.status).json({ message: result.message });
} catch (error) {
res.status(500).json({ error: 'Failed to send verification email.' });
}
};
export const verifyUserController = async (req: Request, res: Response): Promise<void> => {
try {
// Extract token from the request query
const token = req.query.token as string;
const result = await userService.verifyNewUser(token);
res.status(result.status).json({ message: result.message });
} catch (error) {
res.status(500).json({ error: 'Failed to verify new user.' });
}
};
5 changes: 4 additions & 1 deletion src/docs/swagger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
sendResetLink,
updateForgotPassword,
verifyUserAccessToken,
verifyUserEmail,
} from "./users";
import {
getProducts,
Expand All @@ -39,7 +40,6 @@ import { getCategories, addCategories, getSingleCategory, updateCategories, dele
deleteRole
} from "./roledoc";
import { AddToWishes, deleteWish, getWishes, getWishesByProduct, wishSchema } from "./wishes";
import { joinChats } from "./chats";
import { addItemToCartDoc, clearAllProductFromCartDoc, removeProductFromCartDoc, updateProductQuantityDoc, viewCartDoc } from "./cart";
import { getAllNotifications, readNotification } from "./notifications";
import { homepage } from "./home";
Expand Down Expand Up @@ -115,6 +115,9 @@ const options = {
"/api/v1/users/reset-password": {
patch: updateForgotPassword,
},
"/api/v1/users/verify-user-email": {
post: verifyUserEmail,
},

"/api/v1/users/me": {
post: verifyUserAccessToken,
Expand Down
Loading

0 comments on commit 04225b4

Please sign in to comment.