actingAs($user); BackupSchedule::query()->create([ 'tenant_id' => $tenant->id, 'name' => 'Daily 10:00', 'is_enabled' => true, 'timezone' => 'UTC', 'frequency' => 'daily', 'time_of_day' => '10:00:00', 'days_of_week' => null, 'policy_types' => ['deviceConfiguration'], 'include_foundations' => true, 'retention_keep_last' => 30, 'next_run_at' => null, ]); Bus::fake(); $dispatcher = app(BackupScheduleDispatcher::class); $dispatcher->dispatchDue([$tenant->external_id]); $dispatcher->dispatchDue([$tenant->external_id]); expect(OperationRun::query() ->where('tenant_id', $tenant->getKey()) ->where('type', 'backup_schedule_run') ->count())->toBe(1); Bus::assertDispatchedTimes(RunBackupScheduleJob::class, 1); Bus::assertDispatched(RunBackupScheduleJob::class, function (RunBackupScheduleJob $job) use ($tenant): bool { return $job->backupScheduleId !== null && $job->backupScheduleRunId === 0 && $job->operationRun?->tenant_id === $tenant->getKey() && $job->operationRun?->type === 'backup_schedule_run'; }); }); it('treats an existing canonical run as already-dispatched and advances next_run_at', function () { CarbonImmutable::setTestNow(CarbonImmutable::create(2026, 1, 5, 10, 0, 30, 'UTC')); [$user, $tenant] = createUserWithTenant(role: 'owner'); $this->actingAs($user); $schedule = BackupSchedule::query()->create([ 'tenant_id' => $tenant->id, 'name' => 'Daily 10:00', 'is_enabled' => true, 'timezone' => 'UTC', 'frequency' => 'daily', 'time_of_day' => '10:00:00', 'days_of_week' => null, 'policy_types' => ['deviceConfiguration'], 'include_foundations' => true, 'retention_keep_last' => 30, 'next_run_at' => null, ]); /** @var OperationRunService $operationRunService */ $operationRunService = app(OperationRunService::class); $operationRunService->ensureRunWithIdentityStrict( tenant: $tenant, type: 'backup_schedule_run', identityInputs: [ 'backup_schedule_id' => (int) $schedule->id, 'scheduled_for' => CarbonImmutable::now('UTC')->startOfMinute()->toDateTimeString(), ], context: [ 'backup_schedule_id' => (int) $schedule->id, 'scheduled_for' => CarbonImmutable::now('UTC')->startOfMinute()->toDateTimeString(), 'trigger' => 'scheduled', ], ); Bus::fake(); $dispatcher = app(BackupScheduleDispatcher::class); $dispatcher->dispatchDue([$tenant->external_id]); Bus::assertNotDispatched(RunBackupScheduleJob::class); expect(OperationRun::query() ->where('tenant_id', $tenant->getKey()) ->where('type', 'backup_schedule_run') ->count())->toBe(1); $schedule->refresh(); expect($schedule->next_run_at)->not->toBeNull(); expect($schedule->next_run_at->toDateTimeString())->toBe('2026-01-06 10:00:00'); });