feat: due resolution and cascade math

Also: StoresUtc trait normalizes datetime casts to UTC (controller ruling).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BCrwHHnGCB5XH968Nxokqw
This commit is contained in:
nikita.hohlov
2026-09-04 06:19:00 -03:00
parent 774e0bce1a
commit e48e48aa4a
9 changed files with 297 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
<?php
namespace Tests\Unit\Scheduling;
use App\Scheduling\Cascade;
use Carbon\CarbonImmutable;
use Tests\TestCase; // Laravel TestCase: Eloquent date-casts need the connection grammar even in memory
class CascadeTest extends TestCase
{
private function daysBefore(array $points, CarbonImmutable $due): array
{
return array_map(fn (CarbonImmutable $p) => (int) round($p->diffInDays($due)), $points);
}
public function test_no_preparation_gives_no_points(): void
{
$due = CarbonImmutable::parse('2026-08-14', 'UTC');
$this->assertSame([], Cascade::points($due, $due));
}
public function test_halving_points(): void
{
$due = CarbonImmutable::parse('2026-09-30', 'UTC');
$this->assertSame([1], $this->daysBefore(Cascade::points($due->subDay(), $due), $due));
$this->assertSame([7, 3, 1], $this->daysBefore(Cascade::points($due->subDays(7), $due), $due));
$this->assertSame([30, 15, 7, 3, 1], $this->daysBefore(Cascade::points($due->subDays(30), $due), $due));
$this->assertSame([60, 30, 15, 7, 3, 1], $this->daysBefore(Cascade::points($due->subDays(60), $due), $due));
$this->assertSame([180, 90, 45, 22, 11, 5, 2, 1], $this->daysBefore(Cascade::points($due->subDays(180), $due), $due));
}
public function test_points_are_local_midnights_ascending(): void
{
$due = CarbonImmutable::parse('2026-09-30', 'Asia/Shanghai');
$points = Cascade::points($due->subDays(7), $due);
$this->assertSame('2026-09-23 00:00 Asia/Shanghai', $points[0]->format('Y-m-d H:i e'));
$this->assertSame('2026-09-29 00:00 Asia/Shanghai', $points[2]->format('Y-m-d H:i e'));
}
}