Files
termanager2/tests/Feature/GruppiServizioTest.php
Francesco Picone 96084499f5 ++ gruppi di servizio: sorvegliante con utenza limitata, pacchetto di territori, assegnazione ai membri
Il gruppo ha un sorvegliante (proclamatore con utenza: email e password
temporanea, senza conferma dell'email) e dei membri; l'amministratore
mette in blocco dei territori nel pacchetto del gruppo. Il pacchetto non
e' un'assegnazione e non tocca le statistiche; quando il sorvegliante da'
un territorio a un membro nasce un'assegnazione normale con
gruppo_servizio_id. Al rientro il territorio torna in reparto. Sciogliere
il gruppo libera pacchetto e membri e disattiva l'utenza.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 17:51:37 +02:00

376 lines
16 KiB
PHP

<?php
namespace Tests\Feature;
use App\Livewire\Assegnazioni\Assegna;
use App\Livewire\Assegnazioni\Rientra;
use App\Livewire\Gruppi\GruppiIndex;
use App\Livewire\Gruppi\GruppoShow;
use App\Livewire\Gruppi\MioGruppo;
use App\Livewire\Settings\UsersIndex;
use App\Livewire\Settings\XmlExchange;
use App\Models\AnnoTeocratico;
use App\Models\Assegnazione;
use App\Models\GruppoServizio;
use App\Models\Proclamatore;
use App\Models\Setting;
use App\Models\Territorio;
use App\Models\User;
use App\Services\GruppiServizio;
use Database\Seeders\RolesAndPermissionsSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
use InvalidArgumentException;
use Livewire\Livewire;
use Tests\TestCase;
/**
* Gruppi di servizio (19 ottobre 2026): un sorvegliante con un'utenza
* limitata al proprio gruppo distribuisce ai membri i territori del pacchetto.
*/
class GruppiServizioTest 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');
$this->actingAs($this->admin);
}
private function proclamatore(string $cognome, string $nome = 'Test'): Proclamatore
{
return Proclamatore::create(['nome' => $nome, 'cognome' => $cognome, 'attivo' => true]);
}
private function territorio(string $numero, bool $pdf = true): Territorio
{
if ($pdf) {
Storage::disk('public')->put("territori-pdf/t{$numero}.pdf", '%PDF-1.4');
}
return Territorio::create(['numero' => $numero, 'attivo' => true, 'pdf_path' => $pdf ? "territori-pdf/t{$numero}.pdf" : null]);
}
/** Gruppo "Zisa": sorvegliante Rossi, membri Verdi e Bianchi, pacchetto 12, 14, 35. */
private function gruppo(): array
{
$rossi = $this->proclamatore('Rossi', 'Mario');
$verdi = $this->proclamatore('Verdi', 'Anna');
$bianchi = $this->proclamatore('Bianchi', 'Luca');
[$gruppo, $password] = app(GruppiServizio::class)->crea('Zisa', $rossi, 'mario@example.test', [$verdi->id, $bianchi->id]);
$territori = collect(['12', '14', '35'])->map(fn ($n) => $this->territorio($n));
app(GruppiServizio::class)->aggiungiAlPacchetto($gruppo, $territori->pluck('id')->all());
$sorvegliante = User::where('email', 'mario@example.test')->sole();
// Il cambio password al primo accesso ha i suoi test: qui si lavora.
$sorvegliante->forceFill(['password_change_required' => false])->save();
return compact('gruppo', 'password', 'rossi', 'verdi', 'bianchi', 'territori', 'sorvegliante');
}
// ─── Creazione ──────────────────────────────────────────────
public function test_creare_un_gruppo_da_un_utenza_limitata_al_sorvegliante(): void
{
$rossi = $this->proclamatore('Rossi', 'Mario');
$pagina = Livewire::test(GruppiIndex::class)
->set('nome', 'Zisa')
->set('sorveglianteId', $rossi->id)
->set('email', 'mario@example.test')
->call('crea')
->assertHasNoErrors();
$gruppo = GruppoServizio::sole();
$utente = User::where('email', 'mario@example.test')->sole();
$this->assertSame($rossi->id, $gruppo->sorvegliante_id);
$this->assertSame($gruppo->id, $rossi->fresh()->gruppo_servizio_id, 'Il sorvegliante è anche membro');
$this->assertSame($rossi->id, $utente->proclamatore_id);
$this->assertTrue($utente->hasRole('sorvegliante'));
$this->assertTrue($utente->password_change_required);
// Nessuna conferma dell'email per il sorvegliante.
$this->assertNull($utente->email_verified_at);
$this->assertTrue($utente->hasVerifiedEmail());
// La password temporanea si vede una volta, ed è quella vera.
$password = $pagina->get('passwordTemporanea');
$this->assertNotEmpty($password);
$pagina->assertSee($password);
$this->assertTrue(\Illuminate\Support\Facades\Hash::check($password, $utente->password));
}
public function test_un_proclamatore_sta_in_un_solo_gruppo(): void
{
['gruppo' => $zisa, 'verdi' => $verdi] = $this->gruppo();
[$noce] = app(GruppiServizio::class)->crea('Noce', $this->proclamatore('Neri'), 'neri@example.test');
$this->expectException(InvalidArgumentException::class);
app(GruppiServizio::class)->aggiungiMembri($noce, [$verdi->id]);
}
public function test_il_ruolo_sorvegliante_non_si_da_dalla_pagina_utenti(): void
{
['sorvegliante' => $sorvegliante] = $this->gruppo();
$utenti = Livewire::test(UsersIndex::class);
$this->assertNotContains('sorvegliante', $utenti->get('availableRoles'));
$utenti->call('startEdit', $sorvegliante->id)->assertSet('editingUserId', null);
$utenti->call('deleteUser', $sorvegliante->id);
$this->assertNotNull($sorvegliante->fresh());
}
// ─── Pacchetto ──────────────────────────────────────────────
public function test_il_pacchetto_non_e_un_assegnazione_e_non_e_in_reparto(): void
{
['territori' => $territori] = $this->gruppo();
$libero = $this->territorio('99');
$this->assertSame(0, Assegnazione::count(), 'Il pacchetto non crea assegnazioni: medie e statistiche non cambiano');
$this->assertSame([$libero->id], Territorio::inReparto()->pluck('id')->all());
$this->assertSame('in_gruppo', $territori[0]->fresh()->stato);
$this->assertSame('In gruppo Zisa', $territori[0]->fresh()->stato_etichetta);
}
public function test_dalla_pagina_assegna_un_territorio_del_pacchetto_non_si_assegna(): void
{
['territori' => $territori] = $this->gruppo();
Livewire::test(Assegna::class)
->set('territorio_id', $territori[0]->id)
->set('proclamatore_id', $this->proclamatore('Neri')->id)
->call('save')
->assertHasErrors('territorio_id');
$this->assertSame(0, Assegnazione::count());
}
public function test_un_territorio_assegnato_non_entra_nel_pacchetto(): void
{
['gruppo' => $gruppo] = $this->gruppo();
$t = $this->territorio('50');
Assegnazione::create([
'territorio_id' => $t->id, 'proclamatore_id' => $this->proclamatore('Neri')->id,
'anno_teocratico_id' => AnnoTeocratico::sole()->id, 'assigned_at' => now(), 'created_by' => $this->admin->id,
]);
$this->expectException(InvalidArgumentException::class);
app(GruppiServizio::class)->aggiungiAlPacchetto($gruppo, [$t->id]);
}
// ─── Accesso del sorvegliante ───────────────────────────────
public function test_il_sorvegliante_vede_solo_il_suo_gruppo_e_il_profilo(): void
{
['sorvegliante' => $sorvegliante] = $this->gruppo();
$this->flushSession();
$this->actingAs($sorvegliante);
$this->get(route('dashboard'))->assertRedirect(route('mio-gruppo'));
$this->get(route('mio-gruppo'))->assertOk()->assertSee('Gruppo Zisa')->assertSee('N° 12');
$this->get(route('profile'))->assertOk();
foreach (['territori.index', 'proclamatori.index', 'registro.index', 'campagne.index', 'assegnazioni.assegna', 'users.index', 'gruppi.index', 'privacy'] as $rotta) {
$this->get(route($rotta))->assertForbidden();
}
}
public function test_l_amministratore_non_ha_la_voce_il_mio_gruppo(): void
{
$this->gruppo();
// Passa ogni @can (Gate::before), ma un gruppo suo non ce l'ha.
$this->get(route('dashboard'))->assertOk()->assertDontSee('Il mio gruppo')->assertSee('Gruppi di servizio');
}
public function test_il_sorvegliante_entra_senza_conferma_dell_email_e_va_al_suo_gruppo(): void
{
$this->gruppo();
$this->flushSession();
auth()->logout();
$password = 'una-password-nuova-1';
User::where('email', 'mario@example.test')->sole()->forceFill(['password' => $password])->save();
$this->post(route('login.store'), ['email' => 'mario@example.test', 'password' => $password])
->assertRedirect(route('mio-gruppo'));
}
// ─── Il sorvegliante al lavoro ──────────────────────────────
public function test_il_sorvegliante_assegna_a_un_membro_e_nasce_un_assegnazione_normale(): void
{
['sorvegliante' => $s, 'verdi' => $verdi, 'territori' => $territori, 'gruppo' => $gruppo] = $this->gruppo();
$this->flushSession();
Livewire::actingAs($s)->test(MioGruppo::class)
->call('apriAssegnazione', $territori[0]->id)
->set('membroId', $verdi->id)
->call('assegna')
->assertHasNoErrors()
// Come dalla pagina Assegna: si apre l'invio del link.
->assertSee('Hai inviato il link?');
$a = Assegnazione::sole();
$this->assertSame($verdi->id, $a->proclamatore_id);
$this->assertSame($gruppo->id, $a->gruppo_servizio_id, 'Tracciato che viene dal gruppo');
$this->assertTrue($a->link_da_inviare);
$this->assertSame($gruppo->id, $territori[0]->fresh()->gruppo_servizio_id, 'Resta nel pacchetto finché non rientra');
$this->assertSame('assegnato', $territori[0]->fresh()->stato);
}
public function test_il_sorvegliante_puo_tenere_un_territorio_per_se(): void
{
['sorvegliante' => $s, 'rossi' => $rossi, 'territori' => $territori] = $this->gruppo();
Livewire::actingAs($s)->test(MioGruppo::class)
->call('apriAssegnazione', $territori[1]->id)
->set('membroId', $rossi->id)
->call('assegna')
->assertHasNoErrors();
$this->assertSame($rossi->id, Assegnazione::sole()->proclamatore_id);
}
public function test_solo_ai_membri_e_solo_i_territori_del_pacchetto(): void
{
['sorvegliante' => $s, 'territori' => $territori] = $this->gruppo();
$estraneo = $this->proclamatore('Neri');
$fuori = $this->territorio('77');
Livewire::actingAs($s)->test(MioGruppo::class)
->call('apriAssegnazione', $territori[0]->id)
->set('membroId', $estraneo->id)
->call('assegna')
->assertHasErrors('membroId');
Livewire::actingAs($s)->test(MioGruppo::class)
->call('apriAssegnazione', $fuori->id)
->assertForbidden();
$this->assertSame(0, Assegnazione::count());
}
public function test_il_rientro_lo_registra_il_sorvegliante_e_il_territorio_torna_in_reparto(): void
{
['sorvegliante' => $s, 'verdi' => $verdi, 'territori' => $territori, 'gruppo' => $gruppo] = $this->gruppo();
$a = app(GruppiServizio::class)->assegna($gruppo, $territori[0], $verdi, now()->subDays(3));
$this->flushSession();
$this->actingAs($s)->get(route('assegnazioni.rientra', $a))->assertOk();
Livewire::actingAs($s)->test(Rientra::class, ['assegnazione' => $a])
->call('save')
->assertRedirect(route('mio-gruppo'));
$this->assertNotNull($a->fresh()->returned_at);
$this->assertNull($territori[0]->fresh()->gruppo_servizio_id, 'Esce dal pacchetto');
$this->assertSame('in_reparto', $territori[0]->fresh()->stato);
}
public function test_il_sorvegliante_non_registra_rientri_di_altri(): void
{
['sorvegliante' => $s] = $this->gruppo();
$altro = Assegnazione::create([
'territorio_id' => $this->territorio('80')->id, 'proclamatore_id' => $this->proclamatore('Neri')->id,
'anno_teocratico_id' => AnnoTeocratico::sole()->id, 'assigned_at' => now(), 'created_by' => $this->admin->id,
]);
$this->flushSession();
$this->actingAs($s)->get(route('assegnazioni.rientra', $altro))->assertForbidden();
}
public function test_il_sorvegliante_rimanda_in_reparto_quelli_non_dati(): void
{
['sorvegliante' => $s, 'territori' => $territori] = $this->gruppo();
Livewire::actingAs($s)->test(MioGruppo::class)->call('rimanda', $territori[2]->id);
$this->assertNull($territori[2]->fresh()->gruppo_servizio_id);
$this->assertSame('in_reparto', $territori[2]->fresh()->stato);
}
public function test_la_piantina_del_pacchetto_si_apre_solo_per_il_proprio_gruppo(): void
{
['sorvegliante' => $s, 'territori' => $territori] = $this->gruppo();
$fuori = $this->territorio('88');
$this->flushSession();
$this->actingAs($s)->get(route('mio-gruppo.pdf', $territori[0]))->assertOk();
$this->actingAs($s)->get(route('mio-gruppo.pdf', $fuori))->assertForbidden();
}
// ─── Scioglimento ───────────────────────────────────────────
public function test_togliere_il_ruolo_scioglie_il_gruppo(): void
{
['gruppo' => $gruppo, 'sorvegliante' => $s, 'verdi' => $verdi, 'territori' => $territori] = $this->gruppo();
$data = app(GruppiServizio::class)->assegna($gruppo, $territori[0], $verdi, now());
Livewire::test(GruppoShow::class, ['gruppo' => $gruppo])->call('sciogli');
$gruppo->refresh();
$this->assertNotNull($gruppo->sciolto_at);
// I non dati tornano in reparto; quello dato resta a Verdi, assegnazione normale.
$this->assertNull($territori[1]->fresh()->gruppo_servizio_id);
$this->assertNull($territori[2]->fresh()->gruppo_servizio_id);
$this->assertNull($data->fresh()->returned_at);
$this->assertSame($gruppo->id, $data->fresh()->gruppo_servizio_id, 'Lo storico continua a dirlo');
// Membri liberi, utenza disattivata e senza ruolo.
$this->assertSame(0, Proclamatore::where('gruppo_servizio_id', $gruppo->id)->count());
$this->assertTrue($s->fresh()->isDisattivato());
$this->assertFalse($s->fresh()->hasRole('sorvegliante'));
// Per ripartire serve un gruppo nuovo.
$this->expectException(InvalidArgumentException::class);
app(GruppiServizio::class)->aggiungiAlPacchetto($gruppo, [$territori[1]->id]);
}
public function test_un_utenza_disattivata_non_entra_e_viene_buttata_fuori(): void
{
['gruppo' => $gruppo, 'sorvegliante' => $s] = $this->gruppo();
$password = 'una-password-nuova-1';
$s->forceFill(['password' => $password])->save();
app(GruppiServizio::class)->sciogli($gruppo);
$this->flushSession();
auth()->logout();
$this->post(route('login.store'), ['email' => 'mario@example.test', 'password' => $password])
->assertSessionHasErrors('email');
$this->assertGuest();
$this->actingAs($s->fresh())->get(route('profile'))->assertRedirect(route('login'));
}
// ─── Backup ─────────────────────────────────────────────────
public function test_l_xml_conserva_gruppi_membri_pacchetto_e_utenza(): void
{
['gruppo' => $gruppo, 'verdi' => $verdi, 'territori' => $territori] = $this->gruppo();
app(GruppiServizio::class)->assegna($gruppo, $territori[0], $verdi, now());
$xml = (new XmlExchange())->currentXmlBackup();
$this->assertArrayNotHasKey('error', (new XmlExchange())->importXmlFromContent($xml));
$g = GruppoServizio::sole();
$this->assertSame('Zisa', $g->nome);
$this->assertSame('Rossi Mario', $g->sorvegliante->nome_completo);
$this->assertEqualsCanonicalizing(['Rossi Mario', 'Verdi Anna', 'Bianchi Luca'], $g->membri->map->nome_completo->all());
$this->assertEqualsCanonicalizing(['12', '14', '35'], $g->territori->pluck('numero')->all());
$this->assertSame($g->id, Assegnazione::sole()->gruppo_servizio_id);
$this->assertSame($g->sorvegliante_id, User::where('email', 'mario@example.test')->sole()->proclamatore_id);
}
}