251209:1453 Frontend: progress nest = UAT & Bug Fixing
Spec Validation / validate-markdown (push) Has been cancelled
Spec Validation / validate-diagrams (push) Has been cancelled
Spec Validation / check-todos (push) Has been cancelled

This commit is contained in:
admin
2025-12-09 14:53:42 +07:00
parent 8aceced902
commit aa96cd90e3
125 changed files with 11052 additions and 785 deletions
@@ -1,5 +1,6 @@
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Contract } from './contract.entity';
@Entity('projects')
export class Project extends BaseEntity {
@@ -14,4 +15,7 @@ export class Project extends BaseEntity {
@Column({ name: 'is_active', default: 1, type: 'tinyint' })
isActive!: boolean;
@OneToMany(() => Contract, (contract) => contract.project)
contracts!: Contract[];
}
@@ -1,19 +1,67 @@
import { Controller, Get, UseGuards } from '@nestjs/common';
import { ProjectService } from './project.service.js';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard.js';
import { Test, TestingModule } from '@nestjs/testing';
import { ProjectController } from './project.controller';
import { ProjectService } from './project.service';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { RbacGuard } from '../../common/guards/rbac.guard';
@Controller('projects')
@UseGuards(JwtAuthGuard)
export class ProjectController {
constructor(private readonly projectService: ProjectService) {}
describe('ProjectController', () => {
let controller: ProjectController;
let mockProjectService: Partial<ProjectService>;
@Get()
findAll() {
return this.projectService.findAllProjects();
}
beforeEach(async () => {
mockProjectService = {
create: jest.fn(),
findAll: jest.fn(),
findOne: jest.fn(),
update: jest.fn(),
remove: jest.fn(),
findAllOrganizations: jest.fn(),
};
@Get('organizations')
findAllOrgs() {
return this.projectService.findAllOrganizations();
}
}
const module: TestingModule = await Test.createTestingModule({
controllers: [ProjectController],
providers: [
{
provide: ProjectService,
useValue: mockProjectService,
},
],
})
// Override guards to avoid dependency issues
.overrideGuard(JwtAuthGuard)
.useValue({ canActivate: () => true })
.overrideGuard(RbacGuard)
.useValue({ canActivate: () => true })
.compile();
controller = module.get<ProjectController>(ProjectController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
describe('findAll', () => {
it('should call projectService.findAll', async () => {
const mockResult = { data: [], meta: {} };
(mockProjectService.findAll as jest.Mock).mockResolvedValue(mockResult);
const result = await controller.findAll({});
expect(mockProjectService.findAll).toHaveBeenCalled();
});
});
describe('findAllOrganizations', () => {
it('should call projectService.findAllOrganizations', async () => {
const mockOrgs = [{ organization_id: 1, name: 'Test Org' }];
(mockProjectService.findAllOrganizations as jest.Mock).mockResolvedValue(
mockOrgs
);
const result = await controller.findAllOrgs();
expect(mockProjectService.findAllOrganizations).toHaveBeenCalled();
});
});
});
@@ -12,14 +12,14 @@ import {
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { ProjectService } from './project.service.js';
import { CreateProjectDto } from './dto/create-project.dto.js';
import { UpdateProjectDto } from './dto/update-project.dto.js';
import { SearchProjectDto } from './dto/search-project.dto.js';
import { ProjectService } from './project.service';
import { CreateProjectDto } from './dto/create-project.dto';
import { UpdateProjectDto } from './dto/update-project.dto';
import { SearchProjectDto } from './dto/search-project.dto';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard.js';
import { RbacGuard } from '../../common/guards/rbac.guard.js';
import { RequirePermission } from '../../common/decorators/require-permission.decorator.js';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { RbacGuard } from '../../common/guards/rbac.guard';
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
@ApiTags('Projects')
@ApiBearerAuth()
@@ -49,6 +49,13 @@ export class ProjectController {
return this.projectService.findAllOrganizations();
}
@Get(':id/contracts')
@ApiOperation({ summary: 'List All Contracts in Project' })
@RequirePermission('project.view')
findContracts(@Param('id', ParseIntPipe) id: number) {
return this.projectService.findContracts(id);
}
@Get(':id')
@ApiOperation({ summary: 'Get Project Details' })
@RequirePermission('project.view')
@@ -61,7 +68,7 @@ export class ProjectController {
@RequirePermission('project.edit')
update(
@Param('id', ParseIntPipe) id: number,
@Body() updateDto: UpdateProjectDto,
@Body() updateDto: UpdateProjectDto
) {
return this.projectService.update(id, updateDto);
}
@@ -1,12 +1,49 @@
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { ProjectService } from './project.service';
import { Project } from './entities/project.entity';
import { Organization } from './entities/organization.entity';
describe('ProjectService', () => {
let service: ProjectService;
let mockProjectRepository: Record<string, jest.Mock>;
let mockOrganizationRepository: Record<string, jest.Mock>;
beforeEach(async () => {
mockProjectRepository = {
find: jest.fn(),
findOne: jest.fn(),
create: jest.fn(),
save: jest.fn(),
softDelete: jest.fn(),
createQueryBuilder: jest.fn(() => ({
leftJoinAndSelect: jest.fn().mockReturnThis(),
where: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
orderBy: jest.fn().mockReturnThis(),
skip: jest.fn().mockReturnThis(),
take: jest.fn().mockReturnThis(),
getManyAndCount: jest.fn().mockResolvedValue([[], 0]),
})),
};
mockOrganizationRepository = {
find: jest.fn(),
findOne: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
providers: [ProjectService],
providers: [
ProjectService,
{
provide: getRepositoryToken(Project),
useValue: mockProjectRepository,
},
{
provide: getRepositoryToken(Organization),
useValue: mockOrganizationRepository,
},
],
}).compile();
service = module.get<ProjectService>(ProjectService);
@@ -15,4 +52,36 @@ describe('ProjectService', () => {
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('findAll', () => {
it('should return paginated projects', async () => {
const mockProjects = [
{
project_id: 1,
project_code: 'PROJ-001',
project_name: 'Test Project',
},
];
mockProjectRepository
.createQueryBuilder()
.getManyAndCount.mockResolvedValue([mockProjects, 1]);
const result = await service.findAll({});
expect(result.data).toBeDefined();
expect(result.meta).toBeDefined();
});
});
describe('findAllOrganizations', () => {
it('should return all organizations', async () => {
const mockOrgs = [{ organization_id: 1, name: 'Test Org' }];
mockOrganizationRepository.find.mockResolvedValue(mockOrgs);
const result = await service.findAllOrganizations();
expect(mockOrganizationRepository.find).toHaveBeenCalled();
expect(result).toEqual(mockOrgs);
});
});
});
+16 -3
View File
@@ -24,7 +24,7 @@ export class ProjectService {
@InjectRepository(Project)
private projectRepository: Repository<Project>,
@InjectRepository(Organization)
private organizationRepository: Repository<Organization>,
private organizationRepository: Repository<Organization>
) {}
// --- CRUD Operations ---
@@ -36,7 +36,7 @@ export class ProjectService {
});
if (existing) {
throw new ConflictException(
`Project Code "${createDto.projectCode}" already exists`,
`Project Code "${createDto.projectCode}" already exists`
);
}
@@ -59,7 +59,7 @@ export class ProjectService {
if (search) {
query.andWhere(
'(project.projectCode LIKE :search OR project.projectName LIKE :search)',
{ search: `%${search}%` },
{ search: `%${search}%` }
);
}
@@ -107,6 +107,19 @@ export class ProjectService {
return this.projectRepository.softRemove(project);
}
async findContracts(projectId: number) {
const project = await this.projectRepository.findOne({
where: { id: projectId },
relations: ['contracts'],
});
if (!project) {
throw new NotFoundException(`Project ID ${projectId} not found`);
}
return project.contracts;
}
// --- Organization Helper ---
async findAllOrganizations() {