createMock(ScheduledJobRepository::class); $runRepository = $this->createMock(ScheduledJobRunRepository::class); $registry = $this->createMock(ScheduledJobRegistry::class); $calculator = new ScheduleCalculator(); $registry->expects($this->once())->method('definitions')->willReturn([]); $jobRepository->expects($this->once())->method('find')->with(10)->willReturn([ 'id' => 10, 'job_key' => 'user_lifecycle_run', 'label' => 'User lifecycle run', 'description' => '', 'enabled' => 1, 'timezone' => 'UTC', 'schedule_type' => 'daily', 'schedule_interval' => 1, 'schedule_time' => '02:15', 'schedule_weekdays_csv' => null, 'catchup_once' => 1, ]); $registry->expects($this->once())->method('get')->with('user_lifecycle_run')->willReturn([ 'label' => 'User lifecycle run', 'description' => '', 'allowed_schedule_types' => ['hourly', 'daily', 'weekly'], ]); $jobRepository->expects($this->never())->method('updateJobMeta'); $service = new ScheduledJobService($jobRepository, $runRepository, $registry, $calculator); $result = $service->updateFromAdmin(10, [ 'enabled' => '1', 'timezone' => 'UTC', 'schedule_type' => 'weekly', 'schedule_interval' => '1', 'schedule_time' => '03:00', ]); $this->assertFalse($result['ok']); $this->assertNotEmpty($result['errors']); } public function testUpdateFromAdminPersistsNormalizedValuesAndReturnsFreshJob(): void { $jobRepository = $this->createMock(ScheduledJobRepository::class); $runRepository = $this->createMock(ScheduledJobRunRepository::class); $registry = $this->createMock(ScheduledJobRegistry::class); $calculator = new ScheduleCalculator(); $registry->expects($this->exactly(2))->method('definitions')->willReturn([]); $jobRepository->expects($this->exactly(2))->method('find')->with(11)->willReturnOnConsecutiveCalls( [ 'id' => 11, 'job_key' => 'user_lifecycle_run', 'label' => 'User lifecycle run', 'description' => 'Runs lifecycle', 'enabled' => 1, 'timezone' => 'UTC', 'schedule_type' => 'daily', 'schedule_interval' => 1, 'schedule_time' => '02:15', 'schedule_weekdays_csv' => null, 'catchup_once' => 1, ], [ 'id' => 11, 'job_key' => 'user_lifecycle_run', 'label' => 'User lifecycle run', 'description' => 'Runs lifecycle', 'enabled' => 0, 'timezone' => 'UTC', 'schedule_type' => 'daily', 'schedule_interval' => 1, 'schedule_time' => '04:00', 'schedule_weekdays_csv' => null, 'catchup_once' => 0, 'next_run_at' => null, ] ); $registry->expects($this->once())->method('get')->with('user_lifecycle_run')->willReturn([ 'label' => 'User lifecycle run', 'description' => 'Runs lifecycle', 'allowed_schedule_types' => ['hourly', 'daily', 'weekly'], ]); $capturedUpdate = null; $jobRepository->expects($this->once()) ->method('updateJobMeta') ->with(11, $this->anything()) ->willReturnCallback(function (int $id, array $data) use (&$capturedUpdate): bool { $capturedUpdate = $data; return true; }); $service = new ScheduledJobService($jobRepository, $runRepository, $registry, $calculator); $result = $service->updateFromAdmin(11, [ 'timezone' => 'UTC', 'schedule_type' => 'daily', 'schedule_interval' => '1', 'schedule_time' => '04:00', ]); $this->assertTrue($result['ok']); $this->assertSame(11, (int) (($result['job'] ?? [])['id'] ?? 0)); $this->assertSame(0, (int) (($result['job'] ?? [])['enabled'] ?? 1)); $this->assertIsArray($capturedUpdate); $this->assertSame(0, (int) ($capturedUpdate['enabled'] ?? -1)); $this->assertSame('UTC', (string) ($capturedUpdate['timezone'] ?? '')); $this->assertSame('daily', (string) ($capturedUpdate['schedule_type'] ?? '')); $this->assertSame(1, (int) ($capturedUpdate['schedule_interval'] ?? -1)); $this->assertSame('04:00', (string) ($capturedUpdate['schedule_time'] ?? '')); $this->assertSame(0, (int) ($capturedUpdate['catchup_once'] ?? -1)); $this->assertNull($capturedUpdate['next_run_at'] ?? null); } }