Add user and employee management enhancements with database schema updates
- Introduced new columns `status`, `created_by`, and `updated_by` in the `users` table to track user status and ownership. - Updated the `employees` table to include a foreign key reference to the `users` table via `user_id`. - Created migration script `0012_users_primary.sql` to apply these changes to the database schema. - Enhanced the `EmployeesService` and `EmployeesRepository` to support user assignments and related data retrieval. - Updated DTOs and service methods to reflect the new user and employee relationships. - Added unit tests to validate the new functionality and ensure data integrity. - Modified existing controllers to accommodate the new fields and relationships in user and employee management.
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
import { ConflictException } from '@nestjs/common';
|
||||
import {
|
||||
BadRequestException,
|
||||
ConflictException,
|
||||
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 { Status } from '../../common/value-objects/status/status';
|
||||
import { PrivilegesService } from '../privileges/privileges.service';
|
||||
import type { User } from './user';
|
||||
import { UsersRepository } from './users.repository';
|
||||
@@ -11,7 +17,16 @@ describe('UsersService', () => {
|
||||
let repository: jest.Mocked<
|
||||
Pick<
|
||||
UsersRepository,
|
||||
'findById' | 'findByUsername' | 'create' | 'updatePrivilegeId'
|
||||
| 'findById'
|
||||
| 'findByUsername'
|
||||
| 'create'
|
||||
| 'update'
|
||||
| 'updatePrivilegeId'
|
||||
| 'updateStatus'
|
||||
| 'bulkUpdateStatus'
|
||||
| 'delete'
|
||||
| 'bulkDelete'
|
||||
| 'list'
|
||||
>
|
||||
>;
|
||||
let privilegesService: jest.Mocked<
|
||||
@@ -25,8 +40,15 @@ describe('UsersService', () => {
|
||||
passwordHash: 'hashed',
|
||||
privilegeId: null,
|
||||
isSuperadmin: false,
|
||||
status: Status.create('draft'),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
createdBy: 'user-1',
|
||||
updatedBy: 'user-1',
|
||||
privilege: null,
|
||||
employee: null,
|
||||
createdByUser: { id: 'user-1', username: 'alice' },
|
||||
updatedByUser: { id: 'user-1', username: 'alice' },
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -34,7 +56,13 @@ describe('UsersService', () => {
|
||||
findById: jest.fn(),
|
||||
findByUsername: jest.fn(),
|
||||
create: jest.fn(),
|
||||
update: jest.fn(),
|
||||
updatePrivilegeId: jest.fn(),
|
||||
updateStatus: jest.fn(),
|
||||
bulkUpdateStatus: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
bulkDelete: jest.fn(),
|
||||
list: jest.fn(),
|
||||
};
|
||||
privilegesService = {
|
||||
findPrivilegeSummary: jest.fn(),
|
||||
@@ -45,6 +73,10 @@ describe('UsersService', () => {
|
||||
UsersService,
|
||||
{ provide: UsersRepository, useValue: repository },
|
||||
{ provide: PrivilegesService, useValue: privilegesService },
|
||||
{
|
||||
provide: ConfigService,
|
||||
useValue: { getOrThrow: () => 4 },
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
@@ -94,9 +126,69 @@ describe('UsersService', () => {
|
||||
repository.updatePrivilegeId.mockResolvedValue({
|
||||
...sampleUser,
|
||||
privilegeId: 'priv-1',
|
||||
privilege: { id: 'priv-1', code: 'ADMIN', name: 'Admin' },
|
||||
});
|
||||
|
||||
const result = await service.assignPrivilege('user-1', 'priv-1');
|
||||
expect(result.privilegeId).toBe('priv-1');
|
||||
expect(result.privilege).toEqual({
|
||||
id: 'priv-1',
|
||||
code: 'ADMIN',
|
||||
name: 'Admin',
|
||||
});
|
||||
expect(result).not.toHaveProperty('passwordHash');
|
||||
});
|
||||
|
||||
it('list maps nested relations and omits password', async () => {
|
||||
repository.list.mockResolvedValue({ data: [sampleUser], total: 1 });
|
||||
const result = await service.list({ page: 1, limit: 10 });
|
||||
expect(result.total).toBe(1);
|
||||
expect(result.data[0]).toMatchObject({
|
||||
id: 'user-1',
|
||||
username: 'alice',
|
||||
status: 'draft',
|
||||
privilege: null,
|
||||
employee: null,
|
||||
createdBy: { id: 'user-1', username: 'alice' },
|
||||
});
|
||||
expect(result.data[0]).not.toHaveProperty('passwordHash');
|
||||
});
|
||||
|
||||
it('update rejects status field', async () => {
|
||||
await expect(
|
||||
service.update('user-1', { status: 'active', actorUserId: 'user-1' }),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
});
|
||||
|
||||
it('assertCanAuthenticate allows active users without employee', () => {
|
||||
expect(() =>
|
||||
service.assertCanAuthenticate({
|
||||
...sampleUser,
|
||||
status: Status.create('active'),
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('assertCanAuthenticate rejects draft users and inactive employees', () => {
|
||||
expect(() => service.assertCanAuthenticate(sampleUser)).toThrow(
|
||||
UnauthorizedException,
|
||||
);
|
||||
expect(() =>
|
||||
service.assertCanAuthenticate({
|
||||
...sampleUser,
|
||||
status: Status.create('active'),
|
||||
employee: {
|
||||
id: 'emp-1',
|
||||
code: 'EMP_01',
|
||||
name: 'Ada',
|
||||
status: Status.create('draft'),
|
||||
},
|
||||
}),
|
||||
).toThrow(UnauthorizedException);
|
||||
});
|
||||
|
||||
it('importCsv requires username and password headers', async () => {
|
||||
await expect(service.importCsv('name\nalice', 'user-1')).rejects.toBeInstanceOf(
|
||||
BadRequestException,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user