11<?php
22
3+ use App \Actions \Backup \RunBackup ;
34use App \Enums \BackupFileStatus ;
45use App \Enums \BackupStatus ;
56use App \Facades \SSH ;
1011use Illuminate \Foundation \Testing \RefreshDatabase ;
1112use Illuminate \Support \Carbon ;
1213use Illuminate \Support \Facades \Bus ;
14+ use Illuminate \Support \Facades \DB ;
1315use Illuminate \Support \Facades \Http ;
16+ use Illuminate \Support \Facades \Log ;
1417
1518uses (RefreshDatabase::class);
1619
@@ -43,7 +46,7 @@ function vitoPestUnitCommandsRunBackupCommandTestCreateBackup(array $attributes)
4346
4447test ('run without any backups ' , function () {
4548 $ this ->artisan ('backups:run ' )
46- ->expectsOutput ('0 backups started ' );
49+ ->expectsOutput ('0 backups started, 0 failed ' );
4750});
4851
4952test ('runs backups that are due ' , function () {
@@ -54,7 +57,7 @@ function vitoPestUnitCommandsRunBackupCommandTestCreateBackup(array $attributes)
5457 vitoPestUnitCommandsRunBackupCommandTestCreateBackup (['interval ' => '0 * * * * ' ]);
5558
5659 $ this ->artisan ('backups:run ' )
57- ->expectsOutput ('1 backups started ' );
60+ ->expectsOutput ('1 backups started, 0 failed ' );
5861});
5962
6063test ('does not run backups that are not due ' , function () {
@@ -64,7 +67,7 @@ function vitoPestUnitCommandsRunBackupCommandTestCreateBackup(array $attributes)
6467 vitoPestUnitCommandsRunBackupCommandTestCreateBackup (['interval ' => '30 * * * * ' ]);
6568
6669 $ this ->artisan ('backups:run ' )
67- ->expectsOutput ('0 backups started ' );
70+ ->expectsOutput ('0 backups started, 0 failed ' );
6871});
6972
7073test ('runs custom interval backups when due ' , function () {
@@ -75,7 +78,7 @@ function vitoPestUnitCommandsRunBackupCommandTestCreateBackup(array $attributes)
7578 vitoPestUnitCommandsRunBackupCommandTestCreateBackup (['interval ' => '5 10 * * * ' ]);
7679
7780 $ this ->artisan ('backups:run ' )
78- ->expectsOutput ('1 backups started ' );
81+ ->expectsOutput ('1 backups started, 0 failed ' );
7982});
8083
8184test ('does not run disabled backups ' , function () {
@@ -85,7 +88,7 @@ function vitoPestUnitCommandsRunBackupCommandTestCreateBackup(array $attributes)
8588 vitoPestUnitCommandsRunBackupCommandTestCreateBackup (['interval ' => '* * * * * ' , 'enabled ' => false ]);
8689
8790 $ this ->artisan ('backups:run ' )
88- ->expectsOutput ('0 backups started ' );
91+ ->expectsOutput ('0 backups started, 0 failed ' );
8992});
9093
9194test ('does not run backups being deleted ' , function () {
@@ -95,7 +98,7 @@ function vitoPestUnitCommandsRunBackupCommandTestCreateBackup(array $attributes)
9598 vitoPestUnitCommandsRunBackupCommandTestCreateBackup (['interval ' => '* * * * * ' , 'status ' => BackupStatus::DELETING ]);
9699
97100 $ this ->artisan ('backups:run ' )
98- ->expectsOutput ('0 backups started ' );
101+ ->expectsOutput ('0 backups started, 0 failed ' );
99102});
100103
101104test ('runs enabled backup even after a failed run ' , function () {
@@ -111,18 +114,55 @@ function vitoPestUnitCommandsRunBackupCommandTestCreateBackup(array $attributes)
111114 ]);
112115
113116 $ this ->artisan ('backups:run ' )
114- ->expectsOutput ('1 backups started ' );
117+ ->expectsOutput ('1 backups started, 0 failed ' );
118+ });
119+
120+ test ('continues to the next backup when one fails ' , function () {
121+ SSH ::fake ();
122+ Log::spy ();
123+ Carbon::setTestNow ('2026-06-19 10:00:00 ' );
124+
125+ $ first = vitoPestUnitCommandsRunBackupCommandTestCreateBackup (['interval ' => '0 * * * * ' ]);
126+ vitoPestUnitCommandsRunBackupCommandTestCreateBackup (['interval ' => '0 * * * * ' ]);
127+
128+ $ calls = 0 ;
129+ $ this ->mock (RunBackup::class, function ($ mock ) use (&$ calls ): void {
130+ $ mock ->shouldReceive ('run ' )
131+ ->twice ()
132+ ->andReturnUsing (function (Backup $ backup ) use (&$ calls ): BackupFile {
133+ $ calls ++;
134+
135+ if ($ calls === 1 ) {
136+ throw new RuntimeException ('boom ' );
137+ }
138+
139+ return BackupFile::factory ()->create ([
140+ 'backup_id ' => $ backup ->id ,
141+ 'status ' => BackupFileStatus::CREATED ,
142+ ]);
143+ });
144+ });
145+
146+ $ this ->artisan ('backups:run ' )
147+ ->expectsOutput ('1 backups started, 1 failed ' );
148+
149+ Log::shouldHaveReceived ('warning ' )->withArgs (
150+ fn (string $ message , array $ context ): bool => $ context ['backup_id ' ] === $ first ->id
151+ && $ context ['server_id ' ] === $ this ->server ->id
152+ && $ context ['error ' ] === 'boom '
153+ );
115154});
116155
117156test ('does not run backups whose server is missing ' , function () {
157+ DB ::statement ('PRAGMA defer_foreign_keys = ON ' );
118158 SSH ::fake ();
119159 Bus::fake ();
120160 Carbon::setTestNow ('2026-06-19 10:00:00 ' );
121161
122162 $ backup = vitoPestUnitCommandsRunBackupCommandTestCreateBackup (['interval ' => '0 * * * * ' , 'server_id ' => 999999 ]);
123163
124164 $ this ->artisan ('backups:run ' )
125- ->expectsOutput ('0 backups started ' );
165+ ->expectsOutput ('0 backups started, 0 failed ' );
126166
127167 $ this ->assertDatabaseHas ('backups ' , ['id ' => $ backup ->id , 'server_id ' => 999999 ]);
128168 Bus::assertNothingDispatched ();
0 commit comments