++ fix e controlli
This commit is contained in:
264
tests/Feature/ZonaPericolosaTest.php
Normal file
264
tests/Feature/ZonaPericolosaTest.php
Normal file
@@ -0,0 +1,264 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Jobs\CreateBackupArchive;
|
||||
use App\Jobs\ResetApplication;
|
||||
use App\Livewire\Settings\BackupManager;
|
||||
use App\Livewire\Settings\ZonaPericolosa;
|
||||
use App\Models\User;
|
||||
use App\Services\BackupOperationState;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Livewire\Livewire;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Spatie\Permission\PermissionRegistrar;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* L'azzeramento: l'unica operazione irreversibile dell'applicazione.
|
||||
*
|
||||
* Questi test esistono perché il flusso è stato spostato da BackupManager a un
|
||||
* componente suo, per poterlo mettere in fondo alla pagina invece che a metà. Lo
|
||||
* spostamento non doveva cambiare niente di quello che protegge: i due tempi
|
||||
* separati dal download, la password richiesta al secondo, il limite ai
|
||||
* tentativi, il posto unico. Sono esattamente i controlli di cui non ci si
|
||||
* accorge che si sono rotti finché non servono.
|
||||
*/
|
||||
class ZonaPericolosaTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private const PASSWORD = 'password-di-prova';
|
||||
|
||||
private User $admin;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Queue::fake();
|
||||
|
||||
Role::create(['name' => 'amministratore', 'guard_name' => 'web']);
|
||||
app(PermissionRegistrar::class)->forgetCachedPermissions();
|
||||
|
||||
$this->admin = User::create([
|
||||
'name' => 'Amministratore',
|
||||
'email' => 'admin@example.test',
|
||||
'password' => self::PASSWORD,
|
||||
]);
|
||||
$this->admin->assignRole('amministratore');
|
||||
|
||||
$this->actingAs($this->admin);
|
||||
}
|
||||
|
||||
// ─── Primo tempo ────────────────────────────────────────────
|
||||
|
||||
public function test_senza_la_frase_esatta_non_parte_niente(): void
|
||||
{
|
||||
Livewire::test(ZonaPericolosa::class)
|
||||
->set('confermaReset', 'azzera')
|
||||
->call('avviaAzzeramento')
|
||||
->assertHasErrors('confermaReset');
|
||||
|
||||
Queue::assertNothingPushed();
|
||||
$this->assertNull(app(BackupOperationState::class)->corrente());
|
||||
}
|
||||
|
||||
public function test_la_frase_esatta_avvia_il_backup_preventivo_e_niente_altro(): void
|
||||
{
|
||||
Livewire::test(ZonaPericolosa::class)
|
||||
->set('confermaReset', ZonaPericolosa::CONFERMA_AZZERAMENTO)
|
||||
->call('avviaAzzeramento')
|
||||
->assertHasNoErrors();
|
||||
|
||||
// Il primo tempo crea solo l'archivio: la cancellazione arriva dopo, e
|
||||
// solo su conferma umana.
|
||||
Queue::assertPushed(CreateBackupArchive::class);
|
||||
Queue::assertNotPushed(ResetApplication::class);
|
||||
|
||||
$stato = app(BackupOperationState::class);
|
||||
$corrente = $stato->corrente();
|
||||
|
||||
$this->assertNotNull($corrente, 'L\'azzeramento deve occupare il posto unico.');
|
||||
$this->assertSame(BackupOperationState::TIPO_RESET, $stato->get($corrente)['tipo']);
|
||||
}
|
||||
|
||||
public function test_la_frase_di_conferma_si_svuota_dopo_l_avvio(): void
|
||||
{
|
||||
Livewire::test(ZonaPericolosa::class)
|
||||
->set('confermaReset', ZonaPericolosa::CONFERMA_AZZERAMENTO)
|
||||
->call('avviaAzzeramento')
|
||||
->assertSet('confermaReset', '');
|
||||
}
|
||||
|
||||
public function test_un_backup_gia_in_corso_impedisce_l_azzeramento(): void
|
||||
{
|
||||
app(BackupOperationState::class)->initialize('op-backup', BackupOperationState::TIPO_BACKUP, 'Compressione', 10);
|
||||
|
||||
Livewire::test(ZonaPericolosa::class)
|
||||
->set('confermaReset', ZonaPericolosa::CONFERMA_AZZERAMENTO)
|
||||
->call('avviaAzzeramento');
|
||||
|
||||
Queue::assertNotPushed(CreateBackupArchive::class);
|
||||
$this->assertSame('op-backup', app(BackupOperationState::class)->corrente());
|
||||
}
|
||||
|
||||
// ─── Secondo tempo ──────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Il controllo che regge tutto il disegno a due tempi: senza la prova che
|
||||
* l'archivio è stato spedito, la cancellazione non parte. Il server non può
|
||||
* sapere se il file è *arrivato* — nginx bufferizza la risposta e un
|
||||
* download annullato è indistinguibile da uno riuscito — ma può almeno
|
||||
* sapere di averlo mandato.
|
||||
*/
|
||||
public function test_senza_archivio_servito_la_conferma_non_cancella_niente(): void
|
||||
{
|
||||
$this->operazioneInAttesa(servito: false);
|
||||
|
||||
Livewire::test(ZonaPericolosa::class)
|
||||
->call('confermaAzzeramento', self::PASSWORD)
|
||||
->assertHasErrors('confermaReset');
|
||||
|
||||
Queue::assertNotPushed(ResetApplication::class);
|
||||
}
|
||||
|
||||
public function test_senza_password_la_conferma_non_cancella_niente(): void
|
||||
{
|
||||
$this->operazioneInAttesa();
|
||||
|
||||
Livewire::test(ZonaPericolosa::class)
|
||||
->call('confermaAzzeramento', '')
|
||||
->assertHasErrors('passwordConferma');
|
||||
|
||||
Queue::assertNotPushed(ResetApplication::class);
|
||||
}
|
||||
|
||||
public function test_con_la_password_sbagliata_la_conferma_non_cancella_niente(): void
|
||||
{
|
||||
$this->operazioneInAttesa();
|
||||
|
||||
Livewire::test(ZonaPericolosa::class)
|
||||
->call('confermaAzzeramento', 'non-e-la-mia-password')
|
||||
->assertHasErrors('passwordConferma');
|
||||
|
||||
Queue::assertNotPushed(ResetApplication::class);
|
||||
}
|
||||
|
||||
public function test_con_la_password_giusta_l_azzeramento_parte(): void
|
||||
{
|
||||
$this->operazioneInAttesa();
|
||||
|
||||
Livewire::test(ZonaPericolosa::class)
|
||||
->call('confermaAzzeramento', self::PASSWORD)
|
||||
->assertHasNoErrors();
|
||||
|
||||
Queue::assertPushed(ResetApplication::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Senza limite ai tentativi, chiedere la password su una sessione dirottata
|
||||
* sarebbe solo un rallentamento: si proverebbero le password comuni a
|
||||
* raffica, e il resto della pagina è già raggiungibile.
|
||||
*/
|
||||
public function test_cinque_tentativi_sbagliati_bloccano_l_operazione(): void
|
||||
{
|
||||
$this->operazioneInAttesa();
|
||||
|
||||
for ($tentativo = 1; $tentativo <= 5; $tentativo++) {
|
||||
Livewire::test(ZonaPericolosa::class)->call('confermaAzzeramento', 'sbagliata');
|
||||
}
|
||||
|
||||
// Il sesto tentativo è rifiutato anche se la password è quella giusta.
|
||||
Livewire::test(ZonaPericolosa::class)
|
||||
->call('confermaAzzeramento', self::PASSWORD)
|
||||
->assertHasErrors('passwordConferma');
|
||||
|
||||
Queue::assertNotPushed(ResetApplication::class);
|
||||
}
|
||||
|
||||
public function test_annullare_libera_il_posto_e_lascia_il_backup_scaricabile(): void
|
||||
{
|
||||
$this->operazioneInAttesa();
|
||||
|
||||
Livewire::test(ZonaPericolosa::class)->call('annullaAzzeramento');
|
||||
|
||||
$stato = app(BackupOperationState::class);
|
||||
|
||||
$this->assertNull($stato->corrente(), 'Annullando, il posto deve tornare libero.');
|
||||
Queue::assertNotPushed(ResetApplication::class);
|
||||
|
||||
// Da qui in poi è un backup come un altro: l'archivio resta valido, e la
|
||||
// zona di backup deve poterlo offrire in download.
|
||||
$finale = $stato->get('op-reset');
|
||||
$this->assertSame(BackupOperationState::TIPO_BACKUP, $finale['tipo']);
|
||||
$this->assertSame('completed', $finale['status']);
|
||||
}
|
||||
|
||||
// ─── Console e proprietà dell'operazione ────────────────────
|
||||
|
||||
/**
|
||||
* Ogni operazione ha un solo componente che la racconta. Senza, backup e
|
||||
* zona pericolosa spedirebbero entrambi le stesse righe e la console le
|
||||
* mostrerebbe due volte.
|
||||
*/
|
||||
public function test_la_zona_pericolosa_racconta_solo_i_propri_azzeramenti(): void
|
||||
{
|
||||
$stato = app(BackupOperationState::class);
|
||||
$stato->initialize('op-backup', BackupOperationState::TIPO_BACKUP, 'Compressione', 4);
|
||||
$stato->log('op-backup', 'Riga di un backup, non di un azzeramento.');
|
||||
|
||||
Livewire::test(ZonaPericolosa::class)->assertNotDispatched('console-operazione');
|
||||
Livewire::test(BackupManager::class)->assertDispatched('console-operazione');
|
||||
}
|
||||
|
||||
/**
|
||||
* Il difetto che questo test presidia: chi non possiede l'operazione polla
|
||||
* comunque, e consumerebbe per primo il segno "download già inviato" —
|
||||
* spegnendo l'evento prima che il proprietario lo emetta. L'archivio
|
||||
* sarebbe pronto e non arriverebbe a nessuno, e per l'azzeramento è pure il
|
||||
* passo che sblocca la cancellazione.
|
||||
*/
|
||||
public function test_il_download_lo_emette_solo_chi_possiede_l_operazione(): void
|
||||
{
|
||||
$this->operazioneInAttesa(servito: false, downloadInviato: false);
|
||||
|
||||
Livewire::test(BackupManager::class)->assertNotDispatched('scarica-backup');
|
||||
|
||||
$this->assertFalse(
|
||||
app(BackupOperationState::class)->get('op-reset')['result']['download_inviato'] ?? false,
|
||||
'Il componente che non possiede l\'operazione non deve consumare il segno del download.'
|
||||
);
|
||||
|
||||
Livewire::test(ZonaPericolosa::class)->assertDispatched('scarica-backup');
|
||||
}
|
||||
|
||||
public function test_il_download_si_emette_una_volta_sola(): void
|
||||
{
|
||||
$this->operazioneInAttesa(servito: false, downloadInviato: false);
|
||||
|
||||
Livewire::test(ZonaPericolosa::class)->assertDispatched('scarica-backup');
|
||||
Livewire::test(ZonaPericolosa::class)->assertNotDispatched('scarica-backup');
|
||||
}
|
||||
|
||||
// ─── helper ─────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Azzeramento fermo al secondo tempo: l'archivio esiste e, salvo diversa
|
||||
* indicazione, risulta spedito.
|
||||
*/
|
||||
private function operazioneInAttesa(bool $servito = true, bool $downloadInviato = true): void
|
||||
{
|
||||
$stato = app(BackupOperationState::class);
|
||||
|
||||
$stato->initialize('op-reset', BackupOperationState::TIPO_RESET, 'Compressione', 3);
|
||||
$stato->markAttesaDownload('op-reset', [
|
||||
'file' => '/tmp/backup.zip',
|
||||
'nome_download' => 'backup.zip',
|
||||
'dimensione' => 1024,
|
||||
'servito_at' => $servito ? now()->toDateTimeString() : null,
|
||||
'download_inviato' => $downloadInviato,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user