> endobj\ntrailer << /Root 1 0 R >>\n%%EOF\n"; private User $admin; protected function setUp(): void { parent::setUp(); Storage::fake('public'); Storage::fake('local'); $this->mock(TerritorioThumbnailService::class, function ($mock) { $mock->shouldReceive('generate')->andReturn(null); $mock->shouldReceive('delete'); }); Role::create(['name' => 'amministratore', 'guard_name' => 'web']); app(PermissionRegistrar::class)->forgetCachedPermissions(); $this->admin = User::create([ 'name' => 'Amministratore', 'email' => 'admin@example.test', 'password' => 'password-di-prova', ]); $this->admin->assignRole('amministratore'); $this->actingAs($this->admin); } // ─── Un'operazione di backup blocca gli import ────────────── public function test_l_import_pdf_da_cartella_non_parte_durante_un_ripristino(): void { Queue::fake(); app(BackupOperationState::class)->initialize('op-restore', BackupOperationState::TIPO_RESTORE, 'Estrazione', 10); Territorio::create(['numero' => '1', 'attivo' => true]); Livewire::test(XmlExchange::class) ->set('pdfFolder', [UploadedFile::fake()->createWithContent('1.pdf', self::PDF)]) ->call('importTerritoryPdfFolder') ->assertDispatched('avviso', fn ($nome, $parametri) => $parametri['tipo'] === 'errore' && str_contains($parametri['testo'], 'ripristino')); Queue::assertNotPushed(ImportTerritoryPdfFolder::class); $this->assertSame('op-restore', app(BackupOperationState::class)->corrente()); } public function test_il_controllo_prima_di_caricare_la_cartella_avvisa_se_il_posto_e_occupato(): void { app(BackupOperationState::class)->initialize('op-backup', BackupOperationState::TIPO_BACKUP, 'Compressione', 10); Livewire::test(XmlExchange::class) ->call('cartellaPuoPartire') ->assertReturned(false) ->assertDispatched('avviso', fn ($nome, $parametri) => $parametri['tipo'] === 'errore'); } public function test_l_import_zip_non_parte_durante_un_azzeramento(): void { Queue::fake(); app(BackupOperationState::class)->initialize('op-reset', BackupOperationState::TIPO_RESET, 'Backup preventivo', 3); $this->postJson(route('imports.territori.pdf-zip'), ['pdfZip' => $this->zip(['1.pdf' => self::PDF])]) ->assertStatus(422) ->assertJsonFragment(['message' => app(BackupOperationState::class)->messaggioOccupato('op-reset')]); Queue::assertNotPushed(ImportTerritoryPdfFolder::class); $this->assertSame('op-reset', app(BackupOperationState::class)->corrente()); } public function test_l_import_xml_non_parte_durante_un_backup_e_non_tocca_i_dati(): void { app(BackupOperationState::class)->initialize('op-backup', BackupOperationState::TIPO_BACKUP, 'Compressione', 10); Territorio::create(['numero' => '99', 'attivo' => true]); $this->post(route('xml.import-xml'), ['xmlImport' => $this->xml('1')]) ->assertSessionHasErrors('xmlImport'); $this->assertSame(['99'], Territorio::pluck('numero')->all()); $this->assertSame('op-backup', app(BackupOperationState::class)->corrente()); } // ─── Un import blocca le operazioni di backup ─────────────── public function test_un_import_pdf_in_coda_blocca_backup_ripristino_e_azzeramento(): void { Queue::fake(); Territorio::create(['numero' => '1', 'attivo' => true]); Livewire::test(XmlExchange::class) ->set('pdfFolder', [UploadedFile::fake()->createWithContent('1.pdf', self::PDF)]) ->call('importTerritoryPdfFolder'); Queue::assertPushed(ImportTerritoryPdfFolder::class); $stato = app(BackupOperationState::class); $import = $stato->corrente(); $this->assertNotNull($import, 'L\'import PDF deve occupare il posto unico finche\' il job non finisce.'); $this->assertSame(BackupOperationState::TIPO_IMPORT_PDF, $stato->get($import)['tipo']); Livewire::test(BackupManager::class) ->call('avviaBackup') ->assertSet('erroreOperazione', $stato->messaggioOccupato($import)); Queue::assertNotPushed(CreateBackupArchive::class); Livewire::test(ZonaPericolosa::class) ->set('confermaReset', ZonaPericolosa::CONFERMA_AZZERAMENTO) ->call('avviaAzzeramento'); Queue::assertNotPushed(CreateBackupArchive::class); $this->postJson(route('backup.restore'), [ 'backupZip' => $this->zip(['manifest.json' => '{}']), ])->assertStatus(409); Queue::assertNotPushed(RestoreBackupArchive::class); $this->assertSame($import, $stato->corrente()); } public function test_un_secondo_import_non_parte_mentre_il_primo_e_in_coda(): void { Queue::fake(); Territorio::create(['numero' => '1', 'attivo' => true]); Livewire::test(XmlExchange::class) ->set('pdfFolder', [UploadedFile::fake()->createWithContent('1.pdf', self::PDF)]) ->call('importTerritoryPdfFolder'); $this->post(route('xml.import-xml'), ['xmlImport' => $this->xml('1')]) ->assertSessionHasErrors('xmlImport'); Queue::assertPushed(ImportTerritoryPdfFolder::class, 1); } // ─── Il posto si libera ───────────────────────────────────── public function test_l_import_pdf_libera_il_posto_quando_finisce(): void { $uno = Territorio::create(['numero' => '1', 'attivo' => true]); Livewire::test(XmlExchange::class) ->set('pdfFolder', [UploadedFile::fake()->createWithContent('1.pdf', self::PDF)]) ->call('importTerritoryPdfFolder') ->assertSet('pdfImportStatus', 'completed'); $this->assertNotNull($uno->fresh()->pdf_path); $this->assertNull(app(BackupOperationState::class)->corrente()); } /** * failed() e' quello che Laravel chiama quando il worker uccide il job per * timeout: handle() non arriva a liberare niente. */ public function test_un_import_pdf_ucciso_dal_worker_libera_il_posto(): void { app(TerritorioPdfImportDispatcher::class)->occupaPosto('import-1'); $this->assertSame('import-1', app(BackupOperationState::class)->corrente()); (new ImportTerritoryPdfFolder('import-1', [], $this->admin->id)) ->failed(new \RuntimeException('Job has timed out.')); $this->assertNull(app(BackupOperationState::class)->corrente()); } public function test_l_import_xml_libera_il_posto_quando_riesce_e_quando_fallisce(): void { $this->post(route('xml.import-xml'), ['xmlImport' => $this->xml('1')]) ->assertSessionHasNoErrors(); $this->assertNull(app(BackupOperationState::class)->corrente()); $this->post(route('xml.import-xml'), [ 'xmlImport' => UploadedFile::fake()->createWithContent('rotto.xml', ''), ])->assertSessionHasErrors('xmlImport'); $this->assertNull(app(BackupOperationState::class)->corrente()); } public function test_uno_zip_senza_pdf_libera_il_posto(): void { // L'estrazione legge lo ZIP da storage_path(), non dal disco finto. Storage::forgetDisk('local'); $this->postJson(route('imports.territori.pdf-zip'), ['pdfZip' => $this->zip(['Thumbs.db' => 'x', '.DS_Store' => 'x'])]) ->assertStatus(422) ->assertJsonFragment(['message' => 'Lo ZIP non contiene file PDF validi.']); $this->assertNull(app(BackupOperationState::class)->corrente()); } // ─── Presa del posto ──────────────────────────────────────── /** * La presa e' un passo solo: chi arriva secondo trova il posto occupato * anche se il primo non ha ancora finito di avviare il proprio lavoro. */ public function test_chi_arriva_secondo_trova_il_posto_occupato(): void { $stato = app(BackupOperationState::class); $this->assertNull($stato->occupa('primo', BackupOperationState::TIPO_IMPORT_PDF, 'In coda')); $this->assertSame('primo', $stato->occupa('secondo', BackupOperationState::TIPO_RESTORE, 'In coda')); $this->assertNull($stato->get('secondo'), 'Chi non prende il posto non deve lasciare un\'operazione fantasma.'); $stato->markCompleted('primo'); $this->assertNull($stato->occupa('terzo', BackupOperationState::TIPO_BACKUP, 'In coda')); } // ─── Supporto ─────────────────────────────────────────────── private function xml(string $numero): UploadedFile { $xml = '' . '' . '' . '' . $numero . '1' . '' . ''; return UploadedFile::fake()->createWithContent('export.xml', $xml); } private function zip(array $voci): UploadedFile { $percorso = tempnam(sys_get_temp_dir(), 'zip-pdf-'); $zip = new ZipArchive(); $zip->open($percorso, ZipArchive::OVERWRITE); foreach ($voci as $nome => $contenuto) { $zip->addFromString($nome, $contenuto); } $zip->close(); return new UploadedFile($percorso, 'territori.zip', 'application/zip', null, true); } }