withoutVite(); Storage::fake('public'); $this->seed(RolesAndPermissionsSeeder::class); Setting::firstOrCreate([], ['congregazione_nome' => 'Congregazione di prova']); AnnoTeocratico::create(['label' => '2026-2027', 'start_date' => '2026-09-01', 'end_date' => '2027-08-31']); $this->admin = User::create(['name' => 'Admin', 'email' => 'admin@example.test', 'password' => 'password-di-prova']); $this->admin->assignRole('amministratore'); } // ─── Contatti ─────────────────────────────────────────────── public function test_email_e_telefono_sono_facoltativi_e_cifrati(): void { Livewire::actingAs($this->admin)->test(ProclamatoreCreate::class) ->set('nome', 'Mario')->set('cognome', 'Rossi') ->call('save') ->assertHasNoErrors(); Livewire::actingAs($this->admin)->test(ProclamatoreCreate::class) ->set('nome', 'Anna')->set('cognome', 'Verdi') ->set('email', 'anna@example.test') ->set('telefono', '333 123 4567') ->call('save') ->assertHasNoErrors(); $anna = Proclamatore::all()->first(fn ($p) => $p->nome === 'Anna'); $this->assertSame('anna@example.test', $anna->email); $this->assertSame('333 123 4567', $anna->telefono); $riga = DB::table('proclamatori')->where('id', $anna->id)->first(); $this->assertStringNotContainsString('anna@', $riga->email); $this->assertStringNotContainsString('333', $riga->telefono); } public function test_un_email_sbagliata_si_segnala_ma_si_puo_lasciare_vuota(): void { Livewire::actingAs($this->admin)->test(ProclamatoreCreate::class) ->set('nome', 'Mario')->set('cognome', 'Rossi') ->set('email', 'non-una-email') ->call('save') ->assertHasErrors('email'); $this->assertSame(0, Proclamatore::count()); } #[DataProvider('numeri')] public function test_il_numero_diventa_internazionale_per_whatsapp(string $scritto, ?string $atteso): void { $p = new Proclamatore(['nome' => 'A', 'cognome' => 'B', 'telefono' => $scritto]); $this->assertSame($atteso, $p->telefonoInternazionale()); $this->assertSame($atteso ? 'https://wa.me/' . $atteso : null, $p->whatsappUrl()); } public static function numeri(): array { return [ 'cellulare italiano' => ['333 123 4567', '393331234567'], 'con +39' => ['+39 333 1234567', '393331234567'], 'con 0039' => ['0039 3331234567', '393331234567'], '39 senza +' => ['393331234567', '393331234567'], 'cellulare che inizia con 39' => ['392 1234567', '393921234567'], 'fisso italiano' => ['06 12345678', '390612345678'], 'estero' => ['+44 7911 123456', '447911123456'], 'troppo corto' => ['123', null], 'vuoto' => ['', null], ]; } public function test_il_messaggio_whatsapp_contiene_il_link(): void { $p = new Proclamatore(['nome' => 'A', 'cognome' => 'B', 'telefono' => '3331234567']); $this->assertSame( 'https://wa.me/393331234567?text=' . rawurlencode('Ecco: https://x.test/p/AB12'), $p->whatsappUrl('Ecco: https://x.test/p/AB12'), ); } public function test_anonimizzare_cancella_anche_i_contatti(): void { $p = Proclamatore::create(['nome' => 'Mario', 'cognome' => 'Rossi', 'attivo' => true, 'email' => 'm@example.test', 'telefono' => '3331234567']); $p->anonimizza(); $p = Proclamatore::withTrashed()->find($p->id); $this->assertNull($p->email); $this->assertNull($p->telefono); } public function test_l_xml_porta_con_se_i_contatti(): void { $this->actingAs($this->admin); Proclamatore::create(['nome' => 'Mario', 'cognome' => 'Rossi', 'attivo' => true, 'email' => 'm@example.test', 'telefono' => '3331234567']); $xml = (new XmlExchange())->currentXmlBackup(); $this->assertArrayNotHasKey('error', (new XmlExchange())->importXmlFromContent($xml)); $p = Proclamatore::sole(); $this->assertSame('m@example.test', $p->email); $this->assertSame('3331234567', $p->telefono); } // ─── Dopo l'assegnazione ──────────────────────────────────── private function assegna(Proclamatore $proclamatore, Territorio $territorio) { return Livewire::actingAs($this->admin)->test(Assegna::class) ->set('territorio_id', $territorio->id) ->set('proclamatore_id', $proclamatore->id) ->set('assigned_at', now()->format('Y-m-d')) ->call('save'); } private function territorioConPdf(string $numero = '7'): Territorio { Storage::disk('public')->put("territori-pdf/t{$numero}.pdf", '%PDF-1.4'); return Territorio::create(['numero' => $numero, 'attivo' => true, 'pdf_path' => "territori-pdf/t{$numero}.pdf"]); } public function test_dopo_l_assegnazione_non_si_va_avanti_ma_si_sceglie_il_canale(): void { $mario = Proclamatore::create(['nome' => 'Mario', 'cognome' => 'Rossi', 'attivo' => true, 'telefono' => '333 1234567']); $pagina = $this->assegna($mario, $this->territorioConPdf()) ->assertHasNoErrors() ->assertNoRedirect(); $assegnazione = Assegnazione::sole(); $this->assertTrue($assegnazione->link_da_inviare); $this->assertFalse($assegnazione->link_sent); $link = $assegnazione->shortPdfUrl(); $pagina->assertSet('invioId', $assegnazione->id) ->assertSee('Territorio N° 7 assegnato a Rossi Mario') ->assertSee('Come vuoi mandare il link della piantina?') ->assertSee($link) ->assertSee('https://wa.me/393331234567?text=', false) ->assertSee(rawurlencode($link), false) ->assertSee('Hai inviato il link?'); } public function test_si_dice_inviato_solo_dopo_aver_scelto_un_canale(): void { $mario = Proclamatore::create(['nome' => 'Mario', 'cognome' => 'Rossi', 'attivo' => true, 'telefono' => '3331234567']); $this->assegna($mario, $this->territorioConPdf()) ->call('concludiInvio', true) ->assertNoRedirect() ->assertSet('erroreInvio', 'Scegli prima come mandare il link.') ->call('canale', 'whatsapp') ->call('concludiInvio', true) ->assertRedirect(route('assegnazioni.assegna')); $assegnazione = Assegnazione::sole(); $this->assertTrue($assegnazione->link_sent); $this->assertFalse($assegnazione->link_da_inviare); $this->assertSame('whatsapp', $assegnazione->link_canale); $log = \Spatie\Activitylog\Models\Activity::where('description', 'link_inviato')->sole(); $this->assertSame('whatsapp', $log->properties['canale']); $this->assertSame($mario->id, $log->properties['proclamatore_id']); $this->assertStringNotContainsString('333', json_encode($log->properties)); } public function test_senza_contatti_resta_solo_copia_link(): void { $anna = Proclamatore::create(['nome' => 'Anna', 'cognome' => 'Verdi', 'attivo' => true]); $this->assegna($anna, $this->territorioConPdf()) ->assertSee('Copia link') ->assertSee('nel profilo non c\'è un numero di telefono') ->assertSee('nel profilo non c\'è un indirizzo email') ->assertDontSee('wa.me', false) // WhatsApp senza numero non si può segnare come canale usato. ->call('canale', 'whatsapp') ->assertSet('canaleUsato', null) ->call('canale', 'copia') ->assertSet('canaleUsato', 'copia'); } public function test_l_email_parte_solo_con_indirizzo_e_posta_configurata(): void { Notification::fake(); $anna = Proclamatore::create(['nome' => 'Anna', 'cognome' => 'Verdi', 'attivo' => true, 'email' => 'anna@example.test']); // Posta non configurata: il canale c'è ma è spento, e non parte nulla. $pagina = $this->assegna($anna, $this->territorioConPdf()) ->assertSee('il server di posta non è configurato') ->call('inviaEmail') ->assertSet('canaleUsato', null); Notification::assertNothingSent(); // Posta configurata dall'app: l'email parte, subito e non in coda. Setting::instance()->update(['mail_host' => 'smtp.example.test', 'mail_port' => 587, 'mail_from_address' => 'noreply@example.test']); $pagina->call('inviaEmail')->assertSet('canaleUsato', 'email'); Notification::assertSentOnDemand(LinkTerritorio::class, function ($notifica, $canali, $destinatario) { return $destinatario->routes['mail'] === 'anna@example.test'; }); } public function test_se_non_si_conclude_l_invio_l_assegnazione_lo_segnala(): void { $mario = Proclamatore::create(['nome' => 'Mario', 'cognome' => 'Rossi', 'attivo' => true]); $this->assegna($mario, $this->territorioConPdf()); $assegnazione = Assegnazione::sole(); // La pagina viene chiusa senza rispondere: l'invio è perso, l'avviso resta. Livewire::actingAs($this->admin)->test(Assegna::class) ->assertSet('invioId', null) ->assertSee('Link ancora da inviare') ->call('apriInvio', $assegnazione->id) ->assertSee('Hai inviato il link?') ->call('concludiInvio', false) ->assertRedirect(route('assegnazioni.assegna')); // "No, non ancora": la procedura è finita, ma il link resta da inviare. $this->assertTrue($assegnazione->fresh()->link_in_sospeso); $this->get(route('territori.show', $assegnazione->territorio_id)) ->assertSee('Il link della piantina è ancora da inviare'); // Segnato come inviato dall'elenco: l'avviso sparisce. Livewire::actingAs($this->admin)->test(Assegna::class) ->call('toggleLinkSent', $assegnazione->id) ->assertDontSee('Link ancora da inviare'); $this->assertFalse($assegnazione->fresh()->link_in_sospeso); } public function test_dalla_scheda_territorio_si_riprende_l_invio(): void { $mario = Proclamatore::create(['nome' => 'Mario', 'cognome' => 'Rossi', 'attivo' => true]); $this->assegna($mario, $this->territorioConPdf()); $assegnazione = Assegnazione::sole(); $this->actingAs($this->admin) ->get(route('assegnazioni.assegna', ['invia' => $assegnazione->id])) ->assertOk() ->assertSee('Hai inviato il link?'); } public function test_senza_pdf_non_c_e_un_link_da_inviare(): void { $anna = Proclamatore::create(['nome' => 'Anna', 'cognome' => 'Verdi', 'attivo' => true]); $territorio = Territorio::create(['numero' => '9', 'attivo' => true]); $this->assegna($anna, $territorio) ->assertSee('non ha un PDF') ->call('concludiInvio', false); $this->assertFalse(Assegnazione::sole()->link_in_sospeso); } public function test_le_assegnazioni_gia_esistenti_non_vengono_segnalate(): void { $mario = Proclamatore::create(['nome' => 'Mario', 'cognome' => 'Rossi', 'attivo' => true]); $territorio = $this->territorioConPdf(); $vecchia = Assegnazione::create([ 'territorio_id' => $territorio->id, 'proclamatore_id' => $mario->id, 'anno_teocratico_id' => AnnoTeocratico::sole()->id, 'assigned_at' => now(), 'created_by' => $this->admin->id, ]); $this->assertFalse($vecchia->fresh()->link_in_sospeso); } public function test_l_xml_porta_con_se_lo_stato_dell_invio(): void { $this->actingAs($this->admin); $mario = Proclamatore::create(['nome' => 'Mario', 'cognome' => 'Rossi', 'attivo' => true]); $this->assegna($mario, $this->territorioConPdf())->call('canale', 'copia')->call('concludiInvio', true); $xml = (new XmlExchange())->currentXmlBackup(); $this->assertArrayNotHasKey('error', (new XmlExchange())->importXmlFromContent($xml)); $assegnazione = Assegnazione::sole(); $this->assertTrue($assegnazione->link_sent); $this->assertFalse($assegnazione->link_da_inviare); $this->assertSame('copia', $assegnazione->link_canale); } // ─── Nuovo territorio dal designer ────────────────────────── public function test_nuovo_territorio_porta_al_designer_delle_piantine(): void { $this->actingAs($this->admin) ->get(route('territori.index')) ->assertSee(route('piantine.editor', ['nuovo' => 1]), false); } }