- Il disegno è bozza e si salva da solo; i dati del territorio si modificano dall'editor e valgono subito nel gestionale. - Un solo "Pubblica" (immagine + PDF), uguale in editor e gestione: anteprima preparata in coda su disco privato, conferma obbligatoria, rifiutata se nel frattempo è cambiato qualcosa di stampato. - Stati Bozza / Pubblicata / Modifiche da pubblicare, calcolati sull'impronta di confine e dati stampati e sul PDF in uso. - Gestione riscritta: stato, ricerca, filtri, storico paginato. - Editor ridisegnato a tre zone, utilizzabile anche su tablet e telefono. - Avvisi via evento (trait Avvisa + <x-avvisi>). - .gitattributes: script shell sempre LF, altrimenti il container locale su Windows non parte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
287 lines
11 KiB
PHP
287 lines
11 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Livewire\Piantine\Pubblicazione;
|
|
use App\Models\Setting;
|
|
use App\Models\Territorio;
|
|
use App\Models\TerritorioMappa;
|
|
use App\Models\TerritorioPdfVersione;
|
|
use App\Models\User;
|
|
use App\Models\Zona;
|
|
use App\Services\Piantine\PubblicazionePiantina;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Livewire\Livewire;
|
|
use RuntimeException;
|
|
use Spatie\Permission\Models\Role;
|
|
use Spatie\Permission\PermissionRegistrar;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* Dalla bozza al PDF ufficiale: l'anteprima non tocca niente, la conferma
|
|
* pubblica esattamente quello che si è visto, o niente.
|
|
*/
|
|
class PubblicazionePiantinaTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private const GEOMETRIA = [
|
|
'type' => 'Polygon',
|
|
'coordinates' => [[[13.35, 38.11], [13.37, 38.11], [13.37, 38.13], [13.35, 38.11]]],
|
|
];
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->withoutVite();
|
|
Storage::fake('public');
|
|
Storage::fake('local');
|
|
config(['piantine.formato' => 'jpeg']);
|
|
|
|
// Un JPEG vero: FPDF lo legge davvero quando compone il PDF.
|
|
$immagine = imagecreatetruecolor(80, 50);
|
|
imagefilledrectangle($immagine, 0, 0, 79, 49, imagecolorallocate($immagine, 230, 230, 230));
|
|
ob_start();
|
|
imagejpeg($immagine);
|
|
$jpeg = ob_get_clean();
|
|
imagedestroy($immagine);
|
|
|
|
$this->jpeg = $jpeg;
|
|
}
|
|
|
|
private string $jpeg;
|
|
|
|
private function rendererRisponde(): void
|
|
{
|
|
Http::fake(['*/render' => Http::response($this->jpeg)]);
|
|
}
|
|
|
|
public function test_l_anteprima_si_prepara_senza_toccare_territorio_ne_piantina(): void
|
|
{
|
|
[$territorio, $mappa] = $this->piantinaCollegata();
|
|
$servizio = app(PubblicazionePiantina::class);
|
|
|
|
// In test la coda è sincrona: il job ha già lavorato al ritorno.
|
|
$token = $servizio->avvia($mappa);
|
|
|
|
$this->assertSame(PubblicazionePiantina::PRONTA, $servizio->stato($token)['stato']);
|
|
$this->assertStringStartsWith('%PDF', file_get_contents($servizio->pdfAnteprima($token)));
|
|
|
|
$this->assertNull($territorio->fresh()->pdf_path);
|
|
$this->assertNull($mappa->fresh()->pubblicata_at);
|
|
$this->assertNull($mappa->fresh()->image_path);
|
|
$this->assertSame(TerritorioMappa::STATO_BOZZA, $mappa->fresh()->stato());
|
|
}
|
|
|
|
public function test_il_pdf_dell_anteprima_si_vede_solo_con_il_token_giusto(): void
|
|
{
|
|
[, $mappa] = $this->piantinaCollegata();
|
|
$token = app(PubblicazionePiantina::class)->avvia($mappa);
|
|
|
|
$this->actingAs($this->admin())
|
|
->get(route('piantine.anteprima', $token))
|
|
->assertOk()
|
|
->assertHeader('Content-Type', 'application/pdf');
|
|
|
|
$this->actingAs($this->admin())
|
|
->get(route('piantine.anteprima', str_repeat('a', 40)))
|
|
->assertNotFound();
|
|
}
|
|
|
|
public function test_la_conferma_sostituisce_il_pdf_e_archivia_il_precedente(): void
|
|
{
|
|
[$territorio, $mappa] = $this->piantinaCollegata();
|
|
Storage::disk('public')->put('territori-pdf/vecchio.pdf', '%PDF-vecchio');
|
|
$territorio->update(['pdf_path' => 'territori-pdf/vecchio.pdf']);
|
|
|
|
$servizio = app(PubblicazionePiantina::class);
|
|
$token = $servizio->avvia($mappa);
|
|
|
|
$this->actingAs($this->admin());
|
|
$servizio->conferma($token);
|
|
|
|
$territorio->refresh();
|
|
$mappa->refresh();
|
|
|
|
$this->assertStringStartsWith('territori-pdf/piantina-' . $territorio->id . '-', $territorio->pdf_path);
|
|
Storage::disk('public')->assertExists($territorio->pdf_path);
|
|
$this->assertSame(1, TerritorioPdfVersione::where('territorio_id', $territorio->id)->count());
|
|
|
|
$this->assertNotNull($mappa->image_path);
|
|
Storage::disk('public')->assertExists($mappa->image_path);
|
|
$this->assertSame(TerritorioMappa::STATO_PUBBLICATA, $mappa->stato());
|
|
$this->assertSame($mappa->geojson, $mappa->geojson_pubblicato);
|
|
|
|
// L'anteprima confermata non resta in giro.
|
|
$this->assertNull($servizio->stato($token));
|
|
$this->assertSame([], Storage::disk('local')->files('piantine-anteprime'));
|
|
}
|
|
|
|
public function test_se_la_piantina_cambia_dopo_l_anteprima_la_conferma_viene_rifiutata(): void
|
|
{
|
|
[$territorio, $mappa] = $this->piantinaCollegata();
|
|
$servizio = app(PubblicazionePiantina::class);
|
|
$token = $servizio->avvia($mappa);
|
|
|
|
// I confini sono stampati sulla cartolina: cambiarli rende vecchia l'anteprima.
|
|
$territorio->update(['confini' => 'Nord: via Nuova']);
|
|
|
|
try {
|
|
$servizio->conferma($token);
|
|
$this->fail('La conferma doveva essere rifiutata.');
|
|
} catch (RuntimeException $e) {
|
|
$this->assertStringContainsString('cambiat', $e->getMessage());
|
|
}
|
|
|
|
$this->assertNull($territorio->fresh()->pdf_path);
|
|
$this->assertNull($mappa->fresh()->pubblicata_at);
|
|
}
|
|
|
|
public function test_una_piantina_scollegata_non_si_pubblica(): void
|
|
{
|
|
$mappa = TerritorioMappa::create(['geojson' => json_encode(self::GEOMETRIA)]);
|
|
|
|
$this->expectException(RuntimeException::class);
|
|
app(PubblicazionePiantina::class)->avvia($mappa);
|
|
}
|
|
|
|
public function test_un_renderer_che_non_risponde_lascia_un_errore_leggibile(): void
|
|
{
|
|
Http::fake(['*/render' => Http::response(['error' => 'area troppo estesa'], 413)]);
|
|
[, $mappa] = $this->piantinaCollegata(renderer: false);
|
|
|
|
$servizio = app(PubblicazionePiantina::class);
|
|
$token = $servizio->avvia($mappa);
|
|
|
|
$stato = $servizio->stato($token);
|
|
$this->assertSame(PubblicazionePiantina::ERRORE, $stato['stato']);
|
|
$this->assertStringContainsString('area troppo estesa', $stato['messaggio']);
|
|
$this->assertNull($servizio->pdfAnteprima($token));
|
|
}
|
|
|
|
public function test_annullare_butta_l_anteprima(): void
|
|
{
|
|
[, $mappa] = $this->piantinaCollegata();
|
|
$servizio = app(PubblicazionePiantina::class);
|
|
$token = $servizio->avvia($mappa);
|
|
|
|
$servizio->annulla($token);
|
|
|
|
$this->assertNull($servizio->stato($token));
|
|
$this->assertSame([], Storage::disk('local')->files('piantine-anteprime'));
|
|
}
|
|
|
|
public function test_la_finestra_porta_dalla_richiesta_alla_conferma(): void
|
|
{
|
|
[$territorio, $mappa] = $this->piantinaCollegata();
|
|
|
|
Livewire::actingAs($this->admin())
|
|
->test(Pubblicazione::class)
|
|
->dispatch('pubblica-piantina', mappaId: $mappa->id)
|
|
->assertSet('fase', PubblicazionePiantina::PRONTA)
|
|
->assertSee('Anteprima del PDF da pubblicare', false)
|
|
->call('conferma')
|
|
->assertSet('mappaId', null)
|
|
->assertDispatched('piantina-pubblicata', mappaId: $mappa->id);
|
|
|
|
$this->assertSame(TerritorioMappa::STATO_PUBBLICATA, $mappa->fresh()->stato());
|
|
$this->assertNotNull($territorio->fresh()->pdf_path);
|
|
}
|
|
|
|
public function test_chiudere_la_finestra_non_pubblica_niente(): void
|
|
{
|
|
[$territorio, $mappa] = $this->piantinaCollegata();
|
|
|
|
Livewire::actingAs($this->admin())
|
|
->test(Pubblicazione::class)
|
|
->dispatch('pubblica-piantina', mappaId: $mappa->id)
|
|
->call('chiudi')
|
|
->assertSet('mappaId', null);
|
|
|
|
$this->assertNull($territorio->fresh()->pdf_path);
|
|
$this->assertSame([], Storage::disk('local')->files('piantine-anteprime'));
|
|
}
|
|
|
|
// ─── Stati del modello ──────────────────────────────────────────────────
|
|
|
|
public function test_dopo_la_pubblicazione_ogni_dato_stampato_riporta_la_piantina_da_pubblicare(): void
|
|
{
|
|
[$territorio, $mappa] = $this->piantinaCollegata();
|
|
$servizio = app(PubblicazionePiantina::class);
|
|
$this->actingAs($this->admin());
|
|
$servizio->conferma($servizio->avvia($mappa));
|
|
|
|
$this->assertSame(TerritorioMappa::STATO_PUBBLICATA, $mappa->fresh()->stato());
|
|
|
|
$territorio->update(['zona_id' => Zona::create(['nome' => 'Altra', 'attivo' => true])->id]);
|
|
$this->assertSame(TerritorioMappa::STATO_MODIFICATA, $mappa->fresh()->stato());
|
|
}
|
|
|
|
public function test_un_pdf_sostituito_per_altre_vie_riporta_la_piantina_da_pubblicare(): void
|
|
{
|
|
[$territorio, $mappa] = $this->piantinaCollegata();
|
|
$servizio = app(PubblicazionePiantina::class);
|
|
$this->actingAs($this->admin());
|
|
$servizio->conferma($servizio->avvia($mappa));
|
|
|
|
$territorio->update(['pdf_path' => 'territori-pdf/caricato-a-mano.pdf']);
|
|
|
|
$this->assertSame(TerritorioMappa::STATO_MODIFICATA, $mappa->fresh()->stato());
|
|
}
|
|
|
|
public function test_motivo_non_pubblicabile_dice_cosa_manca(): void
|
|
{
|
|
$senzaDisegno = TerritorioMappa::create(['geojson' => 'rotto']);
|
|
$this->assertStringContainsString('Disegna', $senzaDisegno->motivoNonPubblicabile());
|
|
|
|
$scollegata = TerritorioMappa::create(['geojson' => json_encode(self::GEOMETRIA)]);
|
|
$this->assertStringContainsString('Collega', $scollegata->motivoNonPubblicabile());
|
|
|
|
[$territorio, $mappa] = $this->piantinaCollegata();
|
|
$this->assertNull($mappa->motivoNonPubblicabile());
|
|
|
|
$territorio->delete();
|
|
$this->assertStringContainsString('cestino', $mappa->fresh()->motivoNonPubblicabile());
|
|
$this->assertSame('Terr. 7', $mappa->fresh()->etichetta());
|
|
}
|
|
|
|
private function piantinaCollegata(bool $renderer = true): array
|
|
{
|
|
if ($renderer) {
|
|
$this->rendererRisponde();
|
|
}
|
|
|
|
$zona = Zona::firstOrCreate(['nome' => 'Centro'], ['attivo' => true]);
|
|
$territorio = Territorio::create([
|
|
'numero' => '7',
|
|
'attivo' => true,
|
|
'zona_id' => $zona->id,
|
|
'confini' => 'Nord: via Roma',
|
|
]);
|
|
$mappa = TerritorioMappa::create([
|
|
'territorio_id' => $territorio->id,
|
|
'geojson' => json_encode(self::GEOMETRIA),
|
|
]);
|
|
|
|
return [$territorio, $mappa];
|
|
}
|
|
|
|
private function admin(): User
|
|
{
|
|
Role::firstOrCreate(['name' => 'amministratore', 'guard_name' => 'web']);
|
|
app(PermissionRegistrar::class)->forgetCachedPermissions();
|
|
|
|
Setting::firstOrCreate([], ['congregazione_nome' => 'Congregazione di prova']);
|
|
|
|
$utente = User::firstOrCreate(
|
|
['email' => 'admin@example.test'],
|
|
['name' => 'Amministratore', 'password' => 'password-di-prova'],
|
|
);
|
|
$utente->assignRole('amministratore');
|
|
|
|
return $utente;
|
|
}
|
|
}
|