From 4ec1cc62af2bdd4c657794253b24a45dae8fa927 Mon Sep 17 00:00:00 2001 From: nafiuishaaq Date: Tue, 30 Sep 2025 09:37:12 +0100 Subject: [PATCH 1/9] module --- backend/src/companies/companies.module.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 backend/src/companies/companies.module.ts diff --git a/backend/src/companies/companies.module.ts b/backend/src/companies/companies.module.ts new file mode 100644 index 0000000..6bf45e2 --- /dev/null +++ b/backend/src/companies/companies.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { CompaniesService } from './companies.service'; +import { CompaniesController } from './companies.controller'; +import { Company } from './entities/company.entity'; + +@Module({ + imports: [TypeOrmModule.forFeature([Company])], + controllers: [CompaniesController], + providers: [CompaniesService], + exports: [CompaniesService], +}) +export class CompaniesModule {} + + From 44e00ea57fe231a358fd6d4b3ea3593ae3401cf1 Mon Sep 17 00:00:00 2001 From: nafiuishaaq Date: Tue, 30 Sep 2025 09:37:24 +0100 Subject: [PATCH 2/9] dtos --- .../src/companies/dto/create-company.dto.ts | 20 ++++++++++++++++ .../src/companies/dto/update-company.dto.ts | 6 +++++ .../src/companies/entities/company.entity.ts | 24 +++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 backend/src/companies/dto/create-company.dto.ts create mode 100644 backend/src/companies/dto/update-company.dto.ts create mode 100644 backend/src/companies/entities/company.entity.ts diff --git a/backend/src/companies/dto/create-company.dto.ts b/backend/src/companies/dto/create-company.dto.ts new file mode 100644 index 0000000..b0395e5 --- /dev/null +++ b/backend/src/companies/dto/create-company.dto.ts @@ -0,0 +1,20 @@ +import { IsNotEmpty, IsString, MaxLength } from 'class-validator'; + +export class CreateCompanyDto { + @IsString() + @IsNotEmpty() + @MaxLength(255) + name: string; + + @IsString() + @IsNotEmpty() + @MaxLength(100) + country: string; + + @IsString() + @IsNotEmpty() + @MaxLength(150) + registrationNumber: string; +} + + diff --git a/backend/src/companies/dto/update-company.dto.ts b/backend/src/companies/dto/update-company.dto.ts new file mode 100644 index 0000000..bed7898 --- /dev/null +++ b/backend/src/companies/dto/update-company.dto.ts @@ -0,0 +1,6 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateCompanyDto } from './create-company.dto'; + +export class UpdateCompanyDto extends PartialType(CreateCompanyDto) {} + + diff --git a/backend/src/companies/entities/company.entity.ts b/backend/src/companies/entities/company.entity.ts new file mode 100644 index 0000000..5ee6892 --- /dev/null +++ b/backend/src/companies/entities/company.entity.ts @@ -0,0 +1,24 @@ +import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm'; + +@Entity('companies') +export class Company { + @PrimaryGeneratedColumn() + id: number; + + @Column({ type: 'varchar', length: 255 }) + name: string; + + @Column({ type: 'varchar', length: 100 }) + country: string; + + @Column({ type: 'varchar', length: 150, unique: true }) + registrationNumber: string; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; +} + + From 347e352448f1d4a0d3f24f96c156c99028f9799b Mon Sep 17 00:00:00 2001 From: nafiuishaaq Date: Tue, 30 Sep 2025 09:37:38 +0100 Subject: [PATCH 3/9] service --- backend/src/companies/companies.service.ts | 44 +++++++++++++++++++ .../src/companies/dto/update-company.dto.ts | 20 +++++++-- 2 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 backend/src/companies/companies.service.ts diff --git a/backend/src/companies/companies.service.ts b/backend/src/companies/companies.service.ts new file mode 100644 index 0000000..5495311 --- /dev/null +++ b/backend/src/companies/companies.service.ts @@ -0,0 +1,44 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Company } from './entities/company.entity'; +import { CreateCompanyDto } from './dto/create-company.dto'; +import { UpdateCompanyDto } from './dto/update-company.dto'; + +@Injectable() +export class CompaniesService { + constructor( + @InjectRepository(Company) + private readonly companyRepository: Repository, + ) {} + + async create(createDto: CreateCompanyDto): Promise { + const company = this.companyRepository.create(createDto); + return await this.companyRepository.save(company); + } + + async findAll(): Promise { + return await this.companyRepository.find(); + } + + async findOne(id: number): Promise { + const company = await this.companyRepository.findOne({ where: { id } }); + if (!company) { + throw new NotFoundException(`Company ${id} not found`); + } + return company; + } + + async update(id: number, updateDto: UpdateCompanyDto): Promise { + const company = await this.findOne(id); + Object.assign(company, updateDto); + return await this.companyRepository.save(company); + } + + async remove(id: number): Promise { + const company = await this.findOne(id); + await this.companyRepository.remove(company); + } +} + + diff --git a/backend/src/companies/dto/update-company.dto.ts b/backend/src/companies/dto/update-company.dto.ts index bed7898..5d6173e 100644 --- a/backend/src/companies/dto/update-company.dto.ts +++ b/backend/src/companies/dto/update-company.dto.ts @@ -1,6 +1,20 @@ -import { PartialType } from '@nestjs/mapped-types'; -import { CreateCompanyDto } from './create-company.dto'; +import { IsOptional, IsString, MaxLength } from 'class-validator'; -export class UpdateCompanyDto extends PartialType(CreateCompanyDto) {} +export class UpdateCompanyDto { + @IsString() + @IsOptional() + @MaxLength(255) + name?: string; + + @IsString() + @IsOptional() + @MaxLength(100) + country?: string; + + @IsString() + @IsOptional() + @MaxLength(150) + registrationNumber?: string; +} From 42663becab42fb1ae102cd14c959a578befd3323 Mon Sep 17 00:00:00 2001 From: nafiuishaaq Date: Tue, 30 Sep 2025 09:38:06 +0100 Subject: [PATCH 4/9] entities --- backend/src/app.module.ts | 5 ++- backend/src/companies/companies.controller.ts | 39 +++++++++++++++++++ backend/src/departments/department.entity.ts | 7 ++-- 3 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 backend/src/companies/companies.controller.ts diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index 0c2a507..4f3e6aa 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -7,6 +7,8 @@ import { AssetCategoriesModule } from './asset-categories/asset-categories.modul import { AssetCategory } from './asset-categories/asset-category.entity'; import { DepartmentsModule } from './departments/departments.module'; import { Department } from './departments/department.entity'; +import { CompaniesModule } from './companies/companies.module'; +import { Company } from './companies/entities/company.entity'; @Module({ imports: [ @@ -38,13 +40,14 @@ import { Department } from './departments/department.entity'; username: configService.get('DB_USERNAME', 'postgres'), password: configService.get('DB_PASSWORD', 'password'), database: configService.get('DB_DATABASE', 'manage_assets'), - entities: [AssetCategory, Department], + entities: [AssetCategory, Department, Company], synchronize: configService.get('NODE_ENV') !== 'production', // Only for development }), inject: [ConfigService], }), AssetCategoriesModule, DepartmentsModule, + CompaniesModule, ], controllers: [AppController], providers: [AppService], diff --git a/backend/src/companies/companies.controller.ts b/backend/src/companies/companies.controller.ts new file mode 100644 index 0000000..e5805c2 --- /dev/null +++ b/backend/src/companies/companies.controller.ts @@ -0,0 +1,39 @@ +import { Controller, Get, Post, Body, Patch, Param, Delete, ParseIntPipe } from '@nestjs/common'; +import { CompaniesService } from './companies.service'; +import { CreateCompanyDto } from './dto/create-company.dto'; +import { UpdateCompanyDto } from './dto/update-company.dto'; + +@Controller('companies') +export class CompaniesController { + constructor(private readonly companiesService: CompaniesService) {} + + @Post() + create(@Body() createCompanyDto: CreateCompanyDto) { + return this.companiesService.create(createCompanyDto); + } + + @Get() + findAll() { + return this.companiesService.findAll(); + } + + @Get(':id') + findOne(@Param('id', ParseIntPipe) id: number) { + return this.companiesService.findOne(id); + } + + @Patch(':id') + update( + @Param('id', ParseIntPipe) id: number, + @Body() updateCompanyDto: UpdateCompanyDto, + ) { + return this.companiesService.update(id, updateCompanyDto); + } + + @Delete(':id') + remove(@Param('id', ParseIntPipe) id: number) { + return this.companiesService.remove(id); + } +} + + diff --git a/backend/src/departments/department.entity.ts b/backend/src/departments/department.entity.ts index 4018633..09fbcd2 100644 --- a/backend/src/departments/department.entity.ts +++ b/backend/src/departments/department.entity.ts @@ -1,4 +1,5 @@ import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, OneToMany } from 'typeorm'; +import { Company } from '../companies/entities/company.entity'; @Entity('departments') export class Department { @@ -20,9 +21,9 @@ export class Department { @UpdateDateColumn() updatedAt: Date; - // Relationships (commented out until related entities are created) - // @ManyToOne(() => Company, company => company.departments) - // company: Company; + // Relationships + @ManyToOne(() => Company) + company: Company; // @OneToMany(() => User, user => user.department) // users: User[]; From 6f4521cfdf3ec33150a350bab00a7da074818901 Mon Sep 17 00:00:00 2001 From: nafiuishaaq Date: Tue, 30 Sep 2025 09:40:55 +0100 Subject: [PATCH 5/9] entities --- backend/src/branches/branches.module.ts | 16 +++++++ backend/src/branches/branches.service.ts | 44 +++++++++++++++++++ backend/src/branches/dto/create-branch.dto.ts | 19 ++++++++ backend/src/branches/dto/update-branch.dto.ts | 19 ++++++++ .../src/branches/entities/branch.entity.ts | 28 ++++++++++++ 5 files changed, 126 insertions(+) create mode 100644 backend/src/branches/branches.module.ts create mode 100644 backend/src/branches/branches.service.ts create mode 100644 backend/src/branches/dto/create-branch.dto.ts create mode 100644 backend/src/branches/dto/update-branch.dto.ts create mode 100644 backend/src/branches/entities/branch.entity.ts diff --git a/backend/src/branches/branches.module.ts b/backend/src/branches/branches.module.ts new file mode 100644 index 0000000..0691ad6 --- /dev/null +++ b/backend/src/branches/branches.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { BranchesService } from './branches.service'; +import { BranchesController } from './branches.controller'; +import { Branch } from './entities/branch.entity'; +import { Company } from '../companies/entities/company.entity'; + +@Module({ + imports: [TypeOrmModule.forFeature([Branch, Company])], + controllers: [BranchesController], + providers: [BranchesService], + exports: [BranchesService], +}) +export class BranchesModule {} + + diff --git a/backend/src/branches/branches.service.ts b/backend/src/branches/branches.service.ts new file mode 100644 index 0000000..6544aa5 --- /dev/null +++ b/backend/src/branches/branches.service.ts @@ -0,0 +1,44 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Branch } from './entities/branch.entity'; +import { CreateBranchDto } from './dto/create-branch.dto'; +import { UpdateBranchDto } from './dto/update-branch.dto'; + +@Injectable() +export class BranchesService { + constructor( + @InjectRepository(Branch) + private readonly branchRepository: Repository, + ) {} + + async create(createDto: CreateBranchDto): Promise { + const branch = this.branchRepository.create(createDto); + return await this.branchRepository.save(branch); + } + + async findAll(): Promise { + return await this.branchRepository.find(); + } + + async findOne(id: number): Promise { + const branch = await this.branchRepository.findOne({ where: { id } }); + if (!branch) { + throw new NotFoundException(`Branch ${id} not found`); + } + return branch; + } + + async update(id: number, updateDto: UpdateBranchDto): Promise { + const branch = await this.findOne(id); + Object.assign(branch, updateDto); + return await this.branchRepository.save(branch); + } + + async remove(id: number): Promise { + const branch = await this.findOne(id); + await this.branchRepository.remove(branch); + } +} + + diff --git a/backend/src/branches/dto/create-branch.dto.ts b/backend/src/branches/dto/create-branch.dto.ts new file mode 100644 index 0000000..f348f0e --- /dev/null +++ b/backend/src/branches/dto/create-branch.dto.ts @@ -0,0 +1,19 @@ +import { IsInt, IsNotEmpty, IsOptional, IsString, MaxLength } from 'class-validator'; + +export class CreateBranchDto { + @IsString() + @IsNotEmpty() + @MaxLength(255) + name: string; + + @IsString() + @IsOptional() + @MaxLength(255) + address?: string; + + @IsInt() + @IsNotEmpty() + companyId: number; +} + + diff --git a/backend/src/branches/dto/update-branch.dto.ts b/backend/src/branches/dto/update-branch.dto.ts new file mode 100644 index 0000000..b22bdcb --- /dev/null +++ b/backend/src/branches/dto/update-branch.dto.ts @@ -0,0 +1,19 @@ +import { IsInt, IsOptional, IsString, MaxLength } from 'class-validator'; + +export class UpdateBranchDto { + @IsString() + @IsOptional() + @MaxLength(255) + name?: string; + + @IsString() + @IsOptional() + @MaxLength(255) + address?: string; + + @IsInt() + @IsOptional() + companyId?: number; +} + + diff --git a/backend/src/branches/entities/branch.entity.ts b/backend/src/branches/entities/branch.entity.ts new file mode 100644 index 0000000..90da617 --- /dev/null +++ b/backend/src/branches/entities/branch.entity.ts @@ -0,0 +1,28 @@ +import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne } from 'typeorm'; +import { Company } from '../../companies/entities/company.entity'; + +@Entity('branches') +export class Branch { + @PrimaryGeneratedColumn() + id: number; + + @Column({ type: 'varchar', length: 255 }) + name: string; + + @Column({ type: 'varchar', length: 255, nullable: true }) + address: string; + + @Column({ type: 'int' }) + companyId: number; + + @ManyToOne(() => Company) + company: Company; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; +} + + From 32e52f6741920008bdae9703cf299ecc413164b0 Mon Sep 17 00:00:00 2001 From: nafiuishaaq Date: Tue, 30 Sep 2025 09:41:12 +0100 Subject: [PATCH 6/9] CONTROLLERS --- backend/src/branches/branches.controller.ts | 39 +++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 backend/src/branches/branches.controller.ts diff --git a/backend/src/branches/branches.controller.ts b/backend/src/branches/branches.controller.ts new file mode 100644 index 0000000..1376f41 --- /dev/null +++ b/backend/src/branches/branches.controller.ts @@ -0,0 +1,39 @@ +import { Controller, Get, Post, Body, Patch, Param, Delete, ParseIntPipe } from '@nestjs/common'; +import { BranchesService } from './branches.service'; +import { CreateBranchDto } from './dto/create-branch.dto'; +import { UpdateBranchDto } from './dto/update-branch.dto'; + +@Controller('branches') +export class BranchesController { + constructor(private readonly branchesService: BranchesService) {} + + @Post() + create(@Body() createBranchDto: CreateBranchDto) { + return this.branchesService.create(createBranchDto); + } + + @Get() + findAll() { + return this.branchesService.findAll(); + } + + @Get(':id') + findOne(@Param('id', ParseIntPipe) id: number) { + return this.branchesService.findOne(id); + } + + @Patch(':id') + update( + @Param('id', ParseIntPipe) id: number, + @Body() updateBranchDto: UpdateBranchDto, + ) { + return this.branchesService.update(id, updateBranchDto); + } + + @Delete(':id') + remove(@Param('id', ParseIntPipe) id: number) { + return this.branchesService.remove(id); + } +} + + From 8f85a4f887adbb8a426d040f2b80792d24f6e666 Mon Sep 17 00:00:00 2001 From: nafiuishaaq Date: Tue, 30 Sep 2025 09:41:28 +0100 Subject: [PATCH 7/9] branch --- backend/src/app.module.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index 4f3e6aa..82740c8 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -9,6 +9,8 @@ import { DepartmentsModule } from './departments/departments.module'; import { Department } from './departments/department.entity'; import { CompaniesModule } from './companies/companies.module'; import { Company } from './companies/entities/company.entity'; +import { BranchesModule } from './branches/branches.module'; +import { Branch } from './branches/entities/branch.entity'; @Module({ imports: [ @@ -40,7 +42,7 @@ import { Company } from './companies/entities/company.entity'; username: configService.get('DB_USERNAME', 'postgres'), password: configService.get('DB_PASSWORD', 'password'), database: configService.get('DB_DATABASE', 'manage_assets'), - entities: [AssetCategory, Department, Company], + entities: [AssetCategory, Department, Company, Branch], synchronize: configService.get('NODE_ENV') !== 'production', // Only for development }), inject: [ConfigService], @@ -48,6 +50,7 @@ import { Company } from './companies/entities/company.entity'; AssetCategoriesModule, DepartmentsModule, CompaniesModule, + BranchesModule, ], controllers: [AppController], providers: [AppService], From 8665c535b74d46b502a023806c9dcf05c40b8237 Mon Sep 17 00:00:00 2001 From: nafiuishaaq Date: Tue, 30 Sep 2025 09:42:58 +0100 Subject: [PATCH 8/9] companies test --- .../src/companies/companies.service.spec.ts | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 backend/src/companies/companies.service.spec.ts diff --git a/backend/src/companies/companies.service.spec.ts b/backend/src/companies/companies.service.spec.ts new file mode 100644 index 0000000..e00ef6f --- /dev/null +++ b/backend/src/companies/companies.service.spec.ts @@ -0,0 +1,156 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { CompaniesService } from './companies.service'; +import { Company } from './entities/company.entity'; +import { CreateCompanyDto } from './dto/create-company.dto'; +import { UpdateCompanyDto } from './dto/update-company.dto'; + +describe('CompaniesService', () => { + let service: CompaniesService; + let repository: Repository; + + const mockRepository = { + create: jest.fn(), + save: jest.fn(), + find: jest.fn(), + findOne: jest.fn(), + remove: jest.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + CompaniesService, + { + provide: getRepositoryToken(Company), + useValue: mockRepository, + }, + ], + }).compile(); + + service = module.get(CompaniesService); + repository = module.get>(getRepositoryToken(Company)); + jest.clearAllMocks(); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + expect(repository).toBeDefined(); + }); + + describe('create', () => { + it('should create a new company', async () => { + const dto: CreateCompanyDto = { + name: 'Acme Inc', + country: 'US', + registrationNumber: 'REG-123', + }; + + const expected: Company = { + id: 1, + ...dto, + createdAt: new Date(), + updatedAt: new Date(), + } as Company; + + mockRepository.create.mockReturnValue(expected); + mockRepository.save.mockResolvedValue(expected); + + const result = await service.create(dto); + + expect(mockRepository.create).toHaveBeenCalledWith(dto); + expect(mockRepository.save).toHaveBeenCalledWith(expected); + expect(result).toEqual(expected); + }); + }); + + describe('findAll', () => { + it('should return an array of companies', async () => { + const expected: Company[] = [ + { + id: 1, + name: 'Acme Inc', + country: 'US', + registrationNumber: 'REG-123', + createdAt: new Date(), + updatedAt: new Date(), + } as Company, + ]; + + mockRepository.find.mockResolvedValue(expected); + + const result = await service.findAll(); + expect(mockRepository.find).toHaveBeenCalled(); + expect(result).toEqual(expected); + }); + }); + + describe('findOne', () => { + it('should return a company when found', async () => { + const expected: Company = { + id: 1, + name: 'Acme Inc', + country: 'US', + registrationNumber: 'REG-123', + createdAt: new Date(), + updatedAt: new Date(), + } as Company; + + mockRepository.findOne.mockResolvedValue(expected); + + const result = await service.findOne(1); + expect(mockRepository.findOne).toHaveBeenCalledWith({ where: { id: 1 } }); + expect(result).toEqual(expected); + }); + + it('should throw when company not found', async () => { + mockRepository.findOne.mockResolvedValue(undefined); + await expect(service.findOne(999)).rejects.toThrow('Company 999 not found'); + }); + }); + + describe('update', () => { + it('should merge and save updates', async () => { + const existing: Company = { + id: 1, + name: 'Acme Inc', + country: 'US', + registrationNumber: 'REG-123', + createdAt: new Date(), + updatedAt: new Date(), + } as Company; + + const updates: UpdateCompanyDto = { country: 'CA' }; + const saved = { ...existing, ...updates } as Company; + + mockRepository.findOne.mockResolvedValue(existing); + mockRepository.save.mockResolvedValue(saved); + + const result = await service.update(1, updates); + expect(mockRepository.save).toHaveBeenCalledWith(saved); + expect(result).toEqual(saved); + }); + }); + + describe('remove', () => { + it('should remove the company', async () => { + const existing: Company = { + id: 1, + name: 'Acme Inc', + country: 'US', + registrationNumber: 'REG-123', + createdAt: new Date(), + updatedAt: new Date(), + } as Company; + + mockRepository.findOne.mockResolvedValue(existing); + mockRepository.remove.mockResolvedValue(existing); + + await service.remove(1); + expect(mockRepository.remove).toHaveBeenCalledWith(existing); + }); + }); +}); + + From 3974f43b995d8ec3333b6a7dfa7742b5ad3673dd Mon Sep 17 00:00:00 2001 From: nafiuishaaq Date: Tue, 30 Sep 2025 09:43:15 +0100 Subject: [PATCH 9/9] services test --- backend/src/branches/branches.service.spec.ts | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 backend/src/branches/branches.service.spec.ts diff --git a/backend/src/branches/branches.service.spec.ts b/backend/src/branches/branches.service.spec.ts new file mode 100644 index 0000000..692858f --- /dev/null +++ b/backend/src/branches/branches.service.spec.ts @@ -0,0 +1,146 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { BranchesService } from './branches.service'; +import { Branch } from './entities/branch.entity'; +import { CreateBranchDto } from './dto/create-branch.dto'; +import { UpdateBranchDto } from './dto/update-branch.dto'; + +describe('BranchesService', () => { + let service: BranchesService; + let repository: Repository; + + const mockRepository = { + create: jest.fn(), + save: jest.fn(), + find: jest.fn(), + findOne: jest.fn(), + remove: jest.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + BranchesService, + { + provide: getRepositoryToken(Branch), + useValue: mockRepository, + }, + ], + }).compile(); + + service = module.get(BranchesService); + repository = module.get>(getRepositoryToken(Branch)); + jest.clearAllMocks(); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + expect(repository).toBeDefined(); + }); + + describe('create', () => { + it('should create a new branch', async () => { + const dto: CreateBranchDto = { + name: 'Main Branch', + address: '123 Street', + companyId: 1, + }; + + const expected: Branch = { + id: 1, + ...dto, + createdAt: new Date(), + updatedAt: new Date(), + } as Branch; + + mockRepository.create.mockReturnValue(expected); + mockRepository.save.mockResolvedValue(expected); + + const result = await service.create(dto); + expect(mockRepository.create).toHaveBeenCalledWith(dto); + expect(mockRepository.save).toHaveBeenCalledWith(expected); + expect(result).toEqual(expected); + }); + }); + + describe('findAll', () => { + it('should return an array of branches', async () => { + const expected: Branch[] = [ + { + id: 1, + name: 'Main Branch', + address: '123 Street', + companyId: 1, + createdAt: new Date(), + updatedAt: new Date(), + } as Branch, + ]; + mockRepository.find.mockResolvedValue(expected); + const result = await service.findAll(); + expect(mockRepository.find).toHaveBeenCalled(); + expect(result).toEqual(expected); + }); + }); + + describe('findOne', () => { + it('should return a branch when found', async () => { + const expected: Branch = { + id: 1, + name: 'Main Branch', + address: '123 Street', + companyId: 1, + createdAt: new Date(), + updatedAt: new Date(), + } as Branch; + mockRepository.findOne.mockResolvedValue(expected); + const result = await service.findOne(1); + expect(mockRepository.findOne).toHaveBeenCalledWith({ where: { id: 1 } }); + expect(result).toEqual(expected); + }); + + it('should throw when branch not found', async () => { + mockRepository.findOne.mockResolvedValue(undefined); + await expect(service.findOne(999)).rejects.toThrow('Branch 999 not found'); + }); + }); + + describe('update', () => { + it('should merge and save updates', async () => { + const existing: Branch = { + id: 1, + name: 'Main Branch', + address: '123 Street', + companyId: 1, + createdAt: new Date(), + updatedAt: new Date(), + } as Branch; + const updates: UpdateBranchDto = { address: '456 Ave' }; + const saved = { ...existing, ...updates } as Branch; + mockRepository.findOne.mockResolvedValue(existing); + mockRepository.save.mockResolvedValue(saved); + const result = await service.update(1, updates); + expect(mockRepository.save).toHaveBeenCalledWith(saved); + expect(result).toEqual(saved); + }); + }); + + describe('remove', () => { + it('should remove the branch', async () => { + const existing: Branch = { + id: 1, + name: 'Main Branch', + address: '123 Street', + companyId: 1, + createdAt: new Date(), + updatedAt: new Date(), + } as Branch; + mockRepository.findOne.mockResolvedValue(existing); + mockRepository.remove.mockResolvedValue(existing); + await service.remove(1); + expect(mockRepository.remove).toHaveBeenCalledWith(existing); + }); + }); +}); + +