> 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);
}
}