[#ED-1] Ajout des API de lecture bovin (#2)
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
Build Release Artefact / build (push) Successful in 39s

| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|                  |                 |

## Description de la PR

## Modification du .env

## Check list

- [ ] Pas de régression
- [x] TU/TI/TF rédigée
- [x] TU/TI/TF OK
- [ ] CHANGELOG modifié

Reviewed-on: #2
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #2.
This commit is contained in:
2026-04-21 08:14:37 +00:00
committed by Autin
parent 16798d9abb
commit f757822f36
29 changed files with 3416 additions and 208 deletions

0
tests/Unit/.gitkeep Normal file
View File

View File

@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace Malio\EdnotifBundle\Tests\Unit\Bovin\Mapper;
use Malio\EdnotifBundle\Bovin\Dto\AnimalSummaryDto;
use Malio\EdnotifBundle\Bovin\Mapper\AnimalSummaryMapper;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use stdClass;
/**
* @internal
*/
#[CoversClass(AnimalSummaryMapper::class)]
final class AnimalSummaryMapperTest extends TestCase
{
public function testMapReturnsIdentificationAndPresencePeriods(): void
{
$node = $this->makeBovinNode();
$summary = new AnimalSummaryMapper()->map($node);
self::assertInstanceOf(AnimalSummaryDto::class, $summary);
self::assertNotNull($summary->identification);
self::assertSame('FR1234567890', $summary->identification->bovin?->nationalNumber);
self::assertSame('F', $summary->identification->sex);
self::assertCount(2, $summary->presencePeriods);
}
public function testMapHandlesMissingOptionalNodes(): void
{
$summary = new AnimalSummaryMapper()->map(new stdClass());
self::assertNull($summary->identification);
self::assertSame([], $summary->presencePeriods);
}
private function makeBovinNode(): object
{
$node = new stdClass();
$node->IdentiteBovin = new stdClass();
$node->IdentiteBovin->Sexe = 'F';
$node->IdentiteBovin->Bovin = new stdClass();
$node->IdentiteBovin->Bovin->CodePays = 'FR';
$node->IdentiteBovin->Bovin->NumeroNational = 'FR1234567890';
$node->PeriodesPresences = new stdClass();
$node->PeriodesPresences->PeriodePresence = [
$this->makePresencePeriod('2024-01-10', '2024-06-01'),
$this->makePresencePeriod('2024-06-02', null),
];
return $node;
}
private function makePresencePeriod(string $entryDate, ?string $exitDate): object
{
$period = new stdClass();
$period->Entree = new stdClass();
$period->Entree->DateEntree = $entryDate;
if (null !== $exitDate) {
$period->Sortie = new stdClass();
$period->Sortie->DateSortie = $exitDate;
}
return $period;
}
}

View File

@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace Malio\EdnotifBundle\Tests\Unit\Bovin\Mapper;
use DateTimeImmutable;
use Malio\EdnotifBundle\Bovin\Dto\InventoryDto;
use Malio\EdnotifBundle\Bovin\Mapper\AnimalSummaryMapper;
use Malio\EdnotifBundle\Bovin\Mapper\InventoryMapper;
use Malio\EdnotifBundle\Shared\Mapper\StandardResponseMapper;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use stdClass;
/**
* @internal
*/
#[CoversClass(InventoryMapper::class)]
final class InventoryMapperTest extends TestCase
{
public function testMapFullInventory(): void
{
$mapper = new InventoryMapper(new AnimalSummaryMapper(), new StandardResponseMapper());
$inventory = $mapper->map($this->makeSoapResponse(), $this->makeUnzippedMessage());
self::assertInstanceOf(InventoryDto::class, $inventory);
self::assertTrue($inventory->standardResponse->result);
self::assertSame(2, $inventory->nbBovins);
self::assertEquals(new DateTimeImmutable('2026-01-01'), $inventory->startDate);
self::assertEquals(new DateTimeImmutable('2026-01-31'), $inventory->endDate);
self::assertTrue($inventory->includesEarTagStock);
self::assertCount(2, $inventory->animals);
self::assertCount(1, $inventory->earTagSeries);
self::assertSame('FR123', $inventory->animals[0]->identification?->bovin?->nationalNumber);
}
public function testMapInventoryWithoutMessageZipReturnsEmptyLists(): void
{
$mapper = new InventoryMapper(new AnimalSummaryMapper(), new StandardResponseMapper());
$soapResponse = new stdClass();
$soapResponse->ReponseStandard = new stdClass();
$soapResponse->ReponseStandard->Resultat = true;
$soapResponse->ReponseSpecifique = new stdClass();
$soapResponse->ReponseSpecifique->NbBovins = 0;
$inventory = $mapper->map($soapResponse, null);
self::assertSame(0, $inventory->nbBovins);
self::assertSame([], $inventory->animals);
self::assertSame([], $inventory->earTagSeries);
self::assertNull($inventory->startDate);
}
private function makeSoapResponse(): object
{
$response = new stdClass();
$response->ReponseStandard = new stdClass();
$response->ReponseStandard->Resultat = true;
$response->ReponseSpecifique = new stdClass();
$response->ReponseSpecifique->NbBovins = 2;
return $response;
}
private function makeUnzippedMessage(): object
{
$message = new stdClass();
$message->InformationsMessage = new stdClass();
$message->InformationsMessage->DateDebut = '2026-01-01';
$message->InformationsMessage->DateFin = '2026-01-31';
$message->InformationsMessage->StockBoucles = '1';
$message->Bovins = new stdClass();
$message->Bovins->Bovin = [
$this->makeAnimalNode('FR123'),
$this->makeAnimalNode('FR456'),
];
$message->Boucles = new stdClass();
$message->Boucles->SerieBoucles = new stdClass();
$message->Boucles->SerieBoucles->NumeroSerieDebut = 'A0001';
return $message;
}
private function makeAnimalNode(string $nationalNumber): object
{
$node = new stdClass();
$node->IdentiteBovin = new stdClass();
$node->IdentiteBovin->Bovin = new stdClass();
$node->IdentiteBovin->Bovin->NumeroNational = $nationalNumber;
$node->PeriodesPresences = new stdClass();
$node->PeriodesPresences->PeriodePresence = new stdClass();
$node->PeriodesPresences->PeriodePresence->Entree = new stdClass();
$node->PeriodesPresences->PeriodePresence->Entree->DateEntree = '2025-05-01';
return $node;
}
}

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace Malio\EdnotifBundle\Tests\Unit\Bovin\Mapper;
use DateTimeImmutable;
use Malio\EdnotifBundle\Bovin\Dto\PresumedExitsDto;
use Malio\EdnotifBundle\Bovin\Mapper\PresumedExitsMapper;
use Malio\EdnotifBundle\Shared\Mapper\StandardResponseMapper;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use stdClass;
/**
* @internal
*/
#[CoversClass(PresumedExitsMapper::class)]
final class PresumedExitsMapperTest extends TestCase
{
public function testMapWithExits(): void
{
$soapResponse = new stdClass();
$soapResponse->ReponseStandard = new stdClass();
$soapResponse->ReponseStandard->Resultat = true;
$soapResponse->ReponseSpecifique = new stdClass();
$soapResponse->ReponseSpecifique->NbBovins = 2;
$message = new stdClass();
$message->SortiesPresumees = new stdClass();
$message->SortiesPresumees->SortiePresumee = [
$this->makeExit('FR111', '2026-02-15'),
$this->makeExit('FR222', null),
];
$dto = new PresumedExitsMapper(new StandardResponseMapper())->map($soapResponse, $message);
self::assertInstanceOf(PresumedExitsDto::class, $dto);
self::assertSame(2, $dto->nbBovins);
self::assertCount(2, $dto->presumedExits);
self::assertSame('FR111', $dto->presumedExits[0]->bovin?->nationalNumber);
self::assertEquals(new DateTimeImmutable('2026-02-15'), $dto->presumedExits[0]->exitDate);
self::assertNull($dto->presumedExits[1]->exitDate);
}
public function testMapWithoutMessageReturnsEmpty(): void
{
$soapResponse = new stdClass();
$soapResponse->ReponseStandard = new stdClass();
$soapResponse->ReponseStandard->Resultat = true;
$soapResponse->ReponseSpecifique = new stdClass();
$soapResponse->ReponseSpecifique->NbBovins = 0;
$dto = new PresumedExitsMapper(new StandardResponseMapper())->map($soapResponse, null);
self::assertSame(0, $dto->nbBovins);
self::assertSame([], $dto->presumedExits);
}
private function makeExit(string $nationalNumber, ?string $exitDate): object
{
$node = new stdClass();
$node->Bovin = new stdClass();
$node->Bovin->CodePays = 'FR';
$node->Bovin->NumeroNational = $nationalNumber;
if (null !== $exitDate) {
$node->DateSortie = $exitDate;
}
return $node;
}
}

View File

@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace Malio\EdnotifBundle\Tests\Unit\Bovin\Mapper;
use DateTimeImmutable;
use Malio\EdnotifBundle\Bovin\Dto\ReturnedDossiersDto;
use Malio\EdnotifBundle\Bovin\Mapper\AnimalSummaryMapper;
use Malio\EdnotifBundle\Bovin\Mapper\ReturnedDossiersMapper;
use Malio\EdnotifBundle\Shared\Mapper\StandardResponseMapper;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use stdClass;
/**
* @internal
*/
#[CoversClass(ReturnedDossiersMapper::class)]
final class ReturnedDossiersMapperTest extends TestCase
{
public function testMapReturnsAnimalsAndStartDate(): void
{
$mapper = new ReturnedDossiersMapper(new AnimalSummaryMapper(), new StandardResponseMapper());
$soapResponse = new stdClass();
$soapResponse->ReponseStandard = new stdClass();
$soapResponse->ReponseStandard->Resultat = true;
$soapResponse->ReponseSpecifique = new stdClass();
$soapResponse->ReponseSpecifique->NbBovins = 1;
$message = new stdClass();
$message->InformationsMessage = new stdClass();
$message->InformationsMessage->DateDebut = '2026-03-01';
$message->Bovins = new stdClass();
$message->Bovins->Bovin = new stdClass();
$message->Bovins->Bovin->IdentiteBovin = new stdClass();
$message->Bovins->Bovin->IdentiteBovin->Bovin = new stdClass();
$message->Bovins->Bovin->IdentiteBovin->Bovin->NumeroNational = 'FR789';
$message->Bovins->Bovin->PeriodesPresences = new stdClass();
$message->Bovins->Bovin->PeriodesPresences->PeriodePresence = new stdClass();
$dto = $mapper->map($soapResponse, $message);
self::assertInstanceOf(ReturnedDossiersDto::class, $dto);
self::assertEquals(new DateTimeImmutable('2026-03-01'), $dto->startDate);
self::assertSame(1, $dto->nbBovins);
self::assertCount(1, $dto->animals);
self::assertSame('FR789', $dto->animals[0]->identification?->bovin?->nationalNumber);
}
public function testMapWithoutMessageReturnsEmptyAnimals(): void
{
$mapper = new ReturnedDossiersMapper(new AnimalSummaryMapper(), new StandardResponseMapper());
$soapResponse = new stdClass();
$soapResponse->ReponseStandard = new stdClass();
$soapResponse->ReponseStandard->Resultat = true;
$soapResponse->ReponseSpecifique = new stdClass();
$soapResponse->ReponseSpecifique->NbBovins = 0;
$dto = $mapper->map($soapResponse, null);
self::assertSame(0, $dto->nbBovins);
self::assertSame([], $dto->animals);
}
}

View File

@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace Malio\EdnotifBundle\Tests\Unit\Shared\Soap;
use Malio\EdnotifBundle\Shared\Soap\ZipMessageDecoder;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use ZipArchive;
/**
* @internal
*/
#[CoversClass(ZipMessageDecoder::class)]
final class ZipMessageDecoderTest extends TestCase
{
public function testDecodeReturnsObjectFromZippedXml(): void
{
$xml = '<?xml version="1.0" encoding="UTF-8"?>'
.'<MessageIpBNotifGetInventaire>'
.'<InformationsMessage><DateDebut>2026-01-01</DateDebut></InformationsMessage>'
.'<Bovins><Bovin><IdentiteBovin><Sexe>F</Sexe></IdentiteBovin></Bovin></Bovins>'
.'</MessageIpBNotifGetInventaire>';
$zipBinary = $this->makeZipBinary('message.xml', $xml);
$decoded = new ZipMessageDecoder()->decode($zipBinary);
self::assertIsObject($decoded);
self::assertSame('2026-01-01', (string) $decoded->InformationsMessage->DateDebut);
self::assertSame('F', (string) $decoded->Bovins->Bovin->IdentiteBovin->Sexe);
}
public function testDecodeProducesArrayForMultiChildNodes(): void
{
$xml = '<?xml version="1.0" encoding="UTF-8"?>'
.'<MessageIpBNotifGetInventaire>'
.'<Bovins>'
.'<Bovin><IdentiteBovin><Sexe>F</Sexe></IdentiteBovin></Bovin>'
.'<Bovin><IdentiteBovin><Sexe>M</Sexe></IdentiteBovin></Bovin>'
.'</Bovins>'
.'</MessageIpBNotifGetInventaire>';
$decoded = new ZipMessageDecoder()->decode($this->makeZipBinary('message.xml', $xml));
self::assertIsArray($decoded->Bovins->Bovin);
self::assertCount(2, $decoded->Bovins->Bovin);
self::assertSame('F', (string) $decoded->Bovins->Bovin[0]->IdentiteBovin->Sexe);
self::assertSame('M', (string) $decoded->Bovins->Bovin[1]->IdentiteBovin->Sexe);
}
public function testDecodeThrowsOnEmptyBinary(): void
{
$this->expectException(RuntimeException::class);
new ZipMessageDecoder()->decode('');
}
public function testDecodeThrowsOnInvalidZip(): void
{
$this->expectException(RuntimeException::class);
new ZipMessageDecoder()->decode('not a zip');
}
private function makeZipBinary(string $innerFile, string $content): string
{
$tempFile = tempnam(sys_get_temp_dir(), 'test_zip_');
self::assertIsString($tempFile);
$zip = new ZipArchive();
self::assertTrue(true === $zip->open($tempFile, ZipArchive::CREATE | ZipArchive::OVERWRITE));
self::assertTrue($zip->addFromString($innerFile, $content));
self::assertTrue($zip->close());
$binary = file_get_contents($tempFile);
@unlink($tempFile);
self::assertIsString($binary);
return $binary;
}
}

5
tests/bootstrap.php Normal file
View File

@@ -0,0 +1,5 @@
<?php
declare(strict_types=1);
require __DIR__.'/../vendor/autoload.php';