227 lines
8.8 KiB
PHP
227 lines
8.8 KiB
PHP
|
|
<?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);
|
||
|
|
});
|
||
|
|
|
||
|
|
});
|