Untitled

mail@pastecode.io avatar
unknown
plain_text
a month ago
27 kB
1
Indexable
Never
/* eslint-disable @typescript-eslint/no-unused-vars */
import { expect } from 'chai';
import { describe, it, beforeEach, afterEach, before } from 'mocha';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import * as nodemailer from 'nodemailer';
import sinon from 'sinon';
import request from 'supertest';

import app from '../src/app';
// eslint-disable-next-line import/order
import { UserController } from '../src/controllers/user.controller';
// eslint-disable-next-line import/order
import UserModel from '../src/models/user.model';
const mockAccessToken = 'mockedAccessToken';
const mockRefreshToken = 'mockedRefreshToken';
const mockVerificationToken = 'mockedVerificationToken';
const mockAccessTokenC = 'mockedAccessToken';
const mockRefreshTokenC = 'mockedRefreshToken';
const mockVerificationTokenC = 'mockedVerificationToken';

import { UserService } from '../src/services/user.service';
// import { extractEmailFromPayload } from '../src/utils/authUtils';
import { ResponseHandler } from '../src/utils/handleResponse';

// Define mock users
const mockUser1 = {
  _id: '60f6a1b5f3c1ac3dc0a0a8a0',
  email: 'test1@example.com',
  userType: 'customer',
  status: 'online',
  phoneNumber: '23-788-421',
  accessToken: mockAccessTokenC,
  refreshToken: mockRefreshTokenC,
  verificationToken: mockVerificationTokenC,
};

const mockUser2 = {
  _id: '60f6a1b5f3c1ac3dc0a0a8a2', // Unique _id for the first admin user
  email: 'admin1@example.com',
  userType: 'admin',
  status: 'online',
  phoneNumber: '23-788-421',
  accessToken: mockAccessToken,
  refreshToken: mockRefreshToken,
  verificationToken: mockVerificationToken,
};

const mockUser4 = {
  _id: '60f6a1b5f3c1ac3dc0a0a8a4', // Unique _id for the second admin user
  email: 'admin2@example.com',
  userType: 'admin',
  status: 'online',
  phoneNumber: '23-788-422',
  accessToken: mockAccessToken,
  refreshToken: mockRefreshToken,
  verificationToken: mockVerificationToken,
};

const mockUser3 = {
  phoneNumber: '321-654-0987',
  userType: 'customer',
};
const mockUser = {
  _id: '60f6a1b5f3c1ac3dc0a0a8a5', // Unique _id for the second admin user
  email: 'verify_email@test.com',
  userType: 'customer',
};
// Will Be used for login

//Service

