++ fix e controlli
This commit is contained in:
202
tests/Unit/BackupOperationStateTest.php
Normal file
202
tests/Unit/BackupOperationStateTest.php
Normal file
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Services\BackupOperationState;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Il "posto unico": backup, ripristino e azzeramento lavorano tutti sullo
|
||||
* stesso database e sullo stesso disco, quindi due in parallelo non si
|
||||
* rallentano soltanto, si sovrascrivono. Qui si verifica che il posto venga
|
||||
* occupato, rilasciato e — soprattutto — che non resti occupato per sempre.
|
||||
*
|
||||
* Gira su cache 'array' (phpunit.xml): nessun Redis in mezzo.
|
||||
*/
|
||||
class BackupOperationStateTest extends TestCase
|
||||
{
|
||||
private const CHIAVE_CORRENTE = 'backup-op:corrente';
|
||||
|
||||
private BackupOperationState $stato;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->stato = new BackupOperationState();
|
||||
}
|
||||
|
||||
// ─── Il posto unico ─────────────────────────────────────────
|
||||
|
||||
public function test_senza_operazioni_il_posto_e_libero(): void
|
||||
{
|
||||
$this->assertNull($this->stato->corrente());
|
||||
}
|
||||
|
||||
public function test_avviare_un_operazione_occupa_il_posto(): void
|
||||
{
|
||||
$this->stato->initialize('op-1', BackupOperationState::TIPO_BACKUP, 'Preparazione', 10);
|
||||
|
||||
$this->assertSame('op-1', $this->stato->corrente());
|
||||
}
|
||||
|
||||
public function test_il_completamento_libera_il_posto(): void
|
||||
{
|
||||
$this->stato->initialize('op-1', BackupOperationState::TIPO_BACKUP, 'Preparazione', 10);
|
||||
$this->stato->markCompleted('op-1', ['file' => 'backup.zip']);
|
||||
|
||||
$this->assertNull($this->stato->corrente());
|
||||
$this->assertSame('completed', $this->stato->get('op-1')['status']);
|
||||
}
|
||||
|
||||
public function test_il_fallimento_libera_il_posto(): void
|
||||
{
|
||||
$this->stato->initialize('op-1', BackupOperationState::TIPO_RESTORE, 'Estrazione', 10);
|
||||
$this->stato->markFailed('op-1', 'disco pieno');
|
||||
|
||||
$this->assertNull($this->stato->corrente());
|
||||
|
||||
$finale = $this->stato->get('op-1');
|
||||
$this->assertSame('failed', $finale['status']);
|
||||
$this->assertSame('disco pieno', $finale['error']);
|
||||
$this->assertStringContainsString('ERRORE: disco pieno', end($finale['logs']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Il caso che il TTL da solo non copre: lo stato dell'operazione e' scaduto
|
||||
* ma il puntatore no. Senza questa pulizia il posto resterebbe occupato da
|
||||
* un'operazione che non esiste piu'.
|
||||
*/
|
||||
public function test_un_puntatore_orfano_non_tiene_occupato_il_posto(): void
|
||||
{
|
||||
Cache::put(self::CHIAVE_CORRENTE, 'op-svanita', 3600);
|
||||
|
||||
$this->assertNull($this->stato->corrente());
|
||||
$this->assertNull(Cache::get(self::CHIAVE_CORRENTE), 'Il puntatore orfano va anche rimosso.');
|
||||
}
|
||||
|
||||
public function test_un_puntatore_a_un_operazione_gia_chiusa_non_tiene_occupato_il_posto(): void
|
||||
{
|
||||
$this->stato->initialize('op-1', BackupOperationState::TIPO_RESET, 'Backup preventivo', 5);
|
||||
|
||||
// Chiusura "a mano", come se il worker fosse morto dopo aver scritto lo
|
||||
// stato ma prima di rilasciare il posto.
|
||||
$this->stato->update('op-1', function (array $s) {
|
||||
$s['status'] = 'completed';
|
||||
|
||||
return $s;
|
||||
});
|
||||
Cache::put(self::CHIAVE_CORRENTE, 'op-1', 3600);
|
||||
|
||||
$this->assertNull($this->stato->corrente());
|
||||
$this->assertNull(Cache::get(self::CHIAVE_CORRENTE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Senza il confronto sull'id, la chiusura tardiva di un'operazione vecchia
|
||||
* sbloccherebbe quella appena avviata da qualcun altro — cioe' proprio il
|
||||
* parallelismo che il posto unico esiste per impedire.
|
||||
*/
|
||||
public function test_un_operazione_vecchia_che_si_chiude_non_libera_il_posto_della_nuova(): void
|
||||
{
|
||||
$this->stato->initialize('op-vecchia', BackupOperationState::TIPO_BACKUP, 'Preparazione', 3);
|
||||
$this->stato->initialize('op-nuova', BackupOperationState::TIPO_RESTORE, 'Estrazione', 3);
|
||||
|
||||
$this->stato->markCompleted('op-vecchia');
|
||||
|
||||
$this->assertSame('op-nuova', $this->stato->corrente());
|
||||
}
|
||||
|
||||
/**
|
||||
* L'azzeramento si ferma in attesa del download e riparte da un secondo
|
||||
* clic: in quella pausa il posto deve restare occupato, altrimenti un'altra
|
||||
* operazione potrebbe infilarsi fra il backup preventivo e la cancellazione.
|
||||
*/
|
||||
public function test_l_attesa_del_download_tiene_il_posto_occupato(): void
|
||||
{
|
||||
$this->stato->initialize('op-1', BackupOperationState::TIPO_RESET, 'Backup preventivo', 4);
|
||||
$this->stato->markAttesaDownload('op-1', ['file' => 'backup.zip']);
|
||||
|
||||
$this->assertSame('op-1', $this->stato->corrente());
|
||||
|
||||
$corrente = $this->stato->get('op-1');
|
||||
$this->assertSame(BackupOperationState::FASE_ATTESA_DOWNLOAD, $corrente['fase']);
|
||||
$this->assertSame('running', $corrente['status']);
|
||||
$this->assertSame($corrente['total'], $corrente['processed'], 'La barra deve risultare piena.');
|
||||
}
|
||||
|
||||
// ─── Avanzamento ────────────────────────────────────────────
|
||||
|
||||
public function test_il_cambio_di_fase_riazzera_l_avanzamento_sul_nuovo_totale(): void
|
||||
{
|
||||
$this->stato->initialize('op-1', BackupOperationState::TIPO_RESTORE, 'Estrazione', 100);
|
||||
$this->stato->avanza('op-1', 80);
|
||||
|
||||
$this->stato->fase('op-1', 'Scrittura file', 7);
|
||||
|
||||
$corrente = $this->stato->get('op-1');
|
||||
$this->assertSame('Scrittura file', $corrente['fase']);
|
||||
$this->assertSame(7, $corrente['total']);
|
||||
$this->assertSame(0, $corrente['processed']);
|
||||
}
|
||||
|
||||
public function test_il_completamento_porta_la_barra_al_totale(): void
|
||||
{
|
||||
$this->stato->initialize('op-1', BackupOperationState::TIPO_BACKUP, 'Compressione', 12);
|
||||
$this->stato->avanza('op-1', 9);
|
||||
|
||||
$this->stato->markCompleted('op-1');
|
||||
|
||||
$this->assertSame(12, $this->stato->get('op-1')['processed']);
|
||||
}
|
||||
|
||||
public function test_il_risultato_del_completamento_non_cancella_quello_gia_registrato(): void
|
||||
{
|
||||
$this->stato->initialize('op-1', BackupOperationState::TIPO_RESET, 'Backup preventivo', 1);
|
||||
$this->stato->markAttesaDownload('op-1', ['file' => 'backup.zip']);
|
||||
|
||||
$this->stato->markCompleted('op-1', ['tabelle' => 9]);
|
||||
|
||||
$risultato = $this->stato->get('op-1')['result'];
|
||||
$this->assertSame('backup.zip', $risultato['file']);
|
||||
$this->assertSame(9, $risultato['tabelle']);
|
||||
}
|
||||
|
||||
// ─── Log ────────────────────────────────────────────────────
|
||||
|
||||
public function test_la_coda_dei_log_tiene_solo_le_ultime_righe(): void
|
||||
{
|
||||
$this->stato->initialize('op-1', BackupOperationState::TIPO_BACKUP, 'Preparazione', 0);
|
||||
|
||||
for ($i = 1; $i <= 75; $i++) {
|
||||
$this->stato->log('op-1', "riga {$i}");
|
||||
}
|
||||
|
||||
$logs = $this->stato->get('op-1')['logs'];
|
||||
|
||||
// Lo stato viaggia per intero a ogni poll: una coda illimitata
|
||||
// gonfierebbe ogni risposta di un'operazione lunga.
|
||||
$this->assertCount(60, $logs);
|
||||
$this->assertStringContainsString('riga 75', end($logs));
|
||||
$this->assertStringContainsString('riga 16', $logs[0]);
|
||||
}
|
||||
|
||||
// ─── Bordi ──────────────────────────────────────────────────
|
||||
|
||||
public function test_aggiornare_un_operazione_inesistente_non_la_crea(): void
|
||||
{
|
||||
$this->assertNull($this->stato->update('mai-esistita', fn (array $s) => $s));
|
||||
$this->assertNull($this->stato->get('mai-esistita'));
|
||||
}
|
||||
|
||||
public function test_forget_rimuove_lo_stato(): void
|
||||
{
|
||||
$this->stato->initialize('op-1', BackupOperationState::TIPO_BACKUP, 'Preparazione', 1);
|
||||
|
||||
$this->stato->forget('op-1');
|
||||
|
||||
$this->assertNull($this->stato->get('op-1'));
|
||||
$this->assertNull($this->stato->corrente(), 'Senza stato il posto risulta libero.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user