| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100 |
- <?php
- declare(strict_types=1);
- namespace App\Tests\Unit\Reputation;
- use App\Domain\Reputation\Decay;
- use App\Domain\Reputation\DecayFunction;
- use PHPUnit\Framework\TestCase;
- /**
- * Hand-computed reference values for both decay shapes. The exponential
- * cases hit half-life multiples so the answers are clean fractions.
- */
- final class DecayTest extends TestCase
- {
- public function testLinearAtZeroReturnsFullWeight(): void
- {
- self::assertSame(1.0, Decay::value(DecayFunction::Linear, 0.0, 30.0));
- }
- public function testLinearMidwayReturnsHalf(): void
- {
- self::assertEqualsWithDelta(0.5, Decay::value(DecayFunction::Linear, 15.0, 30.0), 1e-9);
- }
- public function testLinearAtOrPastDecayParamClampsToZero(): void
- {
- self::assertSame(0.0, Decay::value(DecayFunction::Linear, 30.0, 30.0));
- self::assertSame(0.0, Decay::value(DecayFunction::Linear, 100.0, 30.0));
- }
- public function testExponentialAtZeroReturnsFullWeight(): void
- {
- self::assertSame(1.0, Decay::value(DecayFunction::Exponential, 0.0, 14.0));
- }
- public function testExponentialAtOneHalfLifeReturnsHalf(): void
- {
- self::assertEqualsWithDelta(0.5, Decay::value(DecayFunction::Exponential, 14.0, 14.0), 1e-9);
- }
- public function testExponentialAtTwoHalfLivesReturnsQuarter(): void
- {
- self::assertEqualsWithDelta(0.25, Decay::value(DecayFunction::Exponential, 28.0, 14.0), 1e-9);
- }
- public function testNegativeAgeClampsToFullWeight(): void
- {
- // Future-dated reports shouldn't happen, but if they do they count
- // at full weight rather than blowing up.
- self::assertSame(1.0, Decay::value(DecayFunction::Linear, -5.0, 30.0));
- self::assertSame(1.0, Decay::value(DecayFunction::Exponential, -5.0, 14.0));
- }
- public function testZeroDecayParamReturnsZero(): void
- {
- self::assertSame(0.0, Decay::value(DecayFunction::Linear, 5.0, 0.0));
- self::assertSame(0.0, Decay::value(DecayFunction::Exponential, 5.0, 0.0));
- }
- public function testDaysUntilThresholdLinearHalfwayCase(): void
- {
- // Linear decay, T=30 days. Score=2.0, threshold=1.0 → score reaches
- // threshold when 2.0 * (1 − Δ/30) = 1.0, i.e. Δ = 15.
- self::assertEqualsWithDelta(
- 15.0,
- Decay::daysUntilThreshold(DecayFunction::Linear, 30.0, 2.0, 1.0) ?? -1.0,
- 1e-9,
- );
- }
- public function testDaysUntilThresholdExponentialHalfLife(): void
- {
- // Exponential half-life=14 days. Score=2.0, threshold=1.0 → Δ = 14.
- self::assertEqualsWithDelta(
- 14.0,
- Decay::daysUntilThreshold(DecayFunction::Exponential, 14.0, 2.0, 1.0) ?? -1.0,
- 1e-9,
- );
- // Two half-lives: 4.0 → 1.0 takes 28 days.
- self::assertEqualsWithDelta(
- 28.0,
- Decay::daysUntilThreshold(DecayFunction::Exponential, 14.0, 4.0, 1.0) ?? -1.0,
- 1e-9,
- );
- }
- public function testDaysUntilThresholdReturnsZeroWhenAlreadyBelow(): void
- {
- self::assertSame(0.0, Decay::daysUntilThreshold(DecayFunction::Linear, 30.0, 0.5, 1.0));
- self::assertSame(0.0, Decay::daysUntilThreshold(DecayFunction::Exponential, 14.0, 1.0, 1.0));
- }
- public function testDaysUntilThresholdReturnsNullForNonPositiveThreshold(): void
- {
- self::assertNull(Decay::daysUntilThreshold(DecayFunction::Linear, 30.0, 5.0, 0.0));
- self::assertNull(Decay::daysUntilThreshold(DecayFunction::Exponential, 14.0, 5.0, -0.5));
- }
- }
|