//Controller & Service
describe('User Tests', () => {
  beforeEach(async () => {
    // Clear the database before each test
    await UserModel.deleteMany({});
  });

  afterEach(() => {
    sinon.restore();
  });

  describe('POST /users/addUser', () => {
    it('should create a new user admin', async () => {
      const res = await request(app).post('/users/addUser').send({
        _id: mockUser2._id,
        email: mockUser2.email,
        userType: mockUser2.userType,
      });
      expect(res.status).to.equal(201);
      expect(res.body).to.have.property('data');
    });

    it('should create a new user customer', async () => {
      const res = await request(app).post('/users/addUser').send({
        email: mockUser1.email,
        userType: mockUser1.userType,
      });
      expect(res.status).to.equal(201);
      expect(res.body).to.have.property('data');
    });

    it('should return an error for duplicate email', async () => {
      // Create the first user
      await UserModel.create(mockUser1);
      // Try to create a user with the same email
      const res = await request(app).post('/users/addUser').send({ email: mockUser1.email, userType: 'customer' });
      expect(res.status).to.equal(400);
    });

    it('should create a new user with only phoneNumber', async () => {
      const res = await request(app).post('/users/addUser').send({
        phoneNumber: mockUser3.phoneNumber,
      });
      expect(res.status).to.equal(201);
      expect(res.body).to.have.property('data');
    });

    it('should return an error for duplicate phoneNumber', async () => {
      // Create the first user with mockUser3's phoneNumber
      await UserModel.create(mockUser3);

      // Try to create another user with the same phoneNumber as mockUser3
      const res = await request(app).post('/users/addUser').send({ phoneNumber: mockUser3.phoneNumber });

      expect(res.status).to.equal(400); // Assuming 409 for conflict/error
      // Add additional assertions here based on your API's error response structure
    });

    it('should return an error if neither email nor phoneNumber is provided', async () => {
      const res = await request(app).post('/users/addUser').send({});

      expect(res.status).to.equal(500); // Expecting 500 Internal Server Error
      expect(res.text).to.equal('{"error":"Either email or phoneNumber must be provided."}');
    });
  });

  describe('GET /users/:id', () => {
    it('should retrieve a user by ID', async () => {
      // Create a user in the database
      const createdUser = await UserModel.create(mockUser2);
      const response = await request(app).get(`/users/${createdUser._id}`);
      expect(response.status).to.equal(200);

      // The expected data should match mockUser2
      expect(response.body.data).to.deep.equal({
        _id: mockUser2._id,
        email: mockUser2.email,
      });
    });

    it('should handle user not found', async () => {
      const response = await request(app).get('/users/60f6a1b5f3c1ac3dc0a0a8a1');
      expect(response.status).to.equal(404);
    });
  });

  describe('POST /users/loginUltimate', () => {
    it('should log in an existing user with email', async () => {
      // Create a user in the database
      await UserModel.create(mockUser2);

      const res = await request(app).post('/users/loginUltimate').send({
        email: mockUser2.email,
      });

      expect(res.status).to.equal(200);
      // Add assertions for cookies if needed
    });

    it('should register and send verification email to a new user', async () => {
      const sendVerificationEmailStub = sinon.stub(UserController.sendVerificationEmail).resolves({ status: 201 });

      const res = await request(app).post('/users/loginUltimate').send({
        email: mockUser1.email,
      });

      expect(res.status).to.equal(201);
      expect(sendVerificationEmailStub.calledOnce).to.be.false;

      // Add assertions for cookies if needed
    });

    it('should handle errors during login', async () => {
      sinon.stub(UserModel, 'findOne').throws(new Error('Some error occurred'));

      const res = await request(app).post('/users/loginUltimate').send({
        email: 'nonexistent@example.com',
      });

      expect(res.status).to.equal(400);
    });
  });

  describe('POST /users/logout', () => {
    it('should log out an existing user and clear cookies', async () => {
      await UserModel.create({ ...mockUser1, status: 'online' });

      const res = await request(app).post('/users/logout').send({ email: mockUser1.email });

      expect(res.status).to.equal(200);
      expect(res.body.message).to.equal('Logout successful.');

      // Assertions for cleared cookies
      const cookies = res.headers['set-cookie'];
      expect(cookies).to.be.an('array');
      cookies.forEach((cookie) => {
        expect(cookie).to.match(/^(accessToken=;|refreshToken=;|verificationToken=;)/);
      });
    });

    it('should return an error if the user is not found', async () => {
      // Ensure no user with this email exists
      await UserModel.deleteMany({ email: 'nonexistent@example.com' });

      // Attempt to log out a user who does not exist
      const res = await request(app).post('/users/logout').send({ email: 'nonexistent@example.com' });

      // Check for a 401 Unauthorized response
      expect(res.status).to.equal(401);

      // Confirm the text contains the expected error message as a string
      expect(res.text).to.equal('{"error":"User not found. Maybe already logged out or invalid email."}');
    });

    it('should return an error if the user is already logged out', async () => {
      // Ensure a user with this email exists and is 'offline'
      await UserModel.create({ ...mockUser1, status: 'offline' });

      // Attempt to log out a user who is already 'offline'
      const res = await request(app).post('/users/logout').send({ email: mockUser1.email });

      // Check for a 401 Unauthorized response
      expect(res.status).to.equal(401);

      // Confirm the text contains the expected error message as a string
      expect(res.text.trim()).to.equal(`{"error":"${mockUser1.email} - You are already logged out."}`);
    });
    //an error during logout
    let saveStub;
    beforeEach(() => {
      // Stub the save method before each test
      saveStub = sinon.stub(UserModel.prototype, 'save');
    });

    afterEach(() => {
      // Restore the stubbed method after each test
      sinon.restore();
    });
    describe('ResponseHandler.success method', () => {
      it('should return a successful response with the correct structure', () => {
        // Create a mock response object
        const res = {
          status: sinon.stub().returnsThis(), // Stub the status method to return 'this'
          json: sinon.stub(), // Stub the json method
        };

        // Call the success method of ResponseHandler
        ResponseHandler.success({
          res,
          responseBody: { data: { email: 'test@example.com' }, message: 'Email extracted successfully' },
        });

        // Assert that the status method was called with 200
        expect(res.status.calledWith(200)).to.be.true;

        // Assert that the json method was called with the correct response body
        expect(
          res.json.calledWith({
            data: { email: 'test@example.com' },
            message: 'Email extracted successfully',
          }),
        ).to.be.true;
      });
    });
    it('should handle an internal server error during logout', async () => {
      // Create a mock user with 'online' status
      await UserModel.create({ ...mockUser1, status: 'online' });

      // Simulate a save error
      saveStub.throws(new Error('An error occurred during logout.'));

      // Attempt to log out the user
      const res = await request(app).post('/users/logout').send({ email: mockUser1.email });

      // Check for a 500 Internal Server Error response
      expect(res.status).to.equal(500);

      // Confirm the response contains the expected error message
      // If the response is JSON with an 'error' property:
      expect(res.body.error).to.equal('An error occurred during logout.');

      // If the response is a plain text:
      // expect(res.text).to.equal('"An error occurred during logout."');
    });
  });

  describe('convertPermissions', () => {
    const userController = new UserController();

    it('should handle an empty permissions object', () => {
      const userTypePermissions = {};
      const expectedOutput = {};
      expect(userController.convertPermissions(userTypePermissions)).to.deep.equal(expectedOutput);
    });

    it('should handle a single category of permissions', () => {
      const userTypePermissions = {
        category1: { read: true, write: false },
      };
      const expectedOutput = {
        category1: { read: true, write: false },
      };
      expect(userController.convertPermissions(userTypePermissions)).to.deep.equal(expectedOutput);
    });

    it('should handle multiple categories of permissions', () => {
      const userTypePermissions = {
        category1: { read: true, write: false },
        category2: { edit: true, delete: false },
      };
      const expectedOutput = {
        category1: { read: true, write: false },
        category2: { edit: true, delete: false },
      };
      expect(userController.convertPermissions(userTypePermissions)).to.deep.equal(expectedOutput);
    });
  });

  describe('convertPermissions', () => {
    const userService = new UserService();

    it('should handle an empty permissions object', () => {
      const userTypePermissions = {};
      const expectedOutput = {};
      expect(userService.convertPermissions(userTypePermissions)).to.deep.equal(expectedOutput);
    });

    it('should handle a single category of permissions', () => {
      const userTypePermissions = {
        category1: { read: true, write: false },
      };
      const expectedOutput = {
        category1: { read: true, write: false },
      };
      expect(userService.convertPermissions(userTypePermissions)).to.deep.equal(expectedOutput);
    });

    it('should handle multiple categories of permissions', () => {
      const userTypePermissions = {
        category1: { read: true, write: false },
        category2: { edit: true, delete: false },
      };
      const expectedOutput = {
        category1: { read: true, write: false },
        category2: { edit: true, delete: false },
      };
      expect(userService.convertPermissions(userTypePermissions)).to.deep.equal(expectedOutput);
    });
  });

  describe('GET /users', () => {
    let userServiceStub;

    beforeEach(() => {
      // This stubs the UserService's getAllUsers method
      userServiceStub = sinon.stub(UserService.prototype, 'getAllUsers');
    });

    afterEach(() => {
      sinon.restore();
    });

    it('should return an error if user is not authenticated', async () => {
      const res = await request(app).get('/users/');
      expect(res.status).to.equal(401);
      expect(res.body.message).to.equal('Authorization token is required');
    });
    it('should retrieve all users when the admin user is logged in', async () => {
      await UserModel.create(mockUser2);

      const loginResponse = await request(app).post('/users/loginUltimate').send({ email: mockUser2.email });
      expect(loginResponse.status).to.equal(200);

      expect(loginResponse.headers).to.have.property('set-cookie');
      const cookies = loginResponse.headers['set-cookie'];
      const accessTokenCookie = cookies.find((cookie) => cookie.startsWith('accessToken'));

      expect(accessTokenCookie).to.exist;

      const res = await request(app).get('/users').set('Cookie', accessTokenCookie);

      expect(res.status).to.equal(200);
    });

    it('should return an error if no users are found', async () => {
      userServiceStub.resolves([]); // Simulate an empty user list

      const res = await request(app).get('/users'); // Adjust this if your route is different

      expect(res.status).to.equal(401); // Or whatever your logic is for no users found
      expect(res.body.error).to.equal(undefined);
    });
  });

  describe('POST /users/get-email-from-token', () => {
    let getEmailFromTokenStub;
    const userController = new UserController();
    beforeEach(() => {
      getEmailFromTokenStub = sinon.stub(userController, 'getEmailFromToken');
      // Stub ResponseHandler.internalServerError
      sinon.stub(ResponseHandler, 'internalServerError');
    });

    afterEach(() => {
      sinon.restore();
    });

    it('getEmailFromToken function should return email from a valid token', async () => {
      userController.getEmailFromToken;
    });

    it('getEmailFromToken function should handle invalid token', async () => {
      // Stub extractEmailFromPayload to return null for invalid token
      const invalidToken = 'invalidToken';
      getEmailFromTokenStub.returns(null);

      const response = await request(app).post('/users/get-email-from-token').send({ token: invalidToken });

      expect(response.status).to.equal(400); // Expecting 400 for invalid token
    });

    it('getEmailFromToken function should handle missing token', async () => {
      // Do not provide a token
      getEmailFromTokenStub.returns(null);

      const response = await request(app).post('/users/get-email-from-token').send({});

      expect(response.status).to.equal(400); // Expecting 400 for missing token
    });

    it('should successfully return an email and invoke ResponseHandler.success', async () => {
      // Set up a mock response object
      const res = {
        status: sinon.stub().returnsThis(), // Stub the status method to return the response object itself
        json: sinon.stub(), // Stub the json method
      };

      // Call the success method of ResponseHandler
      ResponseHandler.success({
        res,
        responseBody: { data: { email: 'test@example.com' }, message: 'Email extracted successfully' },
      });

      // Assert that the status method was called with 200
      expect(res.status.calledWith(200)).to.be.true;

      // Additionally, assert that the json method was called with the correct body
      expect(
        res.json.calledWith({
          data: { email: 'test@example.com' },
          message: 'Email extracted successfully',
        }),
      ).to.be.true;
    });

    it('should handle exceptions with ResponseHandler.internalServerError', async () => {
      const brokenToken = 'brokenToken';
      getEmailFromTokenStub.withArgs(brokenToken).throws(new Error('Test error'));

      const response = await request(app).post('/users/get-email-from-token').send({ token: brokenToken });

      expect(response.status).to.equal(400); // Internal Server Error

      // Adjust this line based on the actual error response format of your application
      const errorMessage = response.body.error || response.body.message || JSON.stringify(response.body);
      expect(errorMessage).to.include('Invalid token or email not found');
    });
  });

  describe('GET /users/verify-email/:verificationToken', () => {
    beforeEach(async () => {
      // Clear the database before each test
      await UserModel.deleteMany({});

      // Create and log in an admin user before each test
      const admin = await UserModel.create(mockUser2); // Assuming mockUser2 is an admin
      await request(app).post('/users/loginUltimate').send({
        email: admin.email,
        // Add other necessary login fields if required
      });
    });

    afterEach(() => {
      sinon.restore();
    });

    it('should verify email successfully with valid and unexpired verification token', async () => {
      // Retrieve the logged-in user to get the verification token
      const loggedInUser = await UserModel.findOne({ email: mockUser2.email });

      // Make the request to verify the email with the verification token from the logged-in user
      const res = await request(app).get(`/users/verify-email/${loggedInUser.verificationToken}`);

      // Assert that the email verification was successful
      expect(res.status).to.equal(200);
      expect(res.body.message).to.equal('Email verification successful. You can now log in.');

      // Ensure that the user's verification status is updated in the database
      const updatedUser = await UserModel.findById(loggedInUser._id);
      expect(updatedUser.verificationToken).to.be.undefined;
    });

    it('should handle expired verification token', async () => {
      // Retrieve the logged-in user to get the verification token
      const loggedInUser = await UserModel.findOne({ email: mockUser2.email });

      // Simulate an expired verification token by setting it to an old value
      loggedInUser.verificationToken = 'expiredVerificationToken';
      await loggedInUser.save();

      // Make the request with an expired verification token
      const res = await request(app).get('/users/verify-email/expiredVerificationToken');

      // Assert that the response indicates the token has expired
      expect(res.status).to.equal(500);
      expect(res.body.error).to.equal('Verification token expired. A new verification email has been sent.');
    });

    it('should handle user not found with verification token', async () => {
      // Make the request with a non-existent verification token
      const res = await request(app).get('/users/verify-email/nonExistentVerificationToken');

      // Assert that the response indicates the user was not found with the verification token
      expect(res.status).to.equal(404);
      expect(res.body.error).to.equal('User not found with this verification token.');
    });

    it('should handle internal server error', async () => {
      // Stub the UserModel.findOne method to throw an error, simulating an internal server error
      sinon.stub(UserModel, 'findOne').throws(new Error('Internal server error'));

      // Simulate a request to verify-email endpoint
      const res = await request(app).get(`/users/verify-email/${mockUser.verificationToken}`);

      // Assert that the response indicates an internal server error
      expect(res.status).to.equal(500);
      expect(res.body.error).to.equal('Internal server error');
    });
  });

  describe('POST /users/toggleBanStatus/:userId', () => {
    let accessToken, adminUser, normalUser;

    beforeEach(async () => {
      normalUser = await UserModel.create(mockUser1); // Assuming mockUser1 is a normal user

      // Create and log in as an admin user
      adminUser = await UserModel.create(mockUser2); // Assuming mockUser2 is an admin
      const loginResponse = await request(app).post('/users/loginUltimate').send({
        email: adminUser.email,
      });

      const cookies = loginResponse.headers['set-cookie'];
      accessToken = cookies.find((cookie) => cookie.startsWith('accessToken')).split(';')[0];
    });

    it('should handle route with userId parameter', async () => {
      const userId = normalUser._id; // Or any valid userId

      const response = await request(app)
        .post(`/users/toggleBanStatus/${userId}`)
        .set('Cookie', accessToken)
        .send({ banDuration: 30, banReason: 'Violation' });

      expect(response.status).to.equal(200); // Check for successful response status
      // Other assertions based on your controller's functionality
    });

    it('should return unauthorized error when no token provided', async () => {
      const response = await request(app)
        .post(`/users/toggleBanStatus/${normalUser._id}`)
        .send({ banDuration: 30, banReason: 'Violation' });

      expect(response.status).to.equal(401);
      expect(response.body.error).to.equal('Authorization token is required.'); // Expect specific error message
    });

    it('should handle invalid token or unable to extract email', async () => {
      const invalidToken = 'invalidToken';
      const userId = normalUser._id;

      const response = await request(app)
        .post(`/users/toggleBanStatus/${userId}`)
        .set('Cookie', `accessToken=${invalidToken}`)
        .send({ banDuration: 30, banReason: 'Violation' });

      expect(response.status).to.equal(401);
      expect(response.body.error).to.equal('Invalid token or unable to extract email.');
    });

    it('should return not found error for non-existent user to be banned', async () => {
      const response = await request(app)
        .post('/users/toggleBanStatus/60f6a1b5f3c1ac3dc0a0a800')
        .set('Cookie', accessToken)
        .send({ banDuration: 30, banReason: 'Violation' });

      expect(response.status).to.equal(404); // Not found response
      expect(response.body.error).to.equal('User not found.'); // Expect specific error message for not found user
    });

    it('should return not found error if user performing the ban is not found', async () => {
      // Mock the absence of the user performing the ban
      sinon.stub(UserModel, 'findOne').resolves(null);

      // Make a request to toggle ban status
      const response = await request(app)
        .post('/users/toggleBanStatus/nonExistentUserId') // Assuming nonExistentUserId doesn't exist
        .set('Cookie', accessToken)
        .send({ banDuration: 30, banReason: 'Violation' });

      // Assert response status code
      expect(response.status).to.equal(404);

      // Assert response body contains the expected error message
      expect(response.body.error).to.equal('User performing the ban not found.');
    });
    afterEach(() => {
      sinon.restore();
    });
  });

  describe('deleteUser', () => {
    afterEach(() => {
      sinon.restore();
    });

    it('should delete a user successfully', async () => {
      const userId = '123456789012345678901234';
      const findByIdAndDeleteStub = sinon.stub(UserModel, 'findByIdAndDelete').resolves();
      const userService = new UserService();

      await userService.deleteUser(userId);

      expect(findByIdAndDeleteStub.calledOnceWithExactly(userId)).to.be.true;
    });

    it('should throw an error when UserModel.findByIdAndDelete fails', async () => {
      const userId = '123456789012345678901234';
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
      const findByIdAndDeleteStub = sinon
        .stub(UserModel, 'findByIdAndDelete')
        .rejects(new Error('Some error occurred'));
      const userService = new UserService();

      try {
        await userService.deleteUser(userId);
        expect.fail('Expected an error to be thrown');
      } catch (error) {
        expect(error.message).to.equal('Some error occurred');
      }
    });
  });

  describe('updateUser', () => {
    it('should update a user successfully', async () => {
      // Mock updated user data
      const updatedUserData = { name: 'Updated Name' };
      // Stub the findByIdAndUpdate method of UserModel
      const findByIdAndUpdateStub = sinon.stub(UserModel, 'findByIdAndUpdate').resolves({
        _id: 'mockUserId',
        name: 'Updated Name',
      });
      // Create a new instance of UserService
      const userService = new UserService();
      // Call the updateUser method with a mock userId and updatedUserData
      const updatedUser = await userService.updateUser('mockUserId', updatedUserData);
      // Expect findByIdAndUpdate method to have been called with the correct userId and updatedUserData
      expect(findByIdAndUpdateStub.calledOnceWithExactly('mockUserId', updatedUserData, { new: true })).to.be.true;
      // Expect the updated user to have the correct data
      expect(updatedUser).to.deep.equal({
        _id: 'mockUserId',
        name: 'Updated Name',
      });
      // Restore the stub
      findByIdAndUpdateStub.restore();
    });
  });
});
Leave a Comment