265 lines
10 KiB
PHP
265 lines
10 KiB
PHP
<?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,
|
|
]);
|
|
}
|
|
}
|