> endobj\ntrailer << /Root 1 0 R >>\n%%EOF\n"; private User $admin; protected function setUp(): void { parent::setUp(); Storage::fake('public'); // pdftoppm non serve a niente qui, e su un PDF finto fallirebbe comunque. $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); } // ─── Abbinamento nome file → territorio ───────────────────── /** * Il caso segnalato: "1-A" conteneva "1", e senza un territorio 1A il PDF * finiva sul territorio 1 sostituendone la mappa. */ public function test_un_file_1_A_non_sostituisce_il_pdf_del_territorio_1(): void { $uno = $this->territorioConPdf('1'); $stato = $this->importa(['1-A.pdf']); $this->assertSame('territori-pdf/originale-1.pdf', $uno->fresh()->pdf_path); Storage::disk('public')->assertExists('territori-pdf/originale-1.pdf'); $this->assertSame(1, $stato['stats']['skipped']); $this->assertSame('unknown-suffix', $stato['issues'][0]['type']); $this->assertSame(['1'], $stato['issues'][0]['matched_numbers']); } /** * Prima il vecchio PDF si cancellava prima di scrivere il nuovo: un import * sbagliato non si poteva disfare. */ public function test_il_pdf_sostituito_finisce_nello_storico(): void { $uno = $this->territorioConPdf('1'); $this->importa(['1.pdf']); $uno->refresh(); $this->assertNotSame('territori-pdf/originale-1.pdf', $uno->pdf_path); Storage::disk('public')->assertExists($uno->pdf_path); $versione = \App\Models\TerritorioPdfVersione::where('territorio_id', $uno->id)->sole(); $this->assertSame('import', $versione->origine); $this->assertSame('PDF ORIGINALE', Storage::disk('public')->get($versione->pdf_path)); } public function test_un_file_1_A_va_al_territorio_1_A_quando_esiste(): void { $uno = $this->territorioConPdf('1'); $unoA = Territorio::create(['numero' => '1-A', 'attivo' => true]); $this->importa(['1-A.pdf']); $this->assertNotNull($unoA->fresh()->pdf_path); $this->assertSame('territori-pdf/originale-1.pdf', $uno->fresh()->pdf_path); } /** * "12 B", "12B" e "12-b" sono lo stesso numero, e non e' il 12: la lettera * dopo il numero fa parte del numero. */ public function test_12_B_con_lo_spazio_e_il_territorio_12B_e_non_il_12(): void { $dodici = $this->territorioConPdf('12'); $dodiciB = Territorio::create(['numero' => '12B', 'attivo' => true]); $stato = $this->importa(['12 B.pdf']); $this->assertSame(1, $stato['stats']['updated']); $this->assertNotNull($dodiciB->fresh()->pdf_path); $this->assertSame('territori-pdf/originale-12.pdf', $dodici->fresh()->pdf_path); } /** * Due territori che si scrivono diversi ma sono lo stesso numero: il file * che li nomina non si puo' abbinare con certezza, quindi si scarta. */ public function test_due_territori_con_lo_stesso_numero_scritto_diverso_rendono_il_file_ambiguo(): void { $attaccato = Territorio::create(['numero' => '12B', 'attivo' => true]); $staccato = Territorio::create(['numero' => '12 B', 'attivo' => true]); $stato = $this->importa(['12b.pdf']); $this->assertNull($attaccato->fresh()->pdf_path); $this->assertNull($staccato->fresh()->pdf_path); $this->assertSame('ambiguous', $stato['issues'][0]['type']); $this->assertEqualsCanonicalizing(['12B', '12 B'], $stato['issues'][0]['matched_numbers']); } public function test_due_numeri_di_territori_esistenti_nello_stesso_nome_rendono_il_file_ambiguo(): void { $tre = Territorio::create(['numero' => '3', 'attivo' => true]); $dodici = Territorio::create(['numero' => '12', 'attivo' => true]); $stato = $this->importa(['Zona 3 - Territorio 12.pdf']); $this->assertNull($tre->fresh()->pdf_path); $this->assertNull($dodici->fresh()->pdf_path); $this->assertSame('ambiguous', $stato['issues'][0]['type']); } /** Le forme di sempre devono continuare a funzionare. */ public function test_zeri_iniziali_testo_intorno_e_numeri_estranei_non_impediscono_l_abbinamento(): void { $sette = Territorio::create(['numero' => '7', 'attivo' => true]); $dodici = Territorio::create(['numero' => '12', 'attivo' => true]); $stato = $this->importa(['Territorio 007 - Via Roma.pdf', '2024_territorio_12.pdf']); $this->assertSame(2, $stato['stats']['updated']); $this->assertNotNull($sette->fresh()->pdf_path); $this->assertNotNull($dodici->fresh()->pdf_path); } public function test_lo_stesso_territorio_scritto_in_due_modi_nella_stessa_cartella_e_un_doppione(): void { $dodiciB = Territorio::create(['numero' => '12B', 'attivo' => true]); $stato = $this->importa(['12B.pdf', '12 B.pdf']); $this->assertSame(1, $stato['stats']['updated']); $this->assertSame('duplicate-in-batch', $stato['issues'][0]['type']); $this->assertNotNull($dodiciB->fresh()->pdf_path); } // ─── Cartella ─────────────────────────────────────────────── /** * Thumbs.db e simili facevano fallire la validazione `mimes:pdf` di tutta la * cartella: nessun PDF importato per colpa di un file che Windows crea da sé. */ public function test_thumbs_db_e_i_file_nascosti_nella_cartella_non_bloccano_l_import(): void { Storage::fake('local'); $uno = Territorio::create(['numero' => '1', 'attivo' => true]); Livewire::test(XmlExchange::class) ->set('pdfFolder', [ UploadedFile::fake()->createWithContent('1.pdf', self::PDF), UploadedFile::fake()->createWithContent('Thumbs.db', 'non un pdf'), UploadedFile::fake()->createWithContent('desktop.ini', '[.ShellClassInfo]'), UploadedFile::fake()->createWithContent('.DS_Store', 'non un pdf'), ]) ->call('importTerritoryPdfFolder') ->assertHasNoErrors() ->assertSet('pdfImportStatus', 'completed'); $this->assertNotNull($uno->fresh()->pdf_path); Storage::disk('public')->assertExists($uno->fresh()->pdf_path); } public function test_un_pdf_non_valido_nella_cartella_si_scarta_da_solo(): void { Storage::fake('local'); $uno = Territorio::create(['numero' => '1', 'attivo' => true]); $due = Territorio::create(['numero' => '2', 'attivo' => true]); $componente = Livewire::test(XmlExchange::class) ->set('pdfFolder', [ UploadedFile::fake()->createWithContent('1.pdf', self::PDF), // Un'immagine rinominata. Nei test Livewire prende il tipo // dichiarato dal file finto, non dal contenuto: va detto. UploadedFile::fake()->createWithContent('2.pdf', "\x89PNG\r\n\x1a\n" . str_repeat("\0", 64))->mimeType('image/png'), ]) ->call('importTerritoryPdfFolder') ->assertHasNoErrors(); $this->assertNotNull($uno->fresh()->pdf_path); $this->assertNull($due->fresh()->pdf_path); $this->assertSame('invalid-file', $componente->get('pdfImportIssues')[0]['type']); } /** * Con wire:model la cartella partiva in una richiesta sola, e PHP ne teneva * i primi `max_file_uploads` (20) scartando gli altri in silenzio. Il browser * la spedisce a lotti: la pagina deve dirgli quanto grandi, e l'input non * deve avere wire:model. */ public function test_la_cartella_parte_a_lotti_dentro_il_limite_di_php(): void { $this->withoutVite(); $lotto = min(20, (int) ini_get('max_file_uploads')); $this->get(route('xml.exchange')) ->assertOk() ->assertSee('data-file-per-lotto="' . $lotto . '"', false) ->assertSee('uploadMultiple(', false) ->assertDontSee('wire:model="pdfFolder"', false); } /** Lotti aggiunti uno dopo l'altro: il server deve importarli tutti. */ public function test_una_cartella_di_piu_di_venti_pdf_viene_importata_per_intero(): void { Storage::fake('local'); $file = []; for ($numero = 1; $numero <= 25; $numero++) { Territorio::create(['numero' => (string) $numero, 'attivo' => true]); $file[] = UploadedFile::fake()->createWithContent($numero . '.pdf', self::PDF); } Livewire::test(XmlExchange::class) ->set('pdfFolder', $file) ->call('importTerritoryPdfFolder') ->assertHasNoErrors(); $this->assertSame(25, Territorio::whereNotNull('pdf_path')->count()); } // ─── ZIP ──────────────────────────────────────────────────── /** * Uno ZIP fatto su macOS porta con se' __MACOSX/._1.pdf: estensione giusta, * contenuto spazzatura. Arrivando per primo, diventava la mappa del * territorio 1 e il PDF vero veniva scartato come doppione. */ public function test_lo_zip_ignora_file_di_sistema_e_copie_macosx(): void { $uno = Territorio::create(['numero' => '1', 'attivo' => true]); $zip = $this->zip([ '__MACOSX/._1.pdf' => 'risorse di macOS, non un PDF', 'Thumbs.db' => 'non un pdf', 'territori/1.pdf' => self::PDF, ]); $this->postJson(route('imports.territori.pdf-zip'), ['pdfZip' => $zip]) ->assertOk(); $percorso = $uno->fresh()->pdf_path; $this->assertNotNull($percorso); $this->assertSame(self::PDF, Storage::disk('public')->get($percorso)); } // ─── Supporto ─────────────────────────────────────────────── private function territorioConPdf(string $numero): Territorio { $percorso = 'territori-pdf/originale-' . Str::slug($numero) . '.pdf'; Storage::disk('public')->put($percorso, 'PDF ORIGINALE'); return Territorio::create(['numero' => $numero, 'attivo' => true, 'pdf_path' => $percorso]); } /** * Esegue il job su file con i nomi dati, come se fossero arrivati da una * cartella, e restituisce lo stato finale dell'import. */ private function importa(array $nomi): array { Storage::fake('local'); $importId = (string) Str::uuid(); $file = []; foreach (array_values($nomi) as $indice => $nome) { $percorso = 'bulk-territori-imports/' . $importId . '/' . $indice . '.pdf'; Storage::disk('local')->put($percorso, self::PDF); $file[] = ['original_name' => $nome, 'stored_path' => $percorso]; } app(TerritorioPdfImportState::class)->initialize($importId, count($file)); ImportTerritoryPdfFolder::dispatchSync($importId, $file, $this->admin->id); return app(TerritorioPdfImportState::class)->get($importId); } 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); } }