agmission/Development/server/tests/integration/user.integration.test.js
Devin Major 6ff07c9d74
All checks were successful
Server Tests / Mocha – Unit & Utility Tests (push) Successful in 1m13s
Server Tests / Jest – Integration Tests (push) Successful in 1m43s
additional integration tests
2026-05-08 14:51:04 -04:00

376 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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();
});
});
});