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
100 lines
4.1 KiB
PHP
100 lines
4.1 KiB
PHP
<?php
|
||
|
||
namespace Tests\Unit\Scheduling;
|
||
|
||
use App\Enums\AfterDue;
|
||
use App\Enums\DueMode;
|
||
use App\Enums\EventState;
|
||
use App\Models\Event;
|
||
use App\Models\User;
|
||
use App\Scheduling\DueResolver;
|
||
use Carbon\CarbonImmutable;
|
||
use Tests\TestCase; // Laravel TestCase: Eloquent date-casts need the connection grammar even in memory
|
||
|
||
class DueResolverTest extends TestCase
|
||
{
|
||
private function user(string $tz = 'America/Montevideo'): User
|
||
{
|
||
return new User(['login' => 'n', 'tz' => $tz, 'quiet_start' => '22:00:00', 'quiet_end' => '09:00:00']);
|
||
}
|
||
|
||
private function local(string $date, string $time = '24:00:00', ?string $fireOn = null): Event
|
||
{
|
||
return new Event([
|
||
'due_mode' => DueMode::Local, 'due_date' => $date, 'due_time' => $time,
|
||
'fire_on' => $fireOn ?? $date, 'after_due' => AfterDue::Expire,
|
||
'state' => EventState::Scheduled, 'payload' => ['title' => 't'],
|
||
]);
|
||
}
|
||
|
||
private function fixed(string $instant, string $fireOn): Event
|
||
{
|
||
return new Event([
|
||
'due_mode' => DueMode::Fixed, 'due_instant' => CarbonImmutable::parse($instant),
|
||
'fire_on' => $fireOn, 'after_due' => AfterDue::Expire,
|
||
'state' => EventState::Scheduled, 'payload' => ['title' => 't'],
|
||
]);
|
||
}
|
||
|
||
public function test_whole_day_local_event(): void
|
||
{
|
||
$due = (new DueResolver)->resolve($this->local('2026-08-14'), $this->user());
|
||
|
||
$this->assertSame('2026-08-14 00:00 America/Montevideo', $due->day->format('Y-m-d H:i e'));
|
||
$this->assertSame('2026-08-15 00:00 America/Montevideo', $due->moment->format('Y-m-d H:i e'));
|
||
}
|
||
|
||
public function test_timed_local_event(): void
|
||
{
|
||
$due = (new DueResolver)->resolve($this->local('2026-09-10', '14:00:00'), $this->user());
|
||
|
||
$this->assertSame('2026-09-10 00:00', $due->day->format('Y-m-d H:i'));
|
||
$this->assertSame('2026-09-10 14:00', $due->moment->format('Y-m-d H:i'));
|
||
}
|
||
|
||
public function test_local_event_follows_user_timezone(): void
|
||
{
|
||
$event = $this->local('2026-09-10', '14:00:00');
|
||
|
||
$uy = (new DueResolver)->resolve($event, $this->user('America/Montevideo'));
|
||
$cn = (new DueResolver)->resolve($event, $this->user('Asia/Shanghai'));
|
||
|
||
$this->assertSame('2026-09-10 14:00', $uy->moment->format('Y-m-d H:i'));
|
||
$this->assertSame('2026-09-10 14:00', $cn->moment->format('Y-m-d H:i'));
|
||
$this->assertNotEquals($uy->moment->utc()->toIso8601String(), $cn->moment->utc()->toIso8601String());
|
||
}
|
||
|
||
public function test_fixed_event_is_converted_into_user_timezone(): void
|
||
{
|
||
// Вылет из Мадрида 08:15+02:00 = 06:15Z = 03:15 в Монтевидео (-03:00)
|
||
$due = (new DueResolver)->resolve($this->fixed('2026-09-10T08:15:00+02:00', '2026-09-03'), $this->user());
|
||
|
||
$this->assertSame('2026-09-10 03:15 America/Montevideo', $due->moment->format('Y-m-d H:i e'));
|
||
$this->assertSame('2026-09-10 00:00', $due->day->format('Y-m-d H:i'));
|
||
}
|
||
|
||
public function test_fire_start_is_local_midnight(): void
|
||
{
|
||
$start = (new DueResolver)->fireStart($this->local('2026-09-30', '24:00:00', '2026-08-31'), $this->user());
|
||
$this->assertSame('2026-08-31 00:00 America/Montevideo', $start->format('Y-m-d H:i e'));
|
||
}
|
||
|
||
public function test_today_starts_at_midnight_for_normal_moment(): void
|
||
{
|
||
$r = new DueResolver;
|
||
$due = $r->resolve($this->local('2026-09-10', '14:00:00'), $this->user());
|
||
$this->assertSame('2026-09-10 00:00', $r->todayStart($due, $this->user())->format('Y-m-d H:i'));
|
||
}
|
||
|
||
public function test_today_starts_the_evening_before_for_early_moment(): void
|
||
{
|
||
$r = new DueResolver;
|
||
$due = $r->resolve($this->local('2026-09-10', '07:00:00'), $this->user());
|
||
// quiet_start 22:00 − 2h = 20:00 накануне
|
||
$this->assertSame('2026-09-09 20:00', $r->todayStart($due, $this->user())->format('Y-m-d H:i'));
|
||
|
||
$exactlyAtActiveStart = $r->resolve($this->local('2026-09-10', '09:00:00'), $this->user());
|
||
$this->assertSame('2026-09-09 20:00', $r->todayStart($exactlyAtActiveStart, $this->user())->format('Y-m-d H:i'));
|
||
}
|
||
}
|