1
0

CapacityCalculatorTest.php 5.6 KB

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