Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions __tests__/email.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { IEmail } from '../src/interfaces/email';

function isValidEmail(email: IEmail): boolean {
return (
typeof email.from === 'string' &&
typeof email.to === 'string' &&
typeof email.subject === 'string' &&
typeof email.text === 'string' &&
(email.cc === undefined || typeof email.cc === 'string') &&
(email.bcc === undefined || typeof email.bcc === 'string') &&
(email.html === undefined || typeof email.html === 'string')
);
}

describe('IEmail Interface', () => {
test('should accept a valid email object', () => {
const validEmail: IEmail = {
from: 'test@example.com',
to: 'recipient@example.com',
subject: 'Test Subject',
text: 'Test message body',
};

expect(isValidEmail(validEmail)).toBe(true);
});

test('should accept an email with optional fields', () => {
const validEmailWithOptionalFields: IEmail = {
from: 'test@example.com',
to: 'recipient@example.com',
subject: 'Test Subject',
text: 'Test message body',
cc: 'cc@example.com',
bcc: 'bcc@example.com',
html: '<p>Test message body</p>',
};

expect(isValidEmail(validEmailWithOptionalFields)).toBe(true);
});

test('should reject an email without required fields', () => {
const invalidEmail: any = {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to specify the any type for invalidEmail because TypeScript will infer its type based on the assigned value. This ensures that passing invalidEmail to isValidEmail will trigger a compiler error if the email object is invalid. As the image below
image

from: 'test@example.com',
subject: 'Test Subject',
text: 'Test message body',
};

expect(isValidEmail(invalidEmail)).toBe(false);
});

test('should reject an email with invalid types', () => {
const invalidEmail: any = {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

from: 'test@example.com',
to: 123,
subject: 'Test Subject',
text: 'Test message body',
};

expect(isValidEmail(invalidEmail)).toBe(false);
});
});
51 changes: 51 additions & 0 deletions __tests__/express.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { APIRequest, APIResponse } from '../src/interfaces/express';
import { Request, Response } from 'express';

describe('APIRequest and APIResponse Interfaces', () => {
it('should correctly type the body of APIRequest', () => {
interface ExampleBody {
testName: string;
age: number;
}

const req: APIRequest<ExampleBody> = {
body: {
testName: 'John Doe',
age: 30,
},
} as APIRequest<ExampleBody>;

expect(req.body.testName).toBe('John Doe');
expect(req.body.age).toBe(30);
});

it('should correctly type the body of APIResponse', () => {
interface ExampleResponseBody {
success: boolean;
data: string;
}

const res: APIResponse<ExampleResponseBody> = {
body: {
success: true,
data: 'Operation successful',
},
} as APIResponse<ExampleResponseBody>;

expect(res.body.success).toBe(true);
expect(res.body.data).toBe('Operation successful');
});

it('should allow APIRequest and APIResponse without generics', () => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain a bit more about this test case. Why it's necessary to test if APIRequest and APIResponse can be cast as non-generic?

const req: APIRequest = {
body: undefined,
} as APIRequest;

const res: APIResponse = {
body: undefined,
} as APIResponse;

expect(req.body).toBeUndefined();
expect(res.body).toBeUndefined();
});
});
46 changes: 46 additions & 0 deletions __tests__/otp.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Model } from "mongoose";
import { IOtpDoc, IOtpModel } from "../src/interfaces/otp";

const OtpModel: IOtpModel = {
create: jest.fn(),
findOne: jest.fn(),
} as any;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

: IOtpModel infer the type of OtpModel, not need to tell the TypeScript compiler to treat it as any type. Besides, avoid using any

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the idea of mocking and testing these two functions—it's really helpful and great!


beforeEach(() => {
jest.clearAllMocks();
});

describe('Otp Model Tests', () => {
it('should create an OTP correctly', async () => {
const otpData = {
email: "test@example.com",
otp: "123456",
createdAt: new Date(),
};

(OtpModel.create as jest.Mock).mockResolvedValueOnce(otpData);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the function create is defined correctly at the previous declaration step, not need to convert the data type into jest.Mock again here


const result = await OtpModel.create(otpData);

expect(OtpModel.create).toHaveBeenCalledWith(otpData);

expect(result).toEqual(otpData);
});

it('should validate an OTP correctly', async () => {
const email = "test@example.com";
const otp = "123456";
const otpData = {
email,
otp,
createdAt: new Date(),
};

(OtpModel.findOne as jest.Mock).mockResolvedValueOnce(otpData);

const result = await OtpModel.findOne({ email, otp });

expect(OtpModel.findOne).toHaveBeenCalledWith({ email, otp });
expect(result).toEqual(otpData);
});
});
96 changes: 96 additions & 0 deletions __tests__/rbac.middleware.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import validateToken from '../src/middleware/rbac.middleware';
import User from '../src/models/user.model';
import { Request, Response, NextFunction } from 'express';
import { UserRole } from '../src/constants/userRoles';

