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); }); });