test: éliminer les PHPUnit Notices (createMock → createStub)

Remplace createMock() par createStub() pour tous les collaborateurs
sans expectations configurées. Les mocks avec expects() restent des
mocks locaux créés dans le test concerné. 0 notices sur 49 tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gwadaking
2026-04-10 01:05:48 -04:00
parent cda9a7a8ff
commit eff47f14bc
4 changed files with 602 additions and 0 deletions

View File

@@ -0,0 +1,121 @@
<?php
namespace App\Tests\Unit\Service;
use App\Service\Forecast\DriftSimulationService;
use App\Service\Ocean\OceanCurrentClient;
use App\Service\Weather\OpenMeteoClient;
use Doctrine\DBAL\Connection;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
class DriftSimulationServiceTest extends TestCase
{
private DriftSimulationService $service;
protected function setUp(): void
{
$this->service = new DriftSimulationService(
$this->createStub(OpenMeteoClient::class),
$this->createStub(OceanCurrentClient::class),
$this->createStub(Connection::class),
$this->createStub(EntityManagerInterface::class),
new NullLogger(),
);
}
// ── computeConfidence ─────────────────────────────────────────────────────
#[\PHPUnit\Framework\Attributes\DataProvider('confidenceProvider')]
public function testComputeConfidence(int $horizon, float $expected): void
{
$method = new \ReflectionMethod(DriftSimulationService::class, 'computeConfidence');
$result = $method->invoke($this->service, $horizon);
self::assertSame($expected, $result);
}
/** @return array<string, array{int, float}> */
public static function confidenceProvider(): array
{
return [
'H+6 → 0.85' => [6, 0.85],
'H+12 → 0.75' => [12, 0.75],
'H+24 → 0.60' => [24, 0.60],
'H+48 → 0.45' => [48, 0.45],
'inconnu → 0.50' => [72, 0.50],
];
}
// ── avgDriftVector ────────────────────────────────────────────────────────
public function testAvgDriftVectorWithNoWindNoOcean(): void
{
$method = new \ReflectionMethod(DriftSimulationService::class, 'avgDriftVector');
$result = $method->invoke($this->service, [], 0, 6, 0.0, 0.0);
self::assertSame(0.0, $result['lat']);
self::assertSame(0.0, $result['lng']);
}
public function testAvgDriftVectorWithPureEastwardOceanCurrent(): void
{
// Courant purement Est (u=1.0 m/s), vent nul, 6 heures
$method = new \ReflectionMethod(DriftSimulationService::class, 'avgDriftVector');
$result = $method->invoke($this->service, [], 0, 6, 1.0, 0.0);
self::assertSame(1.0, $result['lng']); // Est-Ouest = 1 m/s
self::assertSame(0.0, $result['lat']); // Nord-Sud = 0
}
public function testAvgDriftVectorWithNorthwardOceanCurrent(): void
{
$method = new \ReflectionMethod(DriftSimulationService::class, 'avgDriftVector');
$result = $method->invoke($this->service, [], 0, 6, 0.0, 1.0);
self::assertSame(0.0, $result['lng']);
self::assertSame(1.0, $result['lat']);
}
public function testAvgDriftVectorWithNorthWind(): void
{
// Vent venant du Nord (direction=0°), vitesse=10 m/s, courant nul
// sin(0°)=0 (pas de composante E-O), cos(0°)=1 (composante N-S)
// Stokes = 3% → 10 * 0.03 * cos(0) = 0.3 m/s vers Nord
$windData = array_fill(0, 6, ['speed' => 10.0, 'direction' => 0.0]);
$method = new \ReflectionMethod(DriftSimulationService::class, 'avgDriftVector');
$result = $method->invoke($this->service, $windData, 0, 6, 0.0, 0.0);
self::assertEqualsWithDelta(0.0, $result['lng'], 1e-10);
self::assertEqualsWithDelta(0.3, $result['lat'], 1e-10);
}
public function testAvgDriftVectorWithEastWind(): void
{
// Vent venant de l'Est (direction=90°), vitesse=10 m/s
// sin(90°)=1 → composante Est = 10*0.03 = 0.3 m/s
// cos(90°)=0 → composante Nord = 0
$windData = array_fill(0, 6, ['speed' => 10.0, 'direction' => 90.0]);
$method = new \ReflectionMethod(DriftSimulationService::class, 'avgDriftVector');
$result = $method->invoke($this->service, $windData, 0, 6, 0.0, 0.0);
self::assertEqualsWithDelta(0.3, $result['lng'], 1e-10);
self::assertEqualsWithDelta(0.0, $result['lat'], 1e-10);
}
public function testAvgDriftVectorUsesOnlyRequestedHourRange(): void
{
// windData[0..5] = vent Nord 10 m/s, windData[6..11] = vent Est 10 m/s
// Si on demande fromHour=6, toHour=12 → seules les heures 6..11 comptent
$windData = array_merge(
array_fill(0, 6, ['speed' => 10.0, 'direction' => 0.0]), // heures 0-5 : Nord
array_fill(6, 6, ['speed' => 10.0, 'direction' => 90.0]), // heures 6-11 : Est
);
$method = new \ReflectionMethod(DriftSimulationService::class, 'avgDriftVector');
$result = $method->invoke($this->service, $windData, 6, 12, 0.0, 0.0);
self::assertEqualsWithDelta(0.3, $result['lng'], 1e-10); // Est
self::assertEqualsWithDelta(0.0, $result['lat'], 1e-10);
}
}

