rootTeam = Team::factory()->create(['id' => 0, 'name' => 'Root Team']); $this->rootUser = User::factory()->create(); $this->rootUser->teams()->attach($this->rootTeam, ['role' => 'owner']); // Create regular team and user $this->regularTeam = Team::factory()->create(); $this->regularUser = User::factory()->create(); $this->regularUser->teams()->attach($this->regularTeam, ['role' => 'owner']); }); test('scheduled jobs page requires instance admin access', function () { $this->actingAs($this->regularUser); session(['currentTeam' => $this->regularTeam]); $response = $this->get(route('settings.scheduled-jobs')); $response->assertRedirect(route('dashboard')); }); test('scheduled jobs page is accessible by instance admin', function () { $this->actingAs($this->rootUser); session(['currentTeam' => $this->rootTeam]); Livewire::test(ScheduledJobs::class) ->assertStatus(200) ->assertSee('Scheduled Job Issues'); }); test('scheduled jobs page shows failed backup executions', function () { $this->actingAs($this->rootUser); session(['currentTeam' => $this->rootTeam]); $server = Server::factory()->create(['team_id' => $this->rootTeam->id]); $backup = ScheduledDatabaseBackup::create([ 'team_id' => $this->rootTeam->id, 'frequency' => '0 * * * *', 'database_id' => 1, 'database_type' => 'App\Models\StandalonePostgresql', 'enabled' => true, ]); ScheduledDatabaseBackupExecution::create([ 'scheduled_database_backup_id' => $backup->id, 'status' => 'failed', 'message' => 'Backup failed: connection timeout', ]); Livewire::test(ScheduledJobs::class) ->assertStatus(200) ->assertSee('Backup'); }); test('scheduled jobs page shows failed cleanup executions', function () { $this->actingAs($this->rootUser); session(['currentTeam' => $this->rootTeam]); $server = Server::factory()->create([ 'team_id' => $this->rootTeam->id, ]); DockerCleanupExecution::create([ 'server_id' => $server->id, 'status' => 'failed', 'message' => 'Cleanup failed: disk full', ]); Livewire::test(ScheduledJobs::class) ->assertStatus(200) ->assertSee('Cleanup'); }); test('filter by type works', function () { $this->actingAs($this->rootUser); session(['currentTeam' => $this->rootTeam]); Livewire::test(ScheduledJobs::class) ->set('filterType', 'backup') ->assertStatus(200) ->set('filterType', 'cleanup') ->assertStatus(200) ->set('filterType', 'task') ->assertStatus(200); }); test('only failed executions are shown', function () { $this->actingAs($this->rootUser); session(['currentTeam' => $this->rootTeam]); $backup = ScheduledDatabaseBackup::create([ 'team_id' => $this->rootTeam->id, 'frequency' => '0 * * * *', 'database_id' => 1, 'database_type' => 'App\Models\StandalonePostgresql', 'enabled' => true, ]); ScheduledDatabaseBackupExecution::create([ 'scheduled_database_backup_id' => $backup->id, 'status' => 'success', 'message' => 'Backup completed successfully', ]); ScheduledDatabaseBackupExecution::create([ 'scheduled_database_backup_id' => $backup->id, 'status' => 'failed', 'message' => 'Backup failed: connection refused', ]); Livewire::test(ScheduledJobs::class) ->assertSee('Backup failed: connection refused') ->assertDontSee('Backup completed successfully'); }); test('filter by date range works', function () { $this->actingAs($this->rootUser); session(['currentTeam' => $this->rootTeam]); Livewire::test(ScheduledJobs::class) ->set('filterDate', 'last_7d') ->assertStatus(200) ->set('filterDate', 'last_30d') ->assertStatus(200) ->set('filterDate', 'all') ->assertStatus(200); }); test('scheduler log parser returns empty collection when no logs exist', function () { $parser = new SchedulerLogParser; $skips = $parser->getRecentSkips(); expect($skips)->toBeEmpty(); $runs = $parser->getRecentRuns(); expect($runs)->toBeEmpty(); })->skip(fn () => file_exists(storage_path('logs/scheduled-'.now()->format('Y-m-d').'.log')), 'Skipped: log file already exists from other tests'); test('scheduler log parser parses skip entries correctly', function () { $logPath = storage_path('logs/scheduled-'.now()->format('Y-m-d').'.log'); $logDir = dirname($logPath); if (! is_dir($logDir)) { mkdir($logDir, 0755, true); } $logLine = '['.now()->format('Y-m-d H:i:s').'] production.INFO: Backup skipped {"type":"backup","skip_reason":"server_not_functional","execution_time":"'.now()->toIso8601String().'","backup_id":1,"team_id":5}'; file_put_contents($logPath, $logLine."\n"); $parser = new SchedulerLogParser; $skips = $parser->getRecentSkips(); expect($skips)->toHaveCount(1); expect($skips->first()['type'])->toBe('backup'); expect($skips->first()['reason'])->toBe('server_not_functional'); expect($skips->first()['team_id'])->toBe(5); // Cleanup @unlink($logPath); }); test('scheduler log parser filters by team id', function () { $logPath = storage_path('logs/scheduled-'.now()->format('Y-m-d').'.log'); $logDir = dirname($logPath); if (! is_dir($logDir)) { mkdir($logDir, 0755, true); } $lines = [ '['.now()->format('Y-m-d H:i:s').'] production.INFO: Backup skipped {"type":"backup","skip_reason":"server_not_functional","team_id":1}', '['.now()->format('Y-m-d H:i:s').'] production.INFO: Backup skipped {"type":"backup","skip_reason":"subscription_unpaid","team_id":2}', ]; file_put_contents($logPath, implode("\n", $lines)."\n"); $parser = new SchedulerLogParser; $allSkips = $parser->getRecentSkips(100); expect($allSkips)->toHaveCount(2); $team1Skips = $parser->getRecentSkips(100, 1); expect($team1Skips)->toHaveCount(1); expect($team1Skips->first()['team_id'])->toBe(1); // Cleanup @unlink($logPath); });