1
0

CapacityCalculatorTest.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Tests\Services;
  4. use App\Services\CapacityCalculator;
  5. use PHPUnit\Framework\Attributes\DataProvider;
  6. use PHPUnit\Framework\TestCase;
  7. final class CapacityCalculatorTest extends TestCase
  8. {
  9. // -------------------------------------------------------------------
  10. // roundHalf
  11. // -------------------------------------------------------------------
  12. /** @return list<array{float,float}> */
  13. public static function roundHalfCases(): array
  14. {
  15. return [
  16. [0.0, 0.0],
  17. [0.24, 0.0],
  18. [0.25, 0.5], // round(0.5) = 1 -> /2 = 0.5
  19. [0.26, 0.5],
  20. [0.75, 1.0],
  21. [1.24, 1.0],
  22. [1.25, 1.5],
  23. [1.74, 1.5],
  24. [1.75, 2.0],
  25. [3.10, 3.0],
  26. [3.30, 3.5],
  27. [5.00, 5.0],
  28. [16.8, 17.0], // 21 * 0.8
  29. [14.4, 14.5], // 18 * 0.8
  30. [13.6, 13.5], // 17 * 0.8
  31. ];
  32. }
  33. #[DataProvider('roundHalfCases')]
  34. public function testRoundHalf(float $input, float $expected): void
  35. {
  36. $this->assertSame($expected, CapacityCalculator::roundHalf($input));
  37. }
  38. // -------------------------------------------------------------------
  39. // forWorker — spec §6.5 examples
  40. // -------------------------------------------------------------------
  41. public function testForWorkerEmptySprint(): void
  42. {
  43. $r = CapacityCalculator::forWorker(0.0, 0.2, 0.0);
  44. $this->assertSame(0.0, $r['ressourcen']);
  45. $this->assertSame(0.0, $r['after_reserves']);
  46. $this->assertSame(0.0, $r['committed_prio1']);
  47. $this->assertSame(0.0, $r['available']);
  48. }
  49. public function testForWorkerSpecExample(): void
  50. {
  51. // 20 days raw, 20% reserve → 20*0.8 = 16 → available = 16 (no prio-1)
  52. $r = CapacityCalculator::forWorker(20.0, 0.2, 0.0);
  53. $this->assertSame(16.0, $r['after_reserves']);
  54. $this->assertSame(16.0, $r['available']);
  55. }
  56. public function testForWorkerFractionalReserveRoundsToHalfStep(): void
  57. {
  58. // 21 * 0.8 = 16.8 → round_half = 17.0
  59. $r = CapacityCalculator::forWorker(21.0, 0.2, 0.0);
  60. $this->assertSame(17.0, $r['after_reserves']);
  61. }
  62. public function testForWorkerPrio1CommitmentsReduceAvailable(): void
  63. {
  64. // 20 res, 20% reserve → after=16; 17 committed → available = -1
  65. $r = CapacityCalculator::forWorker(20.0, 0.2, 17.0);
  66. $this->assertSame(-1.0, $r['available']);
  67. }
  68. public function testForWorkerOverCommitmentStillCompletesCleanly(): void
  69. {
  70. $r = CapacityCalculator::forWorker(10.0, 0.2, 50.0);
  71. // Negative available is intentional (shown in red, not blocked).
  72. $this->assertLessThan(0.0, $r['available']);
  73. }
  74. public function testForWorkerPrio2DoesNotAppearInCommitted(): void
  75. {
  76. // The caller is responsible for only passing prio-1 committed days;
  77. // this method doesn't filter anything. What we verify here is that
  78. // a prio-1 value of 0 (because there were no prio-1 tasks) leaves
  79. // Available unaffected by whatever prio-2 allocations existed.
  80. $r = CapacityCalculator::forWorker(20.0, 0.2, 0.0);
  81. $this->assertSame(16.0, $r['available']);
  82. }
  83. public function testForWorkerZeroReserveFraction(): void
  84. {
  85. $r = CapacityCalculator::forWorker(18.0, 0.0, 3.0);
  86. $this->assertSame(18.0, $r['after_reserves']);
  87. $this->assertSame(15.0, $r['available']);
  88. }
  89. public function testForWorkerFullReserveFraction(): void
  90. {
  91. $r = CapacityCalculator::forWorker(18.0, 1.0, 0.0);
  92. $this->assertSame(0.0, $r['after_reserves']);
  93. $this->assertSame(0.0, $r['available']);
  94. }
  95. // -------------------------------------------------------------------
  96. // Day-value validation (spec §3 constraints enforced in PHP)
  97. // -------------------------------------------------------------------
  98. /** @return list<array{float,bool}> */
  99. public static function halfStepCases(): array
  100. {
  101. return [
  102. [0.0, true],
  103. [0.5, true],
  104. [1.0, true],
  105. [2.5, true],
  106. [5.0, true],
  107. // rejected
  108. [0.3, false], // §10 literal example
  109. [1.7, false], // §10 literal example
  110. [-0.5, false], // §10 literal example: negative
  111. [-1.0, false],
  112. [5.5, false],
  113. [6.0, false],
  114. [0.25, false],
  115. [0.75, false],
  116. ];
  117. }
  118. #[DataProvider('halfStepCases')]
  119. public function testIsHalfStep(float $v, bool $expected): void
  120. {
  121. $this->assertSame($expected, CapacityCalculator::isHalfStep($v, 0.0, 5.0));
  122. }
  123. /** @return list<array{float,bool}> */
  124. public static function rtbStepCases(): array
  125. {
  126. return [
  127. [0.0, true],
  128. [0.05, true],
  129. [0.10, true],
  130. [0.35, true],
  131. [1.0, true],
  132. // rejected
  133. [0.03, false],
  134. [1.1, false],
  135. [-0.05, false],
  136. [0.07, false],
  137. ];
  138. }
  139. #[DataProvider('rtbStepCases')]
  140. public function testIsRtbStep(float $v, bool $expected): void
  141. {
  142. $this->assertSame($expected, CapacityCalculator::isRtbStep($v));
  143. }
  144. }