View File

@@ -0,0 +1,164 @@
<?php
namespace App\Tests\Unit\Service;
use App\Service\Ingestion\IngestionService;
use App\Service\SentinelHub\SentinelHubClient;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
class IngestionServiceTest extends TestCase
{
private IngestionService $service;
protected function setUp(): void
{
$this->service = new IngestionService(
$this->createStub(SentinelHubClient::class),
$this->createStub(EntityManagerInterface::class),
new NullLogger(),
sys_get_temp_dir(),
);
}
// ── buildMultiPolygon ─────────────────────────────────────────────────────
private function callBuildMultiPolygon(string $geojsonContent): ?array
{
$file = tempnam(sys_get_temp_dir(), 'phpunit_ingestion_') . '.geojson';
file_put_contents($file, $geojsonContent);
try {
$method = new \ReflectionMethod(IngestionService::class, 'buildMultiPolygon');
return $method->invoke($this->service, $file);
} finally {
@unlink($file);
}
}
private static function makePolygon(int $points = 10, int $dn = 1): array
{
// Carré simple autour de 0,0 — répété $points fois pour avoir assez de coordonnées
$coords = [];
for ($i = 0; $i < $points; $i++) {
$coords[] = [$i * 0.01, $i * 0.01];
}
$coords[] = $coords[0]; // fermer l'anneau
return [
'type' => 'Feature',
'properties' => ['DN' => $dn],
'geometry' => [
'type' => 'Polygon',
'coordinates' => [$coords],
],
];
}
public function testReturnsNullWhenFileDoesNotExist(): void
{
$method = new \ReflectionMethod(IngestionService::class, 'buildMultiPolygon');
$result = $method->invoke($this->service, '/tmp/nonexistent_phpunit.geojson');
self::assertNull($result);
}
public function testReturnsNullForEmptyFeatureCollection(): void
{
$geojson = json_encode(['type' => 'FeatureCollection', 'features' => []]);
self::assertNull($this->callBuildMultiPolygon($geojson));
}
public function testReturnsNullForInvalidJson(): void
{
self::assertNull($this->callBuildMultiPolygon('not-json'));
}
public function testReturnsNullWhenAllFeaturesHaveWrongDN(): void
{
$geojson = json_encode([
'type' => 'FeatureCollection',
'features' => [self::makePolygon(10, 0), self::makePolygon(10, 2)],
]);
self::assertNull($this->callBuildMultiPolygon($geojson));
}
public function testReturnsNullWhenPolygonHasTooFewPoints(): void
{
// MIN_RING_POINTS = 8 → 5 points doit être ignoré
$geojson = json_encode([
'type' => 'FeatureCollection',
'features' => [self::makePolygon(5, 1)],
]);
self::assertNull($this->callBuildMultiPolygon($geojson));
}
public function testReturnMultiPolygonForValidFeature(): void
{
$geojson = json_encode([
'type' => 'FeatureCollection',
'features' => [self::makePolygon(10, 1)],
]);
$result = $this->callBuildMultiPolygon($geojson);
self::assertNotNull($result);
self::assertSame('MultiPolygon', $result['type']);
self::assertCount(1, $result['coordinates']);
}
public function testHandlesMultipleValidFeatures(): void
{
$geojson = json_encode([
'type' => 'FeatureCollection',
'features' => [
self::makePolygon(10, 1),
self::makePolygon(12, 1),
self::makePolygon(5, 1), // trop petit → ignoré
],
]);
$result = $this->callBuildMultiPolygon($geojson);
self::assertNotNull($result);
self::assertCount(2, $result['coordinates']); // 2 valides sur 3
}
public function testHandlesMultiPolygonGeometryType(): void
{
$coords1 = array_fill(0, 11, [0.0, 0.0]);
$coords1[] = $coords1[0];
$coords2 = array_fill(0, 11, [1.0, 1.0]);
$coords2[] = $coords2[0];
$feature = [
'type' => 'Feature',
'properties' => ['DN' => 1],
'geometry' => [
'type' => 'MultiPolygon',
'coordinates' => [[$coords1], [$coords2]],
],
];
$geojson = json_encode(['type' => 'FeatureCollection', 'features' => [$feature]]);
$result = $this->callBuildMultiPolygon($geojson);
self::assertNotNull($result);
self::assertSame('MultiPolygon', $result['type']);
self::assertCount(2, $result['coordinates']);
}
public function testIgnoresFeaturesWithDNZeroAndKeepsOthers(): void
{
$geojson = json_encode([
'type' => 'FeatureCollection',
'features' => [
self::makePolygon(10, 0), // DN=0 → ignoré
self::makePolygon(10, 1), // valide
],
]);
$result = $this->callBuildMultiPolygon($geojson);
self::assertNotNull($result);
self::assertCount(1, $result['coordinates']);
}
}