Lorsqu'un super-admin crée un établissement via l'interface, le système doit automatiquement créer la base tenant, exécuter les migrations, créer le premier utilisateur admin et envoyer l'invitation — le tout de manière asynchrone pour ne pas bloquer la réponse HTTP. Ce mécanisme rend chaque établissement opérationnel dès sa création sans intervention manuelle sur l'infrastructure.
282 lines
8.1 KiB
PHP
282 lines
8.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Unit\Scolarite\Domain\Service;
|
|
|
|
use App\Scolarite\Domain\Service\TeacherStatisticsCalculator;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class TeacherStatisticsCalculatorTest extends TestCase
|
|
{
|
|
private TeacherStatisticsCalculator $calculator;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->calculator = new TeacherStatisticsCalculator();
|
|
}
|
|
|
|
// --- Distribution ---
|
|
|
|
#[Test]
|
|
public function distributionReturnsEmptyBinsWhenNoValues(): void
|
|
{
|
|
$bins = $this->calculator->calculateDistribution([]);
|
|
|
|
self::assertSame([0, 0, 0, 0, 0, 0, 0, 0], $bins);
|
|
}
|
|
|
|
#[Test]
|
|
public function distributionPlacesValuesInCorrectBins(): void
|
|
{
|
|
// Bins: [0-2.5[, [2.5-5[, [5-7.5[, [7.5-10[, [10-12.5[, [12.5-15[, [15-17.5[, [17.5-20]
|
|
$values = [1.0, 3.0, 6.0, 9.0, 11.0, 14.0, 16.0, 19.0];
|
|
$bins = $this->calculator->calculateDistribution($values);
|
|
|
|
self::assertSame([1, 1, 1, 1, 1, 1, 1, 1], $bins);
|
|
}
|
|
|
|
#[Test]
|
|
public function distributionHandlesMaxValue20InLastBin(): void
|
|
{
|
|
$bins = $this->calculator->calculateDistribution([20.0]);
|
|
|
|
self::assertSame([0, 0, 0, 0, 0, 0, 0, 1], $bins);
|
|
}
|
|
|
|
#[Test]
|
|
public function distributionHandlesMultipleValuesInSameBin(): void
|
|
{
|
|
$values = [10.0, 10.5, 11.0, 12.0];
|
|
$bins = $this->calculator->calculateDistribution($values);
|
|
|
|
// All in bin [10-12.5[
|
|
self::assertSame([0, 0, 0, 0, 4, 0, 0, 0], $bins);
|
|
}
|
|
|
|
#[Test]
|
|
public function distributionHandlesBoundaryValues(): void
|
|
{
|
|
$values = [0.0, 2.5, 5.0, 7.5, 10.0, 12.5, 15.0, 17.5];
|
|
$bins = $this->calculator->calculateDistribution($values);
|
|
|
|
// 0.0 → bin 0, 2.5 → bin 1, 5.0 → bin 2, 7.5 → bin 3
|
|
// 10.0 → bin 4, 12.5 → bin 5, 15.0 → bin 6, 17.5 → bin 7
|
|
self::assertSame([1, 1, 1, 1, 1, 1, 1, 1], $bins);
|
|
}
|
|
|
|
// --- Success Rate ---
|
|
|
|
#[Test]
|
|
public function successRateReturnsZeroWhenNoValues(): void
|
|
{
|
|
self::assertSame(0.0, $this->calculator->calculateSuccessRate([]));
|
|
}
|
|
|
|
#[Test]
|
|
public function successRateCountsValuesAboveThreshold(): void
|
|
{
|
|
// Threshold defaults to 10.0
|
|
$values = [8.0, 10.0, 12.0, 14.0, 6.0];
|
|
|
|
// 10.0, 12.0, 14.0 are >= 10 → 3/5 = 60%
|
|
self::assertSame(60.0, $this->calculator->calculateSuccessRate($values));
|
|
}
|
|
|
|
#[Test]
|
|
public function successRateWithCustomThreshold(): void
|
|
{
|
|
$values = [8.0, 10.0, 12.0, 14.0, 6.0];
|
|
|
|
// >= 12: 12.0, 14.0 → 2/5 = 40%
|
|
self::assertSame(40.0, $this->calculator->calculateSuccessRate($values, 12.0));
|
|
}
|
|
|
|
#[Test]
|
|
public function successRateWithAllAbove(): void
|
|
{
|
|
$values = [15.0, 18.0, 12.0];
|
|
|
|
self::assertSame(100.0, $this->calculator->calculateSuccessRate($values));
|
|
}
|
|
|
|
#[Test]
|
|
public function successRateWithNoneAbove(): void
|
|
{
|
|
$values = [4.0, 6.0, 8.0];
|
|
|
|
self::assertSame(0.0, $this->calculator->calculateSuccessRate($values));
|
|
}
|
|
|
|
// --- Trend Line ---
|
|
|
|
#[Test]
|
|
public function trendLineReturnsNullWhenLessThanTwoPoints(): void
|
|
{
|
|
self::assertNull($this->calculator->calculateTrendLine([]));
|
|
self::assertNull($this->calculator->calculateTrendLine([[1, 10.0]]));
|
|
}
|
|
|
|
#[Test]
|
|
public function trendLineCalculatesLinearRegression(): void
|
|
{
|
|
// Perfectly increasing line: y = 2x + 8
|
|
$points = [[1, 10.0], [2, 12.0], [3, 14.0]];
|
|
|
|
$result = $this->calculator->calculateTrendLine($points);
|
|
|
|
self::assertNotNull($result);
|
|
self::assertEqualsWithDelta(2.0, $result->slope, 0.01);
|
|
self::assertEqualsWithDelta(8.0, $result->intercept, 0.01);
|
|
}
|
|
|
|
#[Test]
|
|
public function trendLineWithFlatData(): void
|
|
{
|
|
$points = [[1, 12.0], [2, 12.0], [3, 12.0]];
|
|
|
|
$result = $this->calculator->calculateTrendLine($points);
|
|
|
|
self::assertNotNull($result);
|
|
self::assertEqualsWithDelta(0.0, $result->slope, 0.01);
|
|
}
|
|
|
|
#[Test]
|
|
public function trendLineWithDecreasingData(): void
|
|
{
|
|
$points = [[1, 16.0], [2, 14.0], [3, 12.0]];
|
|
|
|
$result = $this->calculator->calculateTrendLine($points);
|
|
|
|
self::assertNotNull($result);
|
|
self::assertLessThan(0, $result->slope);
|
|
}
|
|
|
|
// --- Detect Trend ---
|
|
|
|
#[Test]
|
|
public function detectTrendReturnsStableWhenLessThanTwoAverages(): void
|
|
{
|
|
self::assertSame('stable', $this->calculator->detectTrend([]));
|
|
self::assertSame('stable', $this->calculator->detectTrend([12.0]));
|
|
}
|
|
|
|
#[Test]
|
|
public function detectTrendReturnsImprovingWhenLastIsHigherByThreshold(): void
|
|
{
|
|
// Last - first > 1.0 (default threshold)
|
|
self::assertSame('improving', $this->calculator->detectTrend([10.0, 11.5, 13.0]));
|
|
}
|
|
|
|
#[Test]
|
|
public function detectTrendReturnsDecliningWhenLastIsLowerByThreshold(): void
|
|
{
|
|
self::assertSame('declining', $this->calculator->detectTrend([14.0, 12.0, 10.0]));
|
|
}
|
|
|
|
#[Test]
|
|
public function detectTrendReturnsStableWhenDifferenceIsBelowThreshold(): void
|
|
{
|
|
self::assertSame('stable', $this->calculator->detectTrend([12.0, 12.5, 12.8]));
|
|
}
|
|
|
|
// --- Percentile ---
|
|
|
|
#[Test]
|
|
public function percentileReturnsHundredWhenNoOtherValues(): void
|
|
{
|
|
self::assertSame(100.0, $this->calculator->calculatePercentile(12.0, []));
|
|
}
|
|
|
|
#[Test]
|
|
public function percentileCalculatesCorrectly(): void
|
|
{
|
|
// My value: 14.0, others: [10, 12, 16, 18]
|
|
// 2 out of 4 are below → 50th percentile
|
|
self::assertSame(50.0, $this->calculator->calculatePercentile(14.0, [10.0, 12.0, 16.0, 18.0]));
|
|
}
|
|
|
|
#[Test]
|
|
public function percentileAtBottom(): void
|
|
{
|
|
self::assertSame(0.0, $this->calculator->calculatePercentile(5.0, [10.0, 12.0, 14.0]));
|
|
}
|
|
|
|
#[Test]
|
|
public function percentileAtTop(): void
|
|
{
|
|
self::assertSame(100.0, $this->calculator->calculatePercentile(20.0, [10.0, 12.0, 14.0]));
|
|
}
|
|
|
|
#[Test]
|
|
public function percentileWithDuplicateValues(): void
|
|
{
|
|
// My value: 12.0, others: [12.0, 12.0, 12.0]
|
|
// 0 strictly below → 0th percentile
|
|
self::assertSame(0.0, $this->calculator->calculatePercentile(12.0, [12.0, 12.0, 12.0]));
|
|
}
|
|
|
|
// --- Detect Trend edge cases ---
|
|
|
|
#[Test]
|
|
public function detectTrendAtExactThresholdReturnsStable(): void
|
|
{
|
|
// Difference is exactly 1.0 → should be stable (not strictly above threshold)
|
|
self::assertSame('stable', $this->calculator->detectTrend([10.0, 11.0]));
|
|
}
|
|
|
|
#[Test]
|
|
public function detectTrendJustAboveThresholdReturnsImproving(): void
|
|
{
|
|
// Difference is 1.01 → should be improving
|
|
self::assertSame('improving', $this->calculator->detectTrend([10.0, 11.01]));
|
|
}
|
|
|
|
// --- Distribution edge cases ---
|
|
|
|
#[Test]
|
|
public function distributionWithSingleValue(): void
|
|
{
|
|
$bins = $this->calculator->calculateDistribution([10.0]);
|
|
|
|
self::assertSame([0, 0, 0, 0, 1, 0, 0, 0], $bins);
|
|
}
|
|
|
|
#[Test]
|
|
public function distributionWithAllSameValues(): void
|
|
{
|
|
$values = [12.0, 12.0, 12.0, 12.0, 12.0];
|
|
$bins = $this->calculator->calculateDistribution($values);
|
|
|
|
// All in bin [10-12.5[
|
|
self::assertSame([0, 0, 0, 0, 5, 0, 0, 0], $bins);
|
|
}
|
|
|
|
// --- Trend Line edge cases ---
|
|
|
|
#[Test]
|
|
public function trendLineWithTwoPointsExact(): void
|
|
{
|
|
$points = [[1, 10.0], [2, 14.0]];
|
|
|
|
$result = $this->calculator->calculateTrendLine($points);
|
|
|
|
self::assertNotNull($result);
|
|
self::assertEqualsWithDelta(4.0, $result->slope, 0.01);
|
|
self::assertEqualsWithDelta(6.0, $result->intercept, 0.01);
|
|
}
|
|
|
|
#[Test]
|
|
public function trendLineWithNoisyData(): void
|
|
{
|
|
// Noisy but overall increasing
|
|
$points = [[1, 8.0], [2, 12.0], [3, 10.0], [4, 14.0], [5, 13.0]];
|
|
|
|
$result = $this->calculator->calculateTrendLine($points);
|
|
|
|
self::assertNotNull($result);
|
|
self::assertGreaterThan(0, $result->slope);
|
|
}
|
|
}
|