| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- <?php
- declare(strict_types=1);
- namespace App\Tests\Services;
- use App\Services\CapacityCalculator;
- use PHPUnit\Framework\Attributes\DataProvider;
- use PHPUnit\Framework\TestCase;
- final class CapacityCalculatorTest extends TestCase
- {
- // -------------------------------------------------------------------
- // roundHalf
- // -------------------------------------------------------------------
- /** @return list<array{float,float}> */
- public static function roundHalfCases(): array
- {
- return [
- [0.0, 0.0],
- [0.24, 0.0],
- [0.25, 0.5], // round(0.5) = 1 -> /2 = 0.5
- [0.26, 0.5],
- [0.75, 1.0],
- [1.24, 1.0],
- [1.25, 1.5],
- [1.74, 1.5],
- [1.75, 2.0],
- [3.10, 3.0],
- [3.30, 3.5],
- [5.00, 5.0],
- [16.8, 17.0], // 21 * 0.8
- [14.4, 14.5], // 18 * 0.8
- [13.6, 13.5], // 17 * 0.8
- ];
- }
- #[DataProvider('roundHalfCases')]
- public function testRoundHalf(float $input, float $expected): void
- {
- $this->assertSame($expected, CapacityCalculator::roundHalf($input));
- }
- // -------------------------------------------------------------------
- // forWorker — spec §6.5 examples
- // -------------------------------------------------------------------
- public function testForWorkerEmptySprint(): void
- {
- $r = CapacityCalculator::forWorker(0.0, 0.2, 0.0);
- $this->assertSame(0.0, $r['ressourcen']);
- $this->assertSame(0.0, $r['after_reserves']);
- $this->assertSame(0.0, $r['committed_prio1']);
- $this->assertSame(0.0, $r['available']);
- }
- public function testForWorkerSpecExample(): void
- {
- // 20 days raw, 20% reserve → 20*0.8 = 16 → available = 16 (no prio-1)
- $r = CapacityCalculator::forWorker(20.0, 0.2, 0.0);
- $this->assertSame(16.0, $r['after_reserves']);
- $this->assertSame(16.0, $r['available']);
- }
- public function testForWorkerFractionalReserveRoundsToHalfStep(): void
- {
- // 21 * 0.8 = 16.8 → round_half = 17.0
- $r = CapacityCalculator::forWorker(21.0, 0.2, 0.0);
- $this->assertSame(17.0, $r['after_reserves']);
- }
- public function testForWorkerPrio1CommitmentsReduceAvailable(): void
- {
- // 20 res, 20% reserve → after=16; 17 committed → available = -1
- $r = CapacityCalculator::forWorker(20.0, 0.2, 17.0);
- $this->assertSame(-1.0, $r['available']);
- }
- public function testForWorkerOverCommitmentStillCompletesCleanly(): void
- {
- $r = CapacityCalculator::forWorker(10.0, 0.2, 50.0);
- // Negative available is intentional (shown in red, not blocked).
- $this->assertLessThan(0.0, $r['available']);
- }
- public function testForWorkerPrio2DoesNotAppearInCommitted(): void
- {
- // The caller is responsible for only passing prio-1 committed days;
- // this method doesn't filter anything. What we verify here is that
- // a prio-1 value of 0 (because there were no prio-1 tasks) leaves
- // Available unaffected by whatever prio-2 allocations existed.
- $r = CapacityCalculator::forWorker(20.0, 0.2, 0.0);
- $this->assertSame(16.0, $r['available']);
- }
- public function testForWorkerZeroReserveFraction(): void
- {
- $r = CapacityCalculator::forWorker(18.0, 0.0, 3.0);
- $this->assertSame(18.0, $r['after_reserves']);
- $this->assertSame(15.0, $r['available']);
- }
- public function testForWorkerFullReserveFraction(): void
- {
- $r = CapacityCalculator::forWorker(18.0, 1.0, 0.0);
- $this->assertSame(0.0, $r['after_reserves']);
- $this->assertSame(0.0, $r['available']);
- }
- // -------------------------------------------------------------------
- // Day-value validation (spec §3 constraints enforced in PHP)
- // -------------------------------------------------------------------
- /** @return list<array{float,bool}> */
- public static function halfStepCases(): array
- {
- return [
- [0.0, true],
- [0.5, true],
- [1.0, true],
- [2.5, true],
- [5.0, true],
- // rejected
- [0.3, false], // §10 literal example
- [1.7, false], // §10 literal example
- [-0.5, false], // §10 literal example: negative
- [-1.0, false],
- [5.5, false],
- [6.0, false],
- [0.25, false],
- [0.75, false],
- ];
- }
- #[DataProvider('halfStepCases')]
- public function testIsHalfStep(float $v, bool $expected): void
- {
- $this->assertSame($expected, CapacityCalculator::isHalfStep($v, 0.0, 5.0));
- }
- /** @return list<array{float,bool}> */
- public static function rtbStepCases(): array
- {
- return [
- [0.0, true],
- [0.05, true],
- [0.10, true],
- [0.35, true],
- [1.0, true],
- // rejected
- [0.03, false],
- [1.1, false],
- [-0.05, false],
- [0.07, false],
- ];
- }
- #[DataProvider('rtbStepCases')]
- public function testIsRtbStep(float $v, bool $expected): void
- {
- $this->assertSame($expected, CapacityCalculator::isRtbStep($v));
- }
- }
|