ldc-boutique-cnc-sable/tests/Unit/Domain/Services/EmployeeImportServiceTest.php

227 lines
8.8 KiB
PHP
Raw Normal View History

2026-06-14 21:32:07 +02:00
<?php
declare(strict_types=1);
namespace Tests\Unit\Domain\Services;
use Domain\Events\Dto\SalariesHaveBeenImported;
use Domain\Models\ValueObjects\CsvImportFile;
use Domain\Models\ValueObjects\ImportRowResult;
use Domain\Models\ValueObjects\Matricule;
use Domain\Ports\Directories\CodeDirectory;
use Domain\Ports\Services\AdminAuthenticationService;
use Domain\Ports\Services\CsvReaderService;
use Domain\Ports\Services\EmployeeImportStorageService;
use Domain\Services\CodeGenerationService;
use Domain\Services\EmployeeImportService;
use Mockery;
use Ramsey\Uuid\Uuid;
use Shared\DDD\DomainEventDispatcher;
describe('EmployeeImportService', function (): void {
it('returns the import columns', function (): void {
// GIVEN
$csvReader = Mockery::mock(CsvReaderService::class);
$codeRepository = Mockery::mock(CodeDirectory::class);
$codeService = new CodeGenerationService($codeRepository);
$authService = Mockery::mock(AdminAuthenticationService::class);
$eventDispatcher = Mockery::mock(DomainEventDispatcher::class);
$repository = Mockery::mock(EmployeeImportStorageService::class);
$service = new EmployeeImportService(
$csvReader,
$codeService,
$authService,
$eventDispatcher,
$repository
);
// WHEN
$columns = $service->getImportColumns();
// THEN
expect($columns)->toBe([
'matricule',
'nom',
'prenom',
'etablissement',
'site',
'service',
'date_debut',
'date_fin',
'date_fin_prevue',
]);
});
it('successfully imports employees', function (): void {
// GIVEN
$file = new CsvImportFile(__FILE__, 'dummy content');
$csvReader = Mockery::mock(CsvReaderService::class);
$codeRepository = Mockery::mock(CodeDirectory::class);
$codeService = new CodeGenerationService($codeRepository);
$authService = Mockery::mock(AdminAuthenticationService::class);
$eventDispatcher = Mockery::mock(DomainEventDispatcher::class);
$repository = Mockery::mock(EmployeeImportStorageService::class);
$service = new EmployeeImportService(
$csvReader,
$codeService,
$authService,
$eventDispatcher,
$repository
);
$csvReader->shouldReceive('read')->once()->andReturnUsing(function ($f, $mapper) {
// Row 1: Valid
$mapped = $mapper(['matricule' => '1234567', 'nom' => 'Doe', 'prenom' => 'John', 'etablissement' => 'Etab A', 'service' => 'S1', 'site' => 'Site 1', 'date_debut' => '2020-01-01', 'date_fin' => 'null']);
yield new ImportRowResult(1, $mapped, null);
// Row 2: Valid with end date
$mapped2 = $mapper(['matricule' => '2345678', 'nom' => 'Smith', 'prenom' => 'Jane', 'etablissement' => 'Etab B', 'service' => 'S2', 'site' => 'Site 2', 'date_debut' => '2020-01-01', 'date_fin_prevue' => '2020-12-31']);
yield new ImportRowResult(2, $mapped2, null);
});
$codeRepository->shouldReceive('exists')->twice()->andReturn(false);
$repository->shouldReceive('upsertEmployees')->once()->with(Mockery::on(function ($upsertData) {
return count($upsertData) === 2
&& $upsertData[0]->matricule->value === '1234567'
&& $upsertData[1]->matricule->value === '2345678'
&& $upsertData[0]->code !== null
&& $upsertData[1]->code !== null;
}));
$repository->shouldReceive('getImportedAfter')->once()->andReturn((object) [
'matriculesInserted' => [new Matricule('1234567'), new Matricule('2345678')],
'insertedCount' => 2,
'updatedCount' => 0,
]);
$authService->shouldReceive('getMandatoryUserId')->once()->andReturn(Uuid::uuid7());
$eventDispatcher->shouldReceive('dispatch')->once()->with(Mockery::on(function ($event) {
return $event instanceof SalariesHaveBeenImported
&& count($event->matricules) === 2
&& $event->matricules[0]->value === '1234567'
&& $event->matricules[1]->value === '2345678';
}));
// WHEN
$result = $service->import($file);
// THEN
expect($result->insertedCount)->toBe(2)
->and($result->errors)->toBeEmpty();
});
it('handles duplicates, empty file and invalid date format exceptions from mapper', function (): void {
// GIVEN
$file = new CsvImportFile(__FILE__, 'dummy content');
$csvReader = Mockery::mock(CsvReaderService::class);
$codeRepository = Mockery::mock(CodeDirectory::class);
$codeService = new CodeGenerationService($codeRepository);
$authService = Mockery::mock(AdminAuthenticationService::class);
$eventDispatcher = Mockery::mock(DomainEventDispatcher::class);
$repository = Mockery::mock(EmployeeImportStorageService::class);
$service = new EmployeeImportService(
$csvReader,
$codeService,
$authService,
$eventDispatcher,
$repository
);
$csvReader->shouldReceive('read')->once()->andReturnUsing(function ($f, $mapper) {
// Catch domain exception for missing matricule
try {
$mapper(['nom' => 'Doe', 'date_debut' => '2020-01-01']);
} catch (\DomainException $e) {
yield new ImportRowResult(1, null, $e->getMessage());
}
// Catch domain exception for missing start date
try {
$mapper(['matricule' => '1234567']);
} catch (\DomainException $e) {
yield new ImportRowResult(2, null, $e->getMessage());
}
// Catch domain exception for invalid start date
try {
$mapper(['matricule' => '1234567', 'date_debut' => 'not-a-date']);
} catch (\DomainException $e) {
yield new ImportRowResult(3, null, $e->getMessage());
}
// Catch domain exception for invalid end date
try {
$mapper(['matricule' => '1234567', 'date_debut' => '2020-01-01', 'date_fin' => 'not-a-date']);
} catch (\DomainException $e) {
yield new ImportRowResult(4, null, $e->getMessage());
}
// Valid row 1
$mapped = $mapper(['matricule' => '2345678', 'nom' => 'Doe', 'prenom' => 'John', 'etablissement' => 'Etab C', 'service' => 'S3', 'site' => 'Site 3', 'date_debut' => '2020-01-01']);
yield new ImportRowResult(5, $mapped, null);
// Duplicate row
$mapped2 = $mapper(['matricule' => '2345678', 'nom' => 'Doe', 'prenom' => 'John', 'etablissement' => 'Etab C', 'service' => 'S3', 'site' => 'Site 3', 'date_debut' => '2020-01-01']);
yield new ImportRowResult(6, $mapped2, null);
});
$codeRepository->shouldReceive('exists')->once()->andReturn(false);
$repository->shouldReceive('upsertEmployees')->once();
$repository->shouldReceive('getImportedAfter')->once()->andReturn((object) [
'matriculesInserted' => [], // no inserts, only updates
'insertedCount' => 0,
'updatedCount' => 1,
]);
$eventDispatcher->shouldNotReceive('dispatch');
// WHEN
$result = $service->import($file);
// THEN
expect($result->updatedCount)->toBe(1)
->and($result->insertedCount)->toBe(0)
->and($result->errors)->toHaveCount(5); // 4 from exceptions, 1 duplicate
});
it('returns empty data error if file has no valid rows', function (): void {
// GIVEN
$file = new CsvImportFile(__FILE__, 'dummy content');
$csvReader = Mockery::mock(CsvReaderService::class);
$codeRepository = Mockery::mock(CodeDirectory::class);
$codeService = new CodeGenerationService($codeRepository);
$authService = Mockery::mock(AdminAuthenticationService::class);
$eventDispatcher = Mockery::mock(DomainEventDispatcher::class);
$repository = Mockery::mock(EmployeeImportStorageService::class);
$service = new EmployeeImportService(
$csvReader,
$codeService,
$authService,
$eventDispatcher,
$repository
);
$csvReader->shouldReceive('read')->once()->andReturnUsing(function () {
yield from [];
});
// WHEN
$result = $service->import($file);
// THEN
expect($result->errors)->toContain('The file contains no valid data.')
->and($result->insertedCount)->toBe(0);
});
});