jest.mock('../src/models/user.model');

describe('validateToken Middleware', () => {
let req: Partial<Request>;
let res: Partial<Response>;
let next: NextFunction;

beforeEach(() => {
req = {
headers: {},
};
res = {
status: jest.fn().mockReturnThis(),
json: jest.fn(),
};
next = jest.fn();
});

it('should return 401 if no authorization header is present', async () => {
req.headers = {};

await validateToken(req as Request, res as Response, next);

expect(res.status).toHaveBeenCalledWith(401);
expect(res.json).toHaveBeenCalledWith({ message: 'No authorization header found' });
});

it('should return 401 if authorization header format is incorrect', async () => {
req.headers = {
authorization: 'InvalidToken',
};

await validateToken(req as Request, res as Response, next);

expect(res.status).toHaveBeenCalledWith(401);
expect(res.json).toHaveBeenCalledWith({ message: 'Invalid authorization header format. Expected format: Bearer <token>' });
});

it('should return 404 if user is not found', async () => {
req.headers = {
authorization: 'Bearer validToken',
};
(User.findOne as jest.Mock).mockResolvedValueOnce(null);

await validateToken(req as Request, res as Response, next);

expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalledWith({ message: 'Cannot find the user' });
});

it('should return 403 if user role is not admin', async () => {
req.headers = {
authorization: 'Bearer validToken',
};
const mockUser = {
role: UserRole.visitor,
};
(User.findOne as jest.Mock).mockResolvedValueOnce(mockUser);

await validateToken(req as Request, res as Response, next);

expect(res.status).toHaveBeenCalledWith(403);
expect(res.json).toHaveBeenCalledWith({ message: 'Access denied' });
});

it('should call next if token is valid and user is admin', async () => {
req.headers = {
authorization: 'Bearer validToken',
};
const mockUser = {
role: UserRole.ADMIN,
};
(User.findOne as jest.Mock).mockResolvedValueOnce(mockUser);

await validateToken(req as Request, res as Response, next);

expect(next).toHaveBeenCalled();
});

it('should return 500 if an error occurs', async () => {
req.headers = {
authorization: 'Bearer validToken',
};
(User.findOne as jest.Mock).mockRejectedValueOnce(new Error('Database error'));

await validateToken(req as Request, res as Response, next);

expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalledWith({ message: 'Database error' });
});
});
7 changes: 7 additions & 0 deletions __tests__/sum.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const sum = require('../sum');

test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
})

//first test to be sure that everything works
40 changes: 40 additions & 0 deletions __tests__/user.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
it('should generate a token correctly', async () => {
const userDoc = {
generateToken: jest.fn().mockResolvedValueOnce("new_mocked_token"),
};

const token = await userDoc.generateToken();

expect(userDoc.generateToken).toHaveBeenCalled();
expect(token).toBe("new_mocked_token");
});

it('should delete the token correctly', async () => {
const userDoc = {
deleteToken: jest.fn().mockResolvedValueOnce(undefined),
};

await userDoc.deleteToken();

expect(userDoc.deleteToken).toHaveBeenCalled();
});

it('should convert user to JSON correctly excluding the password', () => {
const jsonResponse = {
name: "John Doe",
email: "john@example.com",
role: "user",
token: "mocked_token",
createdAt: new Date(),
};

const userDoc = {
toJSON: jest.fn().mockReturnValueOnce(jsonResponse),
};

const result = userDoc.toJSON();

expect(userDoc.toJSON).toHaveBeenCalled();
expect(result).toEqual(jsonResponse);
expect(result).not.toHaveProperty('password');
});
32 changes: 32 additions & 0 deletions __tests__/userRoles.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { UserRole, TUserRole } from '../src/constants/userRoles';

describe('UserRole', () => {
it('should have correct role values', () => {
expect(UserRole.ADMIN).toBe('admin');
expect(UserRole.visitor).toBe('user');
expect(UserRole.GUEST).toBe('guest');
});

it('should contain only defined roles', () => {
const roles: string[] = [UserRole.ADMIN, UserRole.visitor, UserRole.GUEST];
const uniqueRoles = new Set(roles);

expect(uniqueRoles.size).toBe(roles.length);
expect(roles).toEqual(expect.arrayContaining([UserRole.ADMIN, UserRole.visitor, UserRole.GUEST]));
});
});

describe('TUserRole', () => {
it('should only allow valid UserRole values', () => {
const validRoles: TUserRole[] = [UserRole.ADMIN, UserRole.visitor, UserRole.GUEST];
const invalidRoles: string[] = ['unknown', 'admin_user', 'guest_user'];

validRoles.forEach(role => {
expect(validRoles).toContain(role);
});

invalidRoles.forEach(role => {
expect(validRoles).not.toContain(role);
});
});
});
Loading