Posta configurabile dall'app, contatti dei proclamatori, conferma email

- Server di posta in Impostazioni (password cifrata, email di prova): se
  compilato prende il posto del .env; non si esporta e l'import XML non lo
  cancella. MAIL_SCHEME=tls nel .env.example non era valido per Laravel 11.
- Proclamatori con email e telefono facoltativi, cifrati, nell'XML e
  cancellati dall'anonimizzazione; il numero apre WhatsApp.
- Dopo l'assegnazione un riquadro in evidenza con il link del PDF,
  "Invia su WhatsApp" con il messaggio pronto e la scelta "inviato /
  non ancora", che resta finché non si sceglie.
- Conferma dell'email obbligatoria per tutti gli utenti, anche esistenti:
  email in italiano, link valido senza login, nuova conferma a ogni cambio
  di indirizzo, "Reinvia conferma" in Utenti. Senza conferma restano
  raggiungibili profilo, cambio password e, per l'admin, il server di
  posta; comando utenti:conferma-email per l'emergenza.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 17:12:31 +02:00
parent e7148c7b32
commit fc8623f7db
35 changed files with 1737 additions and 14 deletions

View File

@@ -0,0 +1,210 @@
<?php
namespace Tests\Feature;
use App\Livewire\Assegnazioni\Assegna;
use App\Livewire\Proclamatori\ProclamatoreCreate;
use App\Livewire\Settings\XmlExchange;
use App\Models\AnnoTeocratico;
use App\Models\Assegnazione;
use App\Models\Proclamatore;
use App\Models\Setting;
use App\Models\Territorio;
use App\Models\User;
use Database\Seeders\RolesAndPermissionsSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Livewire\Livewire;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
/**
* Email e telefono dei proclamatori (facoltativi), WhatsApp, e il riquadro
* con il link da mandare subito dopo l'assegnazione.
*/
class ContattiProclamatoriTest extends TestCase
{
use RefreshDatabase;
private User $admin;
protected function setUp(): void
{
parent::setUp();
$this->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_compare_il_link_da_mandare_su_whatsapp(): void
{
$mario = Proclamatore::create(['nome' => 'Mario', 'cognome' => 'Rossi', 'attivo' => true, 'telefono' => '333 1234567']);
$territorio = $this->territorioConPdf();
$this->assegna($mario, $territorio)
->assertHasNoErrors()
->assertRedirect(route('assegnazioni.assegna'));
$assegnazione = Assegnazione::sole();
$this->assertSame([$assegnazione->id], session(Assegna::DA_INVIARE));
$link = $assegnazione->fresh()->shortPdfUrl();
Livewire::actingAs($this->admin)->test(Assegna::class)
->assertSee('Territorio N° 7 assegnato a Rossi Mario')
->assertSee($link)
->assertSee('https://wa.me/393331234567?text=', false)
->assertSee(rawurlencode($link), false)
->assertSee('Il link è stato inviato?');
}
public function test_la_scelta_registra_l_invio_e_toglie_il_riquadro(): void
{
$mario = Proclamatore::create(['nome' => 'Mario', 'cognome' => 'Rossi', 'attivo' => true, 'telefono' => '3331234567']);
$anna = Proclamatore::create(['nome' => 'Anna', 'cognome' => 'Verdi', 'attivo' => true]);
$this->assegna($mario, $this->territorioConPdf('7'));
$this->assegna($anna, $this->territorioConPdf('8'));
[$prima, $seconda] = Assegnazione::orderBy('id')->get()->all();
Livewire::actingAs($this->admin)->test(Assegna::class)
->assertSee('Territorio N° 7 assegnato')
->assertSee('Territorio N° 8 assegnato')
->call('esitoInvio', $prima->id, true)
->assertDontSee('Territorio N° 7 assegnato')
->assertSee('Territorio N° 8 assegnato')
->call('esitoInvio', $seconda->id, false)
->assertDontSee('Territorio N° 8 assegnato');
$this->assertTrue((bool) $prima->fresh()->link_sent);
$this->assertFalse((bool) $seconda->fresh()->link_sent);
$this->assertSame([], session(Assegna::DA_INVIARE));
}
public function test_senza_telefono_il_riquadro_lo_dice(): void
{
$anna = Proclamatore::create(['nome' => 'Anna', 'cognome' => 'Verdi', 'attivo' => true]);
$this->assegna($anna, $this->territorioConPdf());
Livewire::actingAs($this->admin)->test(Assegna::class)
->assertSee('non ha un numero di telefono')
->assertDontSee('wa.me', false);
}
}