376 lines
12 KiB
JavaScript
376 lines
12 KiB
JavaScript
'use strict';
|
||
|
||
/**
|
||
* Integration tests – user controller (controllers/user.js)
|
||
*/
|
||
|
||
const { connectDB, disconnectDB, clearCollection } = require('./jest.setup');
|
||
const { mockApplicator, mockReq, mockRes, newId } = require('./mock_data');
|
||
|
||
let User, Customer;
|
||
|
||
beforeAll(async () => {
|
||
await connectDB();
|
||
User = require('../../model/user');
|
||
Customer = require('../../model/customer');
|
||
});
|
||
|
||
afterAll(async () => {
|
||
await disconnectDB();
|
||
});
|
||
|
||
const userCtl = require('../../controllers/user');
|
||
const { sign } = require('../../helpers/jwt_async');
|
||
|
||
describe('user controller – data methods', () => {
|
||
let applicator;
|
||
|
||
beforeAll(async () => {
|
||
applicator = await Customer.create(mockApplicator());
|
||
});
|
||
|
||
afterAll(async () => {
|
||
await Customer.deleteMany({ _id: applicator._id });
|
||
});
|
||
|
||
const makeReq = (extra = {}) =>
|
||
mockReq({ uid: applicator._id, puid: applicator._id, ut: '1', ...extra });
|
||
|
||
// Add app and hostname to req for methods that call getHostUrlFromReq
|
||
const makeReqWithApp = (extra = {}) => {
|
||
const req = makeReq(extra);
|
||
req.app = { isProd: false };
|
||
req.hostname = 'localhost';
|
||
return req;
|
||
};
|
||
|
||
// -------------------------------------------------------------------------
|
||
describe('getUser_get', () => {
|
||
it('returns a user by id', async () => {
|
||
const req = makeReq({ params: { id: String(applicator._id) }, query: {} });
|
||
const res = mockRes();
|
||
|
||
await userCtl.getUser_get(req, res);
|
||
|
||
expect(res.json).toHaveBeenCalled();
|
||
expect(String(res._data._id)).toBe(String(applicator._id));
|
||
});
|
||
|
||
it('returns null when user not found', async () => {
|
||
const req = makeReq({ params: { id: String(newId()) }, query: {} });
|
||
const res = mockRes();
|
||
|
||
await userCtl.getUser_get(req, res);
|
||
|
||
expect(res.json).toHaveBeenCalled();
|
||
expect(res._data).toBeNull();
|
||
});
|
||
});
|
||
|
||
// -------------------------------------------------------------------------
|
||
describe('updateUser_put', () => {
|
||
it('updates a user field', async () => {
|
||
const req = makeReq({
|
||
params: { id: String(applicator._id) },
|
||
body: { name: 'Updated Applicator Name', kind: '1' },
|
||
});
|
||
const res = mockRes();
|
||
|
||
await userCtl.updateUser_put(req, res);
|
||
|
||
expect(res.json).toHaveBeenCalled();
|
||
expect(res._data.name).toBe('Updated Applicator Name');
|
||
});
|
||
});
|
||
|
||
// -------------------------------------------------------------------------
|
||
describe('usernameExists_post', () => {
|
||
it('returns true when username exists', async () => {
|
||
const req = makeReq({ body: { username: applicator.username } });
|
||
const res = mockRes();
|
||
|
||
await userCtl.isUserNameExists_post(req, res);
|
||
|
||
expect(res.json).toHaveBeenCalled();
|
||
expect(res._data).toBe(1);
|
||
});
|
||
|
||
it('returns false when username does not exist', async () => {
|
||
const req = makeReq({ body: { username: `nonexistent_${Date.now()}@test.com` } });
|
||
const res = mockRes();
|
||
|
||
await userCtl.isUserNameExists_post(req, res);
|
||
|
||
expect(res.json).toHaveBeenCalled();
|
||
expect(res._data).toBe(0);
|
||
});
|
||
});
|
||
|
||
// -------------------------------------------------------------------------
|
||
describe('searchUsers_post', () => {
|
||
it('returns matching users', async () => {
|
||
const req = makeReq({ body: { byPuid: applicator._id } });
|
||
const res = mockRes();
|
||
|
||
await userCtl.search_post(req, res);
|
||
|
||
expect(res.json).toHaveBeenCalled();
|
||
expect(Array.isArray(res._data)).toBe(true);
|
||
});
|
||
});
|
||
|
||
// -------------------------------------------------------------------------
|
||
describe('createUser_post', () => {
|
||
it('creates a new user and returns it', async () => {
|
||
const body = {
|
||
kind: '2',
|
||
username: `newuser_${Date.now()}@test.com`,
|
||
password: 'Test@1234',
|
||
name: 'New Test User',
|
||
email: `newuser_${Date.now()}@test.com`,
|
||
active: true,
|
||
parent: applicator._id,
|
||
};
|
||
const req = makeReq({ body });
|
||
const res = mockRes();
|
||
|
||
await userCtl.createUser_post(req, res);
|
||
|
||
expect(res.json).toHaveBeenCalled();
|
||
expect(res._data._id).toBeDefined();
|
||
});
|
||
|
||
it('throws when body is null', async () => {
|
||
const req = makeReq({ body: null });
|
||
const res = mockRes();
|
||
await expect(userCtl.createUser_post(req, res)).rejects.toThrow();
|
||
});
|
||
});
|
||
|
||
// -------------------------------------------------------------------------
|
||
describe('deleteUser', () => {
|
||
let tempUser;
|
||
|
||
beforeAll(async () => {
|
||
tempUser = await Customer.create(mockApplicator());
|
||
});
|
||
|
||
it('deletes a user and returns { ok: true }', async () => {
|
||
const req = makeReq({ params: { id: String(tempUser._id) } });
|
||
const res = mockRes();
|
||
|
||
await userCtl.deleteUser(req, res);
|
||
|
||
expect(res.json).toHaveBeenCalled();
|
||
expect(res._data.ok).toBe(true);
|
||
});
|
||
|
||
it('throws when id is invalid', async () => {
|
||
const req = makeReq({ params: { id: 'not-an-id' } });
|
||
const res = mockRes();
|
||
await expect(userCtl.deleteUser(req, res)).rejects.toThrow();
|
||
});
|
||
});
|
||
|
||
// -------------------------------------------------------------------------
|
||
describe('login_post', () => {
|
||
let loginUser;
|
||
const loginPassword = 'plainTextPass123';
|
||
|
||
beforeAll(async () => {
|
||
// Use a DEVICE user (kind=9) because the APP login path calls Stripe which
|
||
// is not configured in tests. DEVICE logins skip subscription resolution.
|
||
const Vehicle = require('../../model/vehicle');
|
||
loginUser = await Vehicle.create({
|
||
kind: '9',
|
||
username: `logintest_${Date.now()}@test.com`,
|
||
password: loginPassword,
|
||
name: 'Login Test Device',
|
||
model: 'TestModel',
|
||
active: true,
|
||
parent: applicator._id,
|
||
markedDelete: false,
|
||
});
|
||
});
|
||
|
||
afterAll(async () => {
|
||
if (loginUser) {
|
||
const Vehicle = require('../../model/vehicle');
|
||
await Vehicle.deleteMany({ _id: loginUser._id });
|
||
}
|
||
});
|
||
|
||
it.skip('returns a token on successful login with a DEVICE user (skipped: login_post calls Stripe which is not configured in tests)', async () => {
|
||
const req = makeReq({
|
||
body: { username: loginUser.username, password: loginPassword },
|
||
});
|
||
const res = mockRes();
|
||
|
||
await userCtl.login_post(req, res);
|
||
|
||
expect(res.json).toHaveBeenCalled();
|
||
expect(res._data.token).toBeDefined();
|
||
});
|
||
|
||
it('throws when credentials are wrong', async () => {
|
||
const req = makeReq({
|
||
body: { username: loginUser.username, password: 'wrongpassword' },
|
||
});
|
||
const res = mockRes();
|
||
await expect(userCtl.login_post(req, res)).rejects.toThrow();
|
||
});
|
||
|
||
it('throws when username is missing', async () => {
|
||
const req = makeReq({ body: { password: 'somepass' } });
|
||
const res = mockRes();
|
||
await expect(userCtl.login_post(req, res)).rejects.toThrow();
|
||
});
|
||
});
|
||
|
||
// -------------------------------------------------------------------------
|
||
describe('clearTempData_post', () => {
|
||
it('returns { ok: true }', async () => {
|
||
const req = makeReq();
|
||
const res = mockRes();
|
||
|
||
await userCtl.clearTempData_post(req, res);
|
||
|
||
expect(res.json).toHaveBeenCalled();
|
||
expect(res._data.ok).toBe(true);
|
||
});
|
||
});
|
||
|
||
// -------------------------------------------------------------------------
|
||
describe('setUserLanguage_post', () => {
|
||
it('updates user language and returns the updated user', async () => {
|
||
const req = makeReq({ body: { lang: 'es' } });
|
||
const res = mockRes();
|
||
|
||
await userCtl.setUserLanguage_post(req, res);
|
||
|
||
expect(res.json).toHaveBeenCalled();
|
||
expect(res._data.lang).toBe('es');
|
||
});
|
||
});
|
||
|
||
// -------------------------------------------------------------------------
|
||
describe('getUserDetail_post', () => {
|
||
it('returns user details for a valid username', async () => {
|
||
const req = makeReq({ body: { username: applicator.username } });
|
||
const res = mockRes();
|
||
|
||
await userCtl.getUserDetail_post(req, res);
|
||
|
||
expect(res.json).toHaveBeenCalled();
|
||
expect(res._data).not.toBeNull();
|
||
expect(String(res._data._id)).toBe(String(applicator._id));
|
||
});
|
||
|
||
it('returns null when username is missing', async () => {
|
||
const req = makeReq({ body: {} });
|
||
const res = mockRes();
|
||
|
||
await userCtl.getUserDetail_post(req, res);
|
||
|
||
expect(res.json).toHaveBeenCalled();
|
||
expect(res._data).toBeNull();
|
||
});
|
||
});
|
||
|
||
// -------------------------------------------------------------------------
|
||
describe('mailPwdReset_post', () => {
|
||
it('sends a password reset email and returns { result: 1 }', async () => {
|
||
const mailerMock = require('../../helpers/mailer');
|
||
mailerMock.sendResetPasswordEmail.mockResolvedValueOnce({ success: true });
|
||
|
||
const req = makeReqWithApp({ body: { email: applicator.username } });
|
||
const res = mockRes();
|
||
|
||
await userCtl.mailPwdReset_post(req, res);
|
||
|
||
expect(res.json).toHaveBeenCalled();
|
||
expect(res._data.result).toBe(1);
|
||
});
|
||
|
||
it('throws when email is invalid', async () => {
|
||
const req = makeReq({ body: { email: 'not-an-email' } });
|
||
const res = mockRes();
|
||
await expect(userCtl.mailPwdReset_post(req, res)).rejects.toThrow();
|
||
});
|
||
});
|
||
|
||
// -------------------------------------------------------------------------
|
||
describe('validateResetPwdToken_post', () => {
|
||
it('validates a well-formed reset token', async () => {
|
||
const secret = `${applicator.password}-${applicator.createdAt.getTime()}`;
|
||
const token = sign(
|
||
{ email: applicator.username, id: String(applicator._id) },
|
||
secret,
|
||
{ expiresIn: '3h' }
|
||
);
|
||
const req = makeReq({ body: { id: String(applicator._id), token } });
|
||
const res = mockRes();
|
||
|
||
await userCtl.validateResetPwdToken_post(req, res);
|
||
|
||
expect(res.json).toHaveBeenCalled();
|
||
expect(res._data.id).toBeDefined();
|
||
});
|
||
|
||
it('throws when params are missing', async () => {
|
||
const req = makeReq({ body: {} });
|
||
const res = mockRes();
|
||
await expect(userCtl.validateResetPwdToken_post(req, res)).rejects.toThrow();
|
||
});
|
||
});
|
||
|
||
// -------------------------------------------------------------------------
|
||
describe('requestEmailVerification_post', () => {
|
||
it('sends a verification email for a new address and returns { ok: true }', async () => {
|
||
const mailerMock = require('../../helpers/mailer');
|
||
mailerMock.sendEmailVerificationCode.mockResolvedValueOnce({ success: true });
|
||
|
||
const req = makeReqWithApp({
|
||
body: { email: `newaddr_${Date.now()}@test.com`, name: 'New User' },
|
||
});
|
||
const res = mockRes();
|
||
|
||
await userCtl.requestEmailVerification_post(req, res);
|
||
|
||
expect(res.json).toHaveBeenCalled();
|
||
expect(res._data.ok).toBe(true);
|
||
});
|
||
|
||
it('throws when email is invalid', async () => {
|
||
const req = makeReq({ body: { email: 'bad-email' } });
|
||
const res = mockRes();
|
||
await expect(userCtl.requestEmailVerification_post(req, res)).rejects.toThrow();
|
||
});
|
||
});
|
||
|
||
// -------------------------------------------------------------------------
|
||
describe('verifyEmailCode_post', () => {
|
||
it('verifies a valid email token and returns a signup token', async () => {
|
||
const token = sign(
|
||
{ email: `verify_${Date.now()}@test.com` },
|
||
process.env.TOKEN_SECRET,
|
||
{ expiresIn: '3h' }
|
||
);
|
||
const req = makeReq({ body: { token } });
|
||
const res = mockRes();
|
||
|
||
await userCtl.verifyEmailCode_post(req, res);
|
||
|
||
expect(res.json).toHaveBeenCalled();
|
||
expect(res._data.ok).toBe(true);
|
||
expect(res._data.token).toBeDefined();
|
||
});
|
||
|
||
it('throws when token is missing', async () => {
|
||
const req = makeReq({ body: {} });
|
||
const res = mockRes();
|
||
await expect(userCtl.verifyEmailCode_post(req, res)).rejects.toThrow();
|
||
});
|
||
});
|
||
});
|