Enhance employee and user management with linked user functionality
- Introduced `EmployeeUserWrite` type to manage user details associated with employees. - Updated `EmployeesService` and `EmployeesRepository` to support user assignment and retrieval by user ID. - Enhanced DTOs to include user information for employee creation and updates. - Implemented validation to ensure proper handling of user data during employee operations. - Added unit and e2e tests to validate the new functionality and ensure data integrity in user-employee relationships. - Modified existing controllers to accommodate the new user linkage features in employee management.
This commit is contained in:
@@ -1,12 +1,16 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
ConflictException,
|
||||
NotFoundException,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { DateTime } from '../../common/value-objects/date-time/date-time';
|
||||
import { PhoneNumber } from '../../common/value-objects/phone-number/phone-number';
|
||||
import { Status } from '../../common/value-objects/status/status';
|
||||
import type { Employee } from '../configuration/employees/employee';
|
||||
import { EmployeesRepository } from '../configuration/employees/employees.repository';
|
||||
import { PrivilegesService } from '../privileges/privileges.service';
|
||||
import type { User } from './user';
|
||||
import { UsersRepository } from './users.repository';
|
||||
@@ -32,6 +36,9 @@ describe('UsersService', () => {
|
||||
let privilegesService: jest.Mocked<
|
||||
Pick<PrivilegesService, 'findPrivilegeSummary'>
|
||||
>;
|
||||
let employeesRepository: jest.Mocked<
|
||||
Pick<EmployeesRepository, 'findById' | 'findByUserId' | 'update'>
|
||||
>;
|
||||
|
||||
const now = DateTime.fromUnixMs(1_700_000_000_000);
|
||||
const sampleUser: User = {
|
||||
@@ -51,6 +58,21 @@ describe('UsersService', () => {
|
||||
updatedByUser: { id: 'user-1', username: 'alice' },
|
||||
};
|
||||
|
||||
const sampleEmployee: Employee = {
|
||||
id: 'emp-1',
|
||||
code: 'EMP_01',
|
||||
name: 'Ada Lovelace',
|
||||
phone: PhoneNumber.create('+6281234567890'),
|
||||
position: 'sales',
|
||||
status: Status.create('draft'),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
createdBy: 'actor-1',
|
||||
updatedBy: 'actor-1',
|
||||
userId: null,
|
||||
user: null,
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
repository = {
|
||||
findById: jest.fn(),
|
||||
@@ -67,12 +89,18 @@ describe('UsersService', () => {
|
||||
privilegesService = {
|
||||
findPrivilegeSummary: jest.fn(),
|
||||
};
|
||||
employeesRepository = {
|
||||
findById: jest.fn(),
|
||||
findByUserId: jest.fn(),
|
||||
update: jest.fn(),
|
||||
};
|
||||
|
||||
const moduleRef: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
UsersService,
|
||||
{ provide: UsersRepository, useValue: repository },
|
||||
{ provide: PrivilegesService, useValue: privilegesService },
|
||||
{ provide: EmployeesRepository, useValue: employeesRepository },
|
||||
{
|
||||
provide: ConfigService,
|
||||
useValue: { getOrThrow: () => 4 },
|
||||
@@ -187,8 +215,149 @@ describe('UsersService', () => {
|
||||
});
|
||||
|
||||
it('importCsv requires username and password headers', async () => {
|
||||
await expect(service.importCsv('name\nalice', 'user-1')).rejects.toBeInstanceOf(
|
||||
BadRequestException,
|
||||
await expect(
|
||||
service.importCsv('name\nalice', 'user-1'),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
});
|
||||
|
||||
it('createManaged links an existing employee by id', async () => {
|
||||
repository.create.mockResolvedValue(sampleUser);
|
||||
employeesRepository.findById.mockResolvedValue(sampleEmployee);
|
||||
employeesRepository.update.mockResolvedValue({
|
||||
...sampleEmployee,
|
||||
userId: 'user-1',
|
||||
});
|
||||
repository.findById.mockResolvedValue({
|
||||
...sampleUser,
|
||||
employee: {
|
||||
id: 'emp-1',
|
||||
code: 'EMP_01',
|
||||
name: 'Ada Lovelace',
|
||||
status: Status.create('draft'),
|
||||
},
|
||||
});
|
||||
|
||||
const result = await service.createManaged({
|
||||
username: 'alice',
|
||||
password: 'password123',
|
||||
actorUserId: 'actor-1',
|
||||
employeeId: 'emp-1',
|
||||
});
|
||||
|
||||
expect(employeesRepository.update).toHaveBeenCalledWith(
|
||||
'emp-1',
|
||||
expect.objectContaining({
|
||||
userId: 'actor-1',
|
||||
assignedUserId: 'user-1',
|
||||
}),
|
||||
);
|
||||
expect(result.employee).toEqual({
|
||||
id: 'emp-1',
|
||||
code: 'EMP_01',
|
||||
name: 'Ada Lovelace',
|
||||
});
|
||||
});
|
||||
|
||||
it('createManaged rejects linking an employee assigned to another user', async () => {
|
||||
repository.create.mockResolvedValue(sampleUser);
|
||||
employeesRepository.findById.mockResolvedValue({
|
||||
...sampleEmployee,
|
||||
userId: 'other-user',
|
||||
});
|
||||
repository.delete.mockResolvedValue(undefined);
|
||||
|
||||
await expect(
|
||||
service.createManaged({
|
||||
username: 'alice',
|
||||
password: 'password123',
|
||||
actorUserId: 'actor-1',
|
||||
employeeId: 'emp-1',
|
||||
}),
|
||||
).rejects.toBeInstanceOf(ConflictException);
|
||||
expect(repository.delete).toHaveBeenCalledWith('user-1');
|
||||
});
|
||||
|
||||
it('update with employeeId null unlinks the current employee', async () => {
|
||||
repository.update.mockResolvedValue({
|
||||
...sampleUser,
|
||||
employee: {
|
||||
id: 'emp-1',
|
||||
code: 'EMP_01',
|
||||
name: 'Ada Lovelace',
|
||||
status: Status.create('draft'),
|
||||
},
|
||||
});
|
||||
employeesRepository.findByUserId.mockResolvedValue({
|
||||
...sampleEmployee,
|
||||
userId: 'user-1',
|
||||
});
|
||||
employeesRepository.update.mockResolvedValue({
|
||||
...sampleEmployee,
|
||||
userId: null,
|
||||
});
|
||||
repository.findById.mockResolvedValue({ ...sampleUser, employee: null });
|
||||
|
||||
const result = await service.update('user-1', {
|
||||
actorUserId: 'actor-1',
|
||||
employeeId: null,
|
||||
});
|
||||
|
||||
expect(employeesRepository.update).toHaveBeenCalledWith(
|
||||
'emp-1',
|
||||
expect.objectContaining({ assignedUserId: null, userId: 'actor-1' }),
|
||||
);
|
||||
expect(result.employee).toBeNull();
|
||||
});
|
||||
|
||||
it('update with employeeId reassigns after unlinking the previous employee', async () => {
|
||||
repository.update.mockResolvedValue(sampleUser);
|
||||
employeesRepository.findById.mockResolvedValue({
|
||||
...sampleEmployee,
|
||||
id: 'emp-2',
|
||||
code: 'EMP_02',
|
||||
});
|
||||
employeesRepository.findByUserId.mockResolvedValue({
|
||||
...sampleEmployee,
|
||||
userId: 'user-1',
|
||||
});
|
||||
employeesRepository.update.mockResolvedValue(sampleEmployee);
|
||||
repository.findById.mockResolvedValue({
|
||||
...sampleUser,
|
||||
employee: {
|
||||
id: 'emp-2',
|
||||
code: 'EMP_02',
|
||||
name: 'Ada Lovelace',
|
||||
status: Status.create('draft'),
|
||||
},
|
||||
});
|
||||
|
||||
await service.update('user-1', {
|
||||
actorUserId: 'actor-1',
|
||||
employeeId: 'emp-2',
|
||||
});
|
||||
|
||||
expect(employeesRepository.update).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'emp-1',
|
||||
expect.objectContaining({ assignedUserId: null }),
|
||||
);
|
||||
expect(employeesRepository.update).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'emp-2',
|
||||
expect.objectContaining({ assignedUserId: 'user-1' }),
|
||||
);
|
||||
});
|
||||
|
||||
it('update with unknown employeeId throws NotFoundException', async () => {
|
||||
repository.findById.mockResolvedValue(sampleUser);
|
||||
employeesRepository.findById.mockResolvedValue(null);
|
||||
|
||||
await expect(
|
||||
service.update('user-1', {
|
||||
actorUserId: 'actor-1',
|
||||
employeeId: 'missing',
|
||||
}),
|
||||
).rejects.toBeInstanceOf(NotFoundException);
|
||||
expect(repository.update).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user