feat: Permettre l'import d'élèves via fichier CSV ou XLSX
L'import manuel élève par élève est fastidieux pour les établissements qui gèrent des centaines d'élèves. Un wizard d'import en 4 étapes (upload → mapping → preview → confirmation) permet de traiter un fichier complet en une seule opération, avec détection automatique du format (Pronote, École Directe) et validation avant import. L'import est traité de manière asynchrone via Messenger pour ne pas bloquer l'interface, avec suivi de progression en temps réel et réutilisation des mappings entre imports successifs.
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Unit\Administration\Application\Service\Import;
|
||||
|
||||
use App\Administration\Application\Service\Import\CsvParser;
|
||||
use App\Administration\Domain\Exception\FichierImportInvalideException;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class CsvParserTest extends TestCase
|
||||
{
|
||||
private CsvParser $parser;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->parser = new CsvParser();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function parseSemicolonSeparatedCsv(): void
|
||||
{
|
||||
$result = $this->parser->parse($this->fixture('eleves_simple.csv'));
|
||||
|
||||
self::assertSame(['Nom', 'Prénom', 'Classe', 'Email'], $result->columns);
|
||||
self::assertSame(3, $result->totalRows());
|
||||
self::assertSame('Dupont', $result->rows[0]['Nom']);
|
||||
self::assertSame('Jean', $result->rows[0]['Prénom']);
|
||||
self::assertSame('6ème A', $result->rows[0]['Classe']);
|
||||
self::assertSame('jean.dupont@email.com', $result->rows[0]['Email']);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function parseCommaSeparatedCsv(): void
|
||||
{
|
||||
$result = $this->parser->parse($this->fixture('eleves_comma.csv'));
|
||||
|
||||
self::assertSame(['Nom', 'Prénom', 'Classe'], $result->columns);
|
||||
self::assertSame(2, $result->totalRows());
|
||||
self::assertSame('Dupont', $result->rows[0]['Nom']);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function parsePronoteFormatCsv(): void
|
||||
{
|
||||
$result = $this->parser->parse($this->fixture('eleves_pronote.csv'));
|
||||
|
||||
self::assertContains('Élèves', $result->columns);
|
||||
self::assertContains('Né(e) le', $result->columns);
|
||||
self::assertContains('Sexe', $result->columns);
|
||||
self::assertSame(27, $result->totalRows());
|
||||
self::assertSame('BERTHE Alexandre', $result->rows[0]['Élèves']);
|
||||
self::assertSame('07/07/2011', $result->rows[0]['Né(e) le']);
|
||||
self::assertSame('Masculin', $result->rows[0]['Sexe']);
|
||||
self::assertSame('alexandre.berthe@fournisseur.fr', $result->rows[0]['Adresse E-mail']);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function previewReturnsLimitedRows(): void
|
||||
{
|
||||
$result = $this->parser->parse($this->fixture('eleves_simple.csv'));
|
||||
|
||||
$preview = $result->preview(2);
|
||||
|
||||
self::assertCount(2, $preview);
|
||||
self::assertSame('Dupont', $preview[0]['Nom']);
|
||||
self::assertSame('Martin', $preview[1]['Nom']);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function parseHandlesUtf8Bom(): void
|
||||
{
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'csv_');
|
||||
file_put_contents($tempFile, "\xEF\xBB\xBFNom;Prénom\nDupont;Jean\n");
|
||||
|
||||
try {
|
||||
$result = $this->parser->parse($tempFile);
|
||||
|
||||
self::assertSame(['Nom', 'Prénom'], $result->columns);
|
||||
self::assertSame(1, $result->totalRows());
|
||||
} finally {
|
||||
unlink($tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function parseEmptyFileThrowsException(): void
|
||||
{
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'csv_');
|
||||
file_put_contents($tempFile, '');
|
||||
|
||||
try {
|
||||
$this->expectException(FichierImportInvalideException::class);
|
||||
|
||||
$this->parser->parse($tempFile);
|
||||
} finally {
|
||||
unlink($tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function parseHandlesEmptyRowsGracefully(): void
|
||||
{
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'csv_');
|
||||
file_put_contents($tempFile, "Nom;Prénom\nDupont;Jean\n\n\nMartin;Marie\n");
|
||||
|
||||
try {
|
||||
$result = $this->parser->parse($tempFile);
|
||||
|
||||
self::assertSame(2, $result->totalRows());
|
||||
} finally {
|
||||
unlink($tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
private function fixture(string $filename): string
|
||||
{
|
||||
return __DIR__ . '/../../../../../fixtures/import/' . $filename;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user