'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, ]); } }