withoutVite(); $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->travelTo(Carbon::parse('2026-10-21 10:00')); $this->admin = $this->utente('Admin', 'admin@example.test', 'amministratore'); RateLimiter::clear('richiesta-pubblica:' . sha1('127.0.0.1')); } private function utente(string $nome, string $email, string $ruolo): User { $utente = User::create(['name' => $nome, 'email' => $email, 'password' => 'password-di-prova']); $utente->assignRole($ruolo); return $utente; } private function proclamatore(string $cognome, string $nome): Proclamatore { return Proclamatore::create(['nome' => $nome, 'cognome' => $cognome, 'attivo' => true]); } private function territorio(string $numero): Territorio { return Territorio::create(['numero' => $numero, 'attivo' => true]); } private function assegna(Territorio $t, Proclamatore $p, string $dal, bool $cartaceo = false): Assegnazione { return Assegnazione::create([ 'territorio_id' => $t->id, 'proclamatore_id' => $p->id, 'anno_teocratico_id' => AnnoTeocratico::perData(Carbon::parse($dal))->id, 'assigned_at' => $dal, 'cartaceo' => $cartaceo, ]); } /** Il modulo compilato da una persona: aperto, qualche secondo, inviato. */ private function compila(array $campi): \Livewire\Features\SupportTesting\Testable { $modulo = Livewire::test(ModuloRichiesta::class); foreach ($campi as $campo => $valore) { $modulo->set($campo, $valore); } $this->travel(ModuloRichiesta::SECONDI_MINIMI + 2)->seconds(); return $modulo->call('invia'); } private function postaFunzionante(): void { Setting::instance()->update(['mail_host' => 'smtp.esempio.it']); Setting::flushCache(); } // ─── Il modulo pubblico ───────────────────────────────────── public function test_il_modulo_si_apre_senza_login_e_non_mostra_dati(): void { $this->proclamatore('Rossi', 'Mario'); auth()->logout(); $this->get(route('richiesta')) ->assertOk() ->assertSee('Invia la richiesta') ->assertDontSee('Rossi'); } public function test_una_richiesta_si_salva_cifrata_e_avvisa_chi_la_gestisce(): void { Notification::fake(); $this->postaFunzionante(); $assistente = $this->utente('Assistente', 'assistente@example.test', 'assistente'); $operatore = $this->utente('Operatore', 'operatore@example.test', 'operatore'); $nonConfermato = $this->utente('Altro', 'altro@example.test', 'amministratore'); $nonConfermato->forceFill(['email_verified_at' => null])->save(); auth()->logout(); $this->compila(['tipo' => Richiesta::NUOVO, 'nome' => 'Mario Rossi', 'note' => 'Vicino a casa']) ->assertHasNoErrors() ->assertSet('inviata', true); $r = Richiesta::sole(); $this->assertSame('Mario Rossi', $r->nome); $this->assertSame(Richiesta::APERTA, $r->stato); $this->assertNull($r->numero_territorio); $grezzo = DB::table('richieste')->value('nome'); $this->assertStringNotContainsString('Rossi', $grezzo, 'Il nome nel database è cifrato'); $this->assertStringNotContainsString('Rossi', Activity::all()->toJson(), 'Il nome non va nel registro'); Notification::assertSentTo([$this->admin, $assistente], RichiestaRicevuta::class); Notification::assertNotSentTo([$operatore, $nonConfermato], RichiestaRicevuta::class); } public function test_senza_posta_la_richiesta_resta_comunque(): void { Notification::fake(); auth()->logout(); $this->compila(['tipo' => Richiesta::RICONSEGNA, 'nome' => 'Mario Rossi', 'numero' => '12'])->assertHasNoErrors(); $this->assertSame('12', Richiesta::sole()->numero_territorio); Notification::assertNothingSent(); } public function test_la_riconsegna_vuole_il_numero_del_territorio(): void { $this->compila(['tipo' => Richiesta::RICONSEGNA, 'nome' => 'Mario Rossi'])->assertHasErrors('numero'); $this->compila(['tipo' => Richiesta::NUOVO, 'nome' => 'Ma'])->assertHasErrors('nome'); $this->assertSame(0, Richiesta::count()); } public function test_lo_spam_non_si_salva_e_non_se_ne_accorge(): void { // Il campo trappola riempito. $this->compila(['nome' => 'Spam Bot', 'sito' => 'https://spam.example'])->assertSet('inviata', true); // Inviato subito dopo l'apertura. Livewire::test(ModuloRichiesta::class)->set('nome', 'Spam Bot')->call('invia')->assertSet('inviata', true); $this->assertSame(0, Richiesta::count()); } public function test_al_massimo_cinque_richieste_all_ora_dallo_stesso_indirizzo(): void { for ($i = 1; $i <= ModuloRichiesta::MASSIMO_ORA; $i++) { $this->compila(['nome' => "Mario Rossi {$i}"])->assertHasNoErrors(); } $this->compila(['nome' => 'Mario Rossi 6'])->assertHasErrors('nome'); $this->assertSame(ModuloRichiesta::MASSIMO_ORA, Richiesta::count()); } // ─── I nomi simili ────────────────────────────────────────── public function test_i_nomi_simili_trovano_il_proclamatore_anche_scritto_male(): void { $rossi = $this->proclamatore('Rossi', 'Mario'); $rossini = $this->proclamatore('Rossini', 'Maria'); $this->proclamatore('Bianchi', 'Luca'); $servizio = app(RichiesteServizio::class); $this->assertSame([$rossi->id, $rossini->id], $servizio->nomiSimili('mario rossi')->pluck('id')->all()); $this->assertSame($rossi->id, $servizio->nomiSimili('Rosi Mario')->first()?->id, 'Una lettera in meno'); $this->assertSame($rossi->id, $servizio->nomiSimili('Màrio Rossi')->first()?->id, 'Accenti'); $this->assertTrue($servizio->nomiSimili('Giuseppe Verdi')->isEmpty()); } // ─── La Home ──────────────────────────────────────────────── public function test_la_home_mostra_le_richieste_con_i_nomi_simili_e_i_collegamenti(): void { $rossi = $this->proclamatore('Rossi', 'Mario'); $nuova = app(RichiesteServizio::class)->registra(Richiesta::NUOVO, 'Mario Rosi', null, 'Anche cartaceo'); $t = $this->territorio('12'); $a = $this->assegna($t, $this->proclamatore('Verdi', 'Anna'), '2026-10-01'); $riconsegna = app(RichiesteServizio::class)->registra(Richiesta::RICONSEGNA, 'Anna Verdi', '12', null); app(RichiesteServizio::class)->registra(Richiesta::RICONSEGNA, 'Luca Neri', '99', null); Livewire::actingAs($this->admin)->test(Home::class) ->assertSee('Richieste (3)') ->assertSee('Mario Rosi') ->assertSee('Anche cartaceo') ->assertSee(route('assegnazioni.assegna', ['proclamatore' => $rossi->id, 'richiesta' => $nuova->id, 'dalla_home' => 1])) ->assertSee('Nessuno di questi: apri il modulo') ->assertSee(route('assegnazioni.rientra', ['assegnazione' => $a->id, 'richiesta' => $riconsegna->id])) ->assertSee('Il territorio N° 99 non risulta assegnato.'); } public function test_solo_chi_ha_il_permesso_vede_e_scarta_le_richieste(): void { $r = app(RichiesteServizio::class)->registra(Richiesta::NUOVO, 'Mario Rossi', null, null); $operatore = $this->utente('Operatore', 'operatore@example.test', 'operatore'); Livewire::actingAs($operatore)->test(Home::class) ->assertDontSee('Mario Rossi') ->call('scartaRichiesta', $r->id) ->assertForbidden(); $this->assertTrue($r->fresh()->isAperta()); $assistente = $this->utente('Assistente', 'assistente@example.test', 'assistente'); Livewire::actingAs($assistente)->test(Home::class) ->assertSee('Mario Rossi') ->call('scartaRichiesta', $r->id) ->assertDontSee('Mario Rossi'); $r->refresh(); $this->assertSame(Richiesta::SCARTATA, $r->stato); $this->assertSame($assistente->id, $r->gestita_da); } // ─── Dalla richiesta ai moduli esistenti ──────────────────── public function test_dalla_richiesta_il_modulo_di_assegnazione_e_precompilato_e_la_chiude(): void { $rossi = $this->proclamatore('Rossi', 'Mario'); $t = $this->territorio('7'); $r = app(RichiesteServizio::class)->registra(Richiesta::NUOVO, 'Mario Rossi', null, null); Livewire::withQueryParams(['proclamatore' => $rossi->id, 'richiesta' => $r->id, 'dalla_home' => 1]) ->actingAs($this->admin) ->test(Assegna::class) ->assertSet('proclamatore_id', $rossi->id) ->assertSet('richiestaId', $r->id) ->assertSee('Mario Rossi') ->set('territorio_id', $t->id) ->set('cartaceo', true) ->call('save') ->assertHasNoErrors(); $a = Assegnazione::sole(); $this->assertTrue($a->cartaceo); $r->refresh(); $this->assertSame(Richiesta::EVASA, $r->stato); $this->assertSame($a->id, $r->assegnazione_id); $this->assertSame($this->admin->id, $r->gestita_da); } public function test_senza_permesso_o_con_una_richiesta_chiusa_il_modulo_la_ignora(): void { $r = app(RichiesteServizio::class)->registra(Richiesta::NUOVO, 'Mario Rossi', null, null); $operatore = $this->utente('Operatore', 'operatore@example.test', 'operatore'); Livewire::withQueryParams(['richiesta' => $r->id])->actingAs($operatore) ->test(Assegna::class)->assertSet('richiestaId', null); $r->scarta($this->admin); Livewire::withQueryParams(['richiesta' => $r->id])->actingAs($this->admin) ->test(Assegna::class)->assertSet('richiestaId', null); } public function test_dalla_riconsegna_il_rientro_la_chiude(): void { $t = $this->territorio('12'); $a = $this->assegna($t, $this->proclamatore('Verdi', 'Anna'), '2026-10-01'); $r = app(RichiesteServizio::class)->registra(Richiesta::RICONSEGNA, 'Anna Verdi', '12', null); Livewire::withQueryParams(['richiesta' => $r->id])->actingAs($this->admin) ->test(Rientra::class, ['assegnazione' => $a]) ->assertSee('Riconsegna annunciata da') ->call('save') ->assertHasNoErrors(); $this->assertNotNull($a->fresh()->returned_at); $r->refresh(); $this->assertSame(Richiesta::EVASA, $r->stato); $this->assertSame($a->id, $r->assegnazione_id); } public function test_le_richieste_gestite_si_cancellano_dopo_novanta_giorni(): void { $servizio = app(RichiesteServizio::class); $vecchia = $servizio->registra(Richiesta::NUOVO, 'Vecchia Gestita', null, null); $vecchia->scarta($this->admin); $aperta = $servizio->registra(Richiesta::NUOVO, 'Vecchia Aperta', null, null); $this->travel(PuliziaRichieste::GIORNI - 1)->days(); $recente = $servizio->registra(Richiesta::NUOVO, 'Recente', null, null); $recente->scarta($this->admin); $this->travel(2)->days(); $this->artisan('richieste:pulizia')->assertSuccessful(); $this->assertModelMissing($vecchia); $this->assertModelExists($aperta); $this->assertModelExists($recente); } // ─── Cartacei ─────────────────────────────────────────────── public function test_l_elenco_dei_cartacei_mostra_quelli_fuori_e_a_chi(): void { $rossi = $this->proclamatore('Rossi', 'Mario'); $this->assegna($this->territorio('21'), $rossi, '2026-10-01', cartaceo: true); $this->assegna($this->territorio('22'), $rossi, '2026-10-01'); $rientrato = $this->assegna($this->territorio('23'), $rossi, '2026-09-01', cartaceo: true); $rientrato->update(['returned_at' => '2026-10-01']); $this->actingAs($this->admin)->get(route('assegnazioni.cartacei')) ->assertOk() ->assertSee('N° 21') ->assertSee('Rossi Mario') ->assertDontSee('N° 22') ->assertDontSee('N° 23'); // Nell'elenco degli assegnati, il segno solo dove serve. $html = $this->actingAs($this->admin)->get(route('assegnazioni.assegna'))->getContent(); $this->assertStringContainsString('assegnazioni.elenco-assegnati', $html); Livewire::withoutLazyLoading()->actingAs($this->admin)->test(\App\Livewire\Assegnazioni\ElencoAssegnati::class) ->assertSeeHtml('cartaceo'); $sorvegliante = $this->utente('Sorvegliante', 'sorvegliante@example.test', 'sorvegliante'); $this->actingAs($sorvegliante)->get(route('assegnazioni.cartacei'))->assertForbidden(); } // ─── Modulo S-13 per email ────────────────────────────────── public function test_l_s13_va_agli_amministratori_con_l_email_confermata(): void { Mail::fake(); $this->postaFunzionante(); $assistente = $this->utente('Assistente', 'assistente@example.test', 'assistente'); $nonConfermato = $this->utente('Altro', 'altro@example.test', 'amministratore'); $nonConfermato->forceFill(['email_verified_at' => null])->save(); $this->artisan('registro:invia-s13')->assertSuccessful(); Mail::assertSent(ModuloS13Mail::class, fn (ModuloS13Mail $m) => $m->hasTo('admin@example.test') && collect($m->attachments())->contains(fn ($a) => $a->as === 'S-13-registro-assegnazioni-2026-2027.pdf' && $a->mime === 'application/pdf')); Mail::assertSent(ModuloS13Mail::class, 1); Mail::assertNotSent(ModuloS13Mail::class, fn (ModuloS13Mail $m) => $m->hasTo($assistente->email) || $m->hasTo($nonConfermato->email)); $this->assertSame(1, Activity::where('description', 's13_inviato')->sole()->properties['destinatari']); } public function test_senza_posta_l_s13_non_parte(): void { Mail::fake(); $this->artisan('registro:invia-s13')->assertSuccessful(); Mail::assertNothingSent(); } public function test_l_s13_parte_solo_l_ultima_domenica_del_mese(): void { $evento = collect(app(Schedule::class)->events()) ->first(fn ($e) => str_contains((string) $e->command, 'registro:invia-s13')); $this->assertNotNull($evento); $parte = function (string $quando) use ($evento) { $this->travelTo(Carbon::parse($quando, config('app.timezone'))); return $evento->isDue($this->app) && $evento->filtersPass($this->app); }; $this->assertTrue($parte('2026-10-25 08:00'), 'Ultima domenica di ottobre'); $this->assertFalse($parte('2026-10-18 08:00'), 'Penultima domenica'); $this->assertFalse($parte('2026-10-24 08:00'), 'Sabato'); $this->assertTrue($parte('2026-11-29 08:00'), 'Ultima domenica di novembre'); $this->assertTrue($parte('2027-01-31 08:00'), 'Domenica 31'); } }