diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 72c9b148..9fe9075f 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -22,13 +22,27 @@ class Kernel extends ConsoleKernel ->when(fn (): bool => config('scheduling.tasks.auth_send_mails_expiring_api_tokens')) ->everyTenMinutes(); - $schedule->command('self-host:check-for-update') - ->when(fn (): bool => config('scheduling.tasks.self_hosting_check_for_update')) - ->twiceDaily(); + if (config('scheduling.tasks.self_hosting_check_for_update') || config('scheduling.tasks.self_hosting_telemetry')) { + // Convert string to a stable integer for seeding + /** @var int $seed Take the first 8 hex chars → 32-bit int */ + $seed = hexdec(substr(hash('md5', config('app.key')), 0, 8)); + $seed = abs($seed); // Ensure it's positive + mt_srand($seed); + $firstHour = mt_rand(0, 23); + $secondHour = ($firstHour + 12) % 24; + $minuteOffset = mt_rand(0, 59); + mt_srand(null); // Reset the random number generator - $schedule->command('self-host:telemetry') - ->when(fn (): bool => config('scheduling.tasks.self_hosting_telemetry')) - ->twiceDaily(); + if (config('scheduling.tasks.self_hosting_check_for_update')) { + $schedule->command('self-host:check-for-update') + ->twiceDailyAt($firstHour, $secondHour, $minuteOffset); + } + + if (config('scheduling.tasks.self_hosting_telemetry')) { + $schedule->command('self-host:telemetry') + ->twiceDailyAt($firstHour, $secondHour, $minuteOffset); + } + } $schedule->command('self-host:database-consistency') ->when(fn (): bool => config('scheduling.tasks.self_hosting_database_consistency')) diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index 7b2e4339..0c2f1265 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -42,7 +42,7 @@ class HandleInertiaRequests extends Middleware $hasBilling = Module::has('Billing') && Module::isEnabled('Billing'); $hasInvoicing = Module::has('Invoicing') && Module::isEnabled('Invoicing'); $hasServices = Module::has('Services') && Module::isEnabled('Services'); - + /** @var BillingContract $billing */ $billing = app(BillingContract::class); diff --git a/tests/Unit/Console/KernelTest.php b/tests/Unit/Console/KernelTest.php new file mode 100644 index 00000000..5984c56b --- /dev/null +++ b/tests/Unit/Console/KernelTest.php @@ -0,0 +1,114 @@ + 'base64:cOXN4GLMXYjcdG0fKosnFogofXw1pNoXkLAViRH+a5Y=', + ]); + + // Act + $schedule1 = app()->make(Kernel::class)->resolveConsoleSchedule(); + $firstRunEvents = collect($schedule1->events())->filter(fn ($event) => str_contains($event->command, 'self-host:check-for-update') || + str_contains($event->command, 'self-host:telemetry') + ); + + $schedule2 = app()->make(Kernel::class)->resolveConsoleSchedule(); + $secondRunEvents = collect($schedule2->events())->filter(fn ($event) => str_contains($event->command, 'self-host:check-for-update') || + str_contains($event->command, 'self-host:telemetry') + ); + config([ + 'app.key' => 'base64:eP58hkQ8l3guqf8wvWJR7pB0weVQtnpjMdYpaVwX4Jw=', + ]); + $schedule3 = app()->make(Kernel::class)->resolveConsoleSchedule(); + $thirdRunEvents = collect($schedule3->events())->filter(fn ($event) => str_contains($event->command, 'self-host:check-for-update') || + str_contains($event->command, 'self-host:telemetry') + ); + + // Assert + $this->assertCount(2, $firstRunEvents); + $this->assertCount(2, $secondRunEvents); + $this->assertCount(2, $thirdRunEvents); + + foreach ($firstRunEvents as $index => $event) { + $this->assertSame('52 9,21 * * *', $firstRunEvents[$index]->expression); + $this->assertSame('52 9,21 * * *', $secondRunEvents[$index]->expression); + $this->assertSame('48 13,1 * * *', $thirdRunEvents[$index]->expression); + } + } + + public function test_self_hosting_telemetry_can_be_activated(): void + { + // Arrange + config([ + 'scheduling.tasks.self_hosting_telemetry' => true, + ]); + + // Act + $schedule = app()->make(Kernel::class)->resolveConsoleSchedule(); + $events = collect($schedule->events())->filter(fn ($event) => str_contains($event->command, 'self-host:telemetry') + ); + + // Assert + $this->assertCount(1, $events); + } + + public function test_self_hosting_telemetry_can_be_deactivated(): void + { + // Arrange + config([ + 'scheduling.tasks.self_hosting_telemetry' => false, + ]); + + // Act + $schedule = app()->make(Kernel::class)->resolveConsoleSchedule(); + $events = collect($schedule->events())->filter(fn ($event) => str_contains($event->command, 'self-host:telemetry') + ); + + // Assert + $this->assertCount(0, $events); + } + + public function test_self_hosting_check_for_update_can_be_activated(): void + { + // Arrange + config([ + 'scheduling.tasks.self_hosting_check_for_update' => true, + ]); + + // Act + $schedule = app()->make(Kernel::class)->resolveConsoleSchedule(); + $events = collect($schedule->events())->filter(fn ($event) => str_contains($event->command, 'self-host:check-for-update') + ); + + // Assert + $this->assertCount(1, $events); + } + + public function test_self_hosting_check_for_update_can_be_deactivated(): void + { + // Arrange + config([ + 'scheduling.tasks.self_hosting_check_for_update' => false, + ]); + + // Act + $schedule = app()->make(Kernel::class)->resolveConsoleSchedule(); + $events = collect($schedule->events())->filter(fn ($event) => str_contains($event->command, 'self-host:check-for-update') + ); + + // Assert + $this->assertCount(0, $events); + } +}