Il numero diventa un pulsante che apre una finestra con la miniatura e il collegamento al PDF. La miniatura passa dalla rotta nuova mio-gruppo.miniatura, con la stessa regola del PDF: solo i territori del pacchetto del gruppo sorvegliato. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
792 lines
38 KiB
PHP
792 lines
38 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, [$verdi->id, $bianchi->id]);
|
|
$territori = collect(['12', '14', '35'])->map(fn ($n) => $this->territorio($n));
|
|
app(GruppiServizio::class)->aggiungiAlPacchetto($gruppo, $territori->pluck('id')->all());
|
|
$territori = $territori->map->fresh();
|
|
|
|
$sorvegliante = User::where('email', 'mrossi@termanager.it')->sole();
|
|
// Il cambio password al primo accesso ha i suoi test: qui si lavora.
|
|
// E l'email non confermata, come nella realtà (TestCase le conferma).
|
|
$sorvegliante->forceFill(['password_change_required' => false, 'email_verified_at' => null])->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');
|
|
|
|
// L'email non si chiede: è fittizia, iniziale del nome e cognome.
|
|
$pagina = Livewire::test(GruppiIndex::class)
|
|
->assertDontSee('Email per l')
|
|
->set('nome', 'Zisa')
|
|
->set('sorveglianteId', $rossi->id)
|
|
->call('crea')
|
|
->assertHasNoErrors()
|
|
->assertSee('mrossi@termanager.it');
|
|
|
|
$gruppo = GruppoServizio::sole();
|
|
$utente = User::where('email', 'mrossi@termanager.it')->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 (TestCase conferma
|
|
// gli utenti creati: qui si toglie).
|
|
$utente->forceFill(['email_verified_at' => null])->save();
|
|
$this->assertTrue($utente->fresh()->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_l_email_fittizia_viene_da_iniziale_del_nome_e_cognome(): void
|
|
{
|
|
$email = fn (string $cognome, string $nome) => GruppiServizio::emailFittizia($this->proclamatore($cognome, $nome));
|
|
|
|
$this->assertSame('ffontana@termanager.it', $email('Fontana', 'Francesco'));
|
|
$this->assertSame('fdangelo@termanager.it', $email("D'Angelo", 'Francesco'));
|
|
$this->assertSame('ndeluca@termanager.it', $email('De Luca', 'Niccolò'));
|
|
|
|
// Già presa da un altro utente: un numero in coda.
|
|
User::create(['name' => 'Francesco Fontana', 'email' => 'ffontana@termanager.it', 'password' => 'password-di-prova']);
|
|
$this->assertSame('ffontana2@termanager.it', $email('Fontana', 'Federica'));
|
|
|
|
// Due gruppi con lo stesso nome no.
|
|
app(GruppiServizio::class)->crea('Zisa', $this->proclamatore('Gialli'));
|
|
$this->expectException(InvalidArgumentException::class);
|
|
app(GruppiServizio::class)->crea('Zisa', $this->proclamatore('Blu'));
|
|
}
|
|
|
|
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'));
|
|
|
|
$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_al_primo_accesso_il_sorvegliante_cambia_la_password_temporanea(): void
|
|
{
|
|
$rossi = $this->proclamatore('Rossi', 'Mario');
|
|
[$gruppo] = app(GruppiServizio::class)->crea('Zisa', $rossi);
|
|
$s = User::where('email', 'mrossi@termanager.it')->sole();
|
|
$this->assertTrue($s->password_temporanea);
|
|
$this->flushSession();
|
|
$this->actingAs($s);
|
|
|
|
$this->get(route('mio-gruppo'))->assertRedirect(route('password.change'));
|
|
$this->get(route('password.change'))->assertOk()
|
|
->assertSee('Password temporanea')
|
|
->assertDontSee('Ripristino da backup');
|
|
|
|
$this->post(route('password.change.store'), ['password' => 'una-password-mia-1', 'password_confirmation' => 'una-password-mia-1'])
|
|
->assertRedirect(route('dashboard'));
|
|
$this->assertFalse($s->fresh()->password_temporanea);
|
|
$this->assertFalse($s->fresh()->password_change_required);
|
|
$this->get(route('mio-gruppo'))->assertOk();
|
|
|
|
// "Nuova password temporanea" dalla pagina del gruppo: di nuovo temporanea.
|
|
$this->flushSession();
|
|
Livewire::actingAs($this->admin)->test(GruppoShow::class, ['gruppo' => $gruppo])->call('nuovaPassword');
|
|
$this->assertTrue($s->fresh()->password_temporanea);
|
|
$this->assertTrue($s->fresh()->password_change_required);
|
|
}
|
|
|
|
public function test_il_sorvegliante_entra_con_l_email_fittizia_senza_conferma_e_va_al_suo_gruppo(): void
|
|
{
|
|
$this->gruppo();
|
|
$this->flushSession();
|
|
auth()->logout();
|
|
$password = 'una-password-nuova-1';
|
|
User::where('email', 'mrossi@termanager.it')->sole()->forceFill(['password' => $password])->save();
|
|
|
|
$this->post(route('login.store'), ['email' => 'mrossi@termanager.it', '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_il_sorvegliante_apre_l_anteprima_dal_numero_del_territorio(): void
|
|
{
|
|
['sorvegliante' => $s, 'territori' => $territori] = $this->gruppo();
|
|
$fuori = $this->territorio('88');
|
|
foreach ([$territori[0], $fuori] as $t) {
|
|
Storage::disk('public')->put("territori-thumbnails/t{$t->numero}.png", 'PNG');
|
|
$t->forceFill(['thumbnail_path' => "territori-thumbnails/t{$t->numero}.png"])->save();
|
|
}
|
|
$dodici = $territori[0]->fresh();
|
|
$this->flushSession();
|
|
|
|
// Il numero è un pulsante che porta miniatura e PDF del pacchetto.
|
|
$this->actingAs($s)->get(route('mio-gruppo'))->assertOk()
|
|
->assertSee('data-miniatura="' . $dodici->miniaturaGruppoUrl() . '"', false)
|
|
->assertSee('data-pdf="' . route('mio-gruppo.pdf', $dodici) . '"', false)
|
|
->assertSee('Apri la piantina (PDF)');
|
|
|
|
// La miniatura si apre solo per il proprio pacchetto, e solo da qui.
|
|
$this->actingAs($s)->get($dodici->miniaturaGruppoUrl())->assertOk();
|
|
$this->actingAs($s)->get(route('mio-gruppo.miniatura', $fuori))->assertForbidden();
|
|
$this->actingAs($s)->get($dodici->miniaturaUrl())->assertForbidden();
|
|
}
|
|
|
|
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();
|
|
// L'assegnazione la fa il sorvegliante, come nella realtà.
|
|
auth()->setUser($s);
|
|
$data = app(GruppiServizio::class)->assegna($gruppo, $territori[0], $verdi, now());
|
|
auth()->setUser($this->admin);
|
|
|
|
Livewire::test(GruppoShow::class, ['gruppo' => $gruppo])
|
|
->call('sciogli')
|
|
->assertRedirect(route('gruppi.index'));
|
|
|
|
// Il gruppo non resta nello storico: non esiste più.
|
|
$this->assertNull(GruppoServizio::find($gruppo->id));
|
|
$this->get(route('gruppi.index'))->assertOk()->assertSee('Nessun gruppo di servizio');
|
|
// I non dati tornano in reparto; quello dato resta a Verdi, assegnazione
|
|
// normale, che del gruppo conserva solo il nome.
|
|
$this->assertNull($territori[1]->fresh()->gruppo_servizio_id);
|
|
$this->assertSame('in_reparto', $territori[2]->fresh()->stato);
|
|
$this->assertNull($data->fresh()->returned_at);
|
|
$this->assertSame('assegnato', $territori[0]->fresh()->stato);
|
|
$this->assertNull($data->fresh()->gruppo_servizio_id);
|
|
$this->assertSame('Zisa', $data->fresh()->gruppo_nome);
|
|
$this->get(route('territori.show', $territori[0]))->assertOk()->assertSee('via gruppo Zisa');
|
|
// Membri liberi; l'utenza del sorvegliante non c'è più, lui torna un
|
|
// proclamatore come gli altri.
|
|
$this->assertSame(0, Proclamatore::whereNotNull('gruppo_servizio_id')->count());
|
|
$this->assertNull(User::find($s->id));
|
|
$this->assertNotNull(Proclamatore::find($s->proclamatore_id));
|
|
$this->assertSame(0, \Illuminate\Support\Facades\DB::table('model_has_roles')->where('model_id', $s->id)->count());
|
|
// Quello che aveva fatto passa a chi ha sciolto il gruppo; nel registro
|
|
// le sue voci restano, senza dati personali.
|
|
$this->assertSame($this->admin->id, $data->fresh()->created_by);
|
|
$voce = \Spatie\Activitylog\Models\Activity::where('description', 'assigned')->sole();
|
|
$this->assertNull($voce->causer);
|
|
$this->assertSame('Sorvegliante del gruppo Zisa', $voce->properties['causer_name']);
|
|
$this->assertNull($voce->properties['causer_email']);
|
|
|
|
// Per ripartire serve un gruppo nuovo.
|
|
$this->expectException(InvalidArgumentException::class);
|
|
app(GruppiServizio::class)->aggiungiAlPacchetto($gruppo, [$territori[1]->id]);
|
|
}
|
|
|
|
public function test_sciolto_il_gruppo_l_ex_sorvegliante_non_entra_piu(): 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' => 'mrossi@termanager.it', 'password' => $password])
|
|
->assertSessionHasErrors('email');
|
|
$this->assertGuest();
|
|
}
|
|
|
|
public function test_l_ex_sorvegliante_puo_guidare_un_gruppo_nuovo(): void
|
|
{
|
|
['gruppo' => $vecchio, 'rossi' => $rossi] = $this->gruppo();
|
|
app(GruppiServizio::class)->sciogli($vecchio);
|
|
|
|
$pagina = Livewire::test(GruppiIndex::class);
|
|
$this->assertContains((string) $rossi->id, collect($pagina->viewData('opzioniSorveglianti'))->pluck('valore')->all(), 'Torna tra i candidati');
|
|
|
|
// Sciolto, il nome del gruppo e l'email sono di nuovo liberi.
|
|
[$nuovo, $password] = app(GruppiServizio::class)->crea('Zisa', $rossi->fresh());
|
|
|
|
$utente = User::where('proclamatore_id', $rossi->id)->sole();
|
|
$this->assertTrue($utente->hasRole('sorvegliante'));
|
|
$this->assertSame('mrossi@termanager.it', $utente->email);
|
|
$this->assertTrue($utente->password_change_required);
|
|
$this->assertTrue(\Illuminate\Support\Facades\Hash::check($password, $utente->password));
|
|
$this->assertSame($nuovo->id, $utente->gruppoSorvegliato()?->id);
|
|
}
|
|
|
|
// ─── Sorvegliante non più attivo: il gruppo si scioglie ─────
|
|
|
|
/** Il gruppo è sciolto come da "Togli il ruolo": tutto tranne l'assegnazione già fatta. */
|
|
private function assertSciolto(array $g, Assegnazione $data): void
|
|
{
|
|
$this->assertNull(GruppoServizio::find($g['gruppo']->id), 'Gruppo sciolto ed eliminato');
|
|
$this->assertSame('Zisa', $data->fresh()->gruppo_nome);
|
|
$this->assertNull($g['territori'][1]->fresh()->gruppo_servizio_id, 'I non dati tornano in reparto');
|
|
$this->assertSame('in_reparto', $g['territori'][2]->fresh()->stato);
|
|
$this->assertNull($data->fresh()->returned_at, 'Quello già dato resta al membro');
|
|
$this->assertNull($g['bianchi']->fresh()->gruppo_servizio_id, 'Membri liberi');
|
|
$this->assertNull(User::find($g['sorvegliante']->id), 'Utenza eliminata');
|
|
}
|
|
|
|
public function test_disattivare_il_sorvegliante_dall_elenco_scioglie_il_gruppo(): void
|
|
{
|
|
$g = $this->gruppo();
|
|
$data = app(GruppiServizio::class)->assegna($g['gruppo'], $g['territori'][0], $g['verdi'], now());
|
|
|
|
Livewire::test(\App\Livewire\Proclamatori\ProclamatoreIndex::class)
|
|
->assertSeeHtml('è il sorvegliante del gruppo Zisa')
|
|
->call('toggleActive', $g['rossi']->id)
|
|
->assertDispatched('avviso', fn ($nome, $parametri) => str_contains($parametri['testo'], 'il gruppo è stato sciolto'));
|
|
|
|
$this->assertFalse($g['rossi']->fresh()->attivo);
|
|
$this->assertSciolto($g, $data);
|
|
|
|
// Riattivarlo non ricostruisce il gruppo: se ne crea uno nuovo.
|
|
Livewire::test(\App\Livewire\Proclamatori\ProclamatoreIndex::class)->call('toggleActive', $g['rossi']->id);
|
|
$this->assertSame(0, GruppoServizio::count());
|
|
$this->assertNull($g['rossi']->fresh()->gruppoSorvegliato());
|
|
}
|
|
|
|
public function test_disattivare_il_sorvegliante_dalla_modifica_scioglie_il_gruppo(): void
|
|
{
|
|
$g = $this->gruppo();
|
|
$data = app(GruppiServizio::class)->assegna($g['gruppo'], $g['territori'][0], $g['verdi'], now());
|
|
|
|
$this->get(route('proclamatori.edit', $g['rossi']))->assertOk()->assertSee('se non è più attivo il gruppo si scioglie');
|
|
|
|
Livewire::test(\App\Livewire\Proclamatori\ProclamatoreEdit::class, ['proclamatore' => $g['rossi']])
|
|
->set('attivo', false)
|
|
->call('save')
|
|
->assertHasNoErrors();
|
|
|
|
$this->assertSciolto($g, $data);
|
|
$this->assertStringContainsString('il gruppo è stato sciolto', session('success'));
|
|
}
|
|
|
|
public function test_spostare_nel_cestino_il_sorvegliante_scioglie_il_gruppo(): void
|
|
{
|
|
$g = $this->gruppo();
|
|
$data = app(GruppiServizio::class)->assegna($g['gruppo'], $g['territori'][0], $g['verdi'], now());
|
|
|
|
Livewire::test(\App\Livewire\Proclamatori\ProclamatoreIndex::class)->call('deleteProclamatore', $g['rossi']->id);
|
|
|
|
$this->assertTrue($g['rossi']->fresh()->trashed());
|
|
$this->assertSciolto($g, $data);
|
|
}
|
|
|
|
public function test_disattivare_o_cestinare_un_membro_non_scioglie_il_gruppo(): void
|
|
{
|
|
['gruppo' => $gruppo, 'verdi' => $verdi, 'bianchi' => $bianchi] = $this->gruppo();
|
|
|
|
Livewire::test(\App\Livewire\Proclamatori\ProclamatoreIndex::class)
|
|
->call('toggleActive', $verdi->id)
|
|
->call('deleteProclamatore', $bianchi->id);
|
|
|
|
$this->assertNotNull(GruppoServizio::find($gruppo->id));
|
|
$this->assertSame($gruppo->id, $verdi->fresh()->gruppo_servizio_id, 'Non attivo resta membro, ma non riceve territori');
|
|
$this->assertNull($bianchi->fresh()->gruppo_servizio_id, 'Nel cestino lascia il gruppo');
|
|
}
|
|
|
|
// ─── Dettagli del gruppo nel profilo ────────────────────────
|
|
|
|
public function test_la_scheda_del_proclamatore_mostra_il_gruppo_che_guida(): void
|
|
{
|
|
$g = $this->gruppo();
|
|
app(GruppiServizio::class)->assegna($g['gruppo'], $g['territori'][0], $g['verdi'], now());
|
|
|
|
$this->get(route('proclamatori.show', $g['rossi']))->assertOk()
|
|
->assertSeeInOrder(['Gruppo di servizio', 'Sorvegliante', 'Zisa', 'Membri', '3', 'Nel pacchetto', '3', 'Da dare', '2', 'Assegnazioni dal gruppo', '1'])
|
|
->assertSee('<code>mrossi@termanager.it</code>', false)
|
|
->assertSee('non è ancora entrato');
|
|
|
|
$this->get(route('proclamatori.show', $g['verdi']))->assertOk()
|
|
->assertSee('Membro del gruppo')
|
|
->assertSee('sorvegliante Rossi Mario');
|
|
|
|
// Sciolto il gruppo, nella scheda non ne resta traccia.
|
|
app(GruppiServizio::class)->sciogli($g['gruppo']);
|
|
$this->get(route('proclamatori.show', $g['rossi']))->assertOk()
|
|
->assertDontSee('Assegnazioni dal gruppo')
|
|
->assertDontSee('In passato');
|
|
}
|
|
|
|
public function test_chi_fa_parte_di_un_gruppo_e_segnalato_in_elenco_scheda_e_scelta(): void
|
|
{
|
|
$g = $this->gruppo();
|
|
$this->proclamatore('Neri');
|
|
|
|
Livewire::test(\App\Livewire\Proclamatori\ProclamatoreIndex::class)
|
|
->assertSee('Sorvegliante del gruppo Zisa')
|
|
->assertSee('Gruppo Zisa')
|
|
->set('filtroStato', 'gruppo')
|
|
->assertSee('Verdi')->assertSee('Bianchi')->assertSee('Rossi')
|
|
->assertDontSee('Neri');
|
|
|
|
$this->get(route('proclamatori.show', $g['verdi']))->assertOk()->assertSee('Gruppo Zisa');
|
|
|
|
// Nella tendina di Assegna la nota accanto al nome...
|
|
$opzioni = collect(Livewire::test(Assegna::class)->viewData('opzioniProclamatori'))->keyBy('etichetta');
|
|
$this->assertSame('gruppo Zisa', $opzioni['Verdi Anna']['dettaglio']);
|
|
$this->assertSame('sorvegliante del gruppo Zisa', $opzioni['Rossi Mario']['dettaglio']);
|
|
$this->assertArrayNotHasKey('dettaglio', $opzioni['Neri Test']);
|
|
$this->assertArrayNotHasKey('motivo', $opzioni['Verdi Anna'], 'Si può comunque scegliere');
|
|
|
|
// ...che in "Il mio gruppo", dove sono tutti dello stesso gruppo, non serve.
|
|
$this->flushSession();
|
|
$membri = Livewire::actingAs($g['sorvegliante'])->test(MioGruppo::class)->viewData('opzioniMembri');
|
|
$this->assertSame([], collect($membri)->pluck('dettaglio')->filter()->all());
|
|
}
|
|
|
|
public function test_il_profilo_del_sorvegliante_mostra_il_suo_gruppo(): void
|
|
{
|
|
$g = $this->gruppo();
|
|
app(GruppiServizio::class)->assegna($g['gruppo'], $g['territori'][0], $g['verdi'], now());
|
|
$this->flushSession();
|
|
|
|
$this->actingAs($g['sorvegliante'])->get(route('profile'))->assertOk()
|
|
->assertSee('Il tuo gruppo di servizio')
|
|
->assertSeeInOrder(['Zisa', 'Membri', '3', 'Nel pacchetto', '3', 'Da dare', '2', 'Dati ai membri', '1'])
|
|
->assertSee('Torna al mio gruppo');
|
|
|
|
// L'amministratore un gruppo suo non ce l'ha (sessione nuova: è un
|
|
// altro utente).
|
|
$this->flushSession();
|
|
$this->actingAs($this->admin)->get(route('profile'))->assertOk()
|
|
->assertDontSee('Il tuo gruppo di servizio')
|
|
->assertSee('Torna alla home');
|
|
}
|
|
|
|
// ─── Proclamatori: anonimizzazione, registro ────────────────
|
|
|
|
public function test_anonimizzare_il_sorvegliante_scioglie_il_gruppo_e_ne_elimina_l_utenza(): void
|
|
{
|
|
['gruppo' => $gruppo, 'rossi' => $rossi, 'sorvegliante' => $s] = $this->gruppo();
|
|
|
|
Livewire::test(\App\Livewire\Proclamatori\ProclamatoreIndex::class)->call('anonimizza', $rossi->id);
|
|
|
|
$this->assertNull(GruppoServizio::find($gruppo->id));
|
|
$this->assertNull(User::find($s->id), "Nome ed email dell'utenza spariscono con lei");
|
|
$this->assertSame('Anonimo', Proclamatore::withTrashed()->find($rossi->id)->nome);
|
|
}
|
|
|
|
public function test_nel_registro_delle_attivita_l_utenza_del_sorvegliante_non_ha_nome_ne_email(): void
|
|
{
|
|
['sorvegliante' => $s] = $this->gruppo();
|
|
$s->update(['email' => 'mario.rossi@example.test']);
|
|
|
|
$voci = \Spatie\Activitylog\Models\Activity::where('subject_type', User::class)->where('subject_id', $s->id)->get();
|
|
$this->assertNotEmpty($voci);
|
|
foreach ($voci as $voce) {
|
|
$json = json_encode($voce->properties);
|
|
$this->assertStringNotContainsString('Rossi', $json);
|
|
$this->assertStringNotContainsString('mario', $json);
|
|
}
|
|
}
|
|
|
|
// ─── Le azioni del sorvegliante passano dal middleware ──────
|
|
|
|
public function test_le_azioni_livewire_del_sorvegliante_non_finiscono_in_403(): void
|
|
{
|
|
// Livewire::test() non passa dai middleware delle rotte: qui si chiama
|
|
// la rotta vera delle azioni (in Livewire 3.7 "default.livewire.update",
|
|
// che il filtro 'livewire.*' non riconosceva: ogni clic era un 403).
|
|
['sorvegliante' => $s] = $this->gruppo();
|
|
$this->flushSession();
|
|
$rotta = collect(app('router')->getRoutes()->getRoutes())
|
|
->first(fn ($r) => str_ends_with($r->uri(), 'livewire/update'));
|
|
$this->assertNotNull($rotta);
|
|
|
|
$this->withoutExceptionHandling();
|
|
try {
|
|
$this->actingAs($s)->post('/' . ltrim($rotta->uri(), '/'), ['components' => []], ['X-Livewire' => 'true']);
|
|
} catch (\Symfony\Component\HttpKernel\Exception\HttpException $e) {
|
|
$this->assertNotSame(403, $e->getStatusCode(), $e->getMessage());
|
|
} catch (\Throwable) {
|
|
// Livewire rifiuta la richiesta vuota: non è il 403 del sorvegliante.
|
|
}
|
|
$this->addToAssertionCount(1);
|
|
}
|
|
|
|
// ─── Priorità e filtri ──────────────────────────────────────
|
|
|
|
/** Territori in reparto con giacenze diverse: 40 (200 gg), 41 (50 gg), 42 (spunta prioritario), 43 (nuovo). */
|
|
private function territoriConPriorita(): array
|
|
{
|
|
// Soglia dei prioritari a 180 giorni, e senza l'istanza delle
|
|
// impostazioni rimasta in memoria da un altro test.
|
|
Setting::query()->update(['giorni_giacenza_prioritari' => 180]);
|
|
Setting::flushCache();
|
|
|
|
$zona = \App\Models\Zona::create(['nome' => 'Noce', 'attivo' => true]);
|
|
$altra = \App\Models\Zona::create(['nome' => 'Zisa', 'attivo' => true]);
|
|
$t = [];
|
|
foreach (['40' => 200, '41' => 50, '42' => 0, '43' => 0] as $numero => $giorni) {
|
|
$numero = (string) $numero; // le chiavi numeriche diventano interi
|
|
$t[$numero] = $this->territorio($numero);
|
|
$t[$numero]->forceFill([
|
|
'created_at' => now()->subDays($giorni),
|
|
'prioritario' => $numero === '42',
|
|
'zona_id' => $numero === '43' ? $altra->id : $zona->id,
|
|
])->save();
|
|
}
|
|
|
|
return $t;
|
|
}
|
|
|
|
public function test_aggiungendo_al_pacchetto_i_territori_sono_in_ordine_di_priorita_e_filtrabili(): void
|
|
{
|
|
['gruppo' => $gruppo] = $this->gruppo();
|
|
$t = $this->territoriConPriorita();
|
|
|
|
$pagina = Livewire::test(GruppoShow::class, ['gruppo' => $gruppo]);
|
|
// Prima i prioritari (per giacenza), poi gli altri: la regola della Home.
|
|
$this->assertSame(['40', '42', '41', '43'], $pagina->viewData('candidatiTerritori')->pluck('numero')->all());
|
|
$pagina->assertSee('★ Prioritario')->assertSee('fermo da 200 gg');
|
|
|
|
$pagina->set('soloPrioritari', true);
|
|
$this->assertSame(['40', '42'], $pagina->viewData('candidatiTerritori')->pluck('numero')->all());
|
|
|
|
$pagina->set('soloPrioritari', false)->set('filtroZona', (string) $t['43']->zona_id);
|
|
$this->assertSame(['43'], $pagina->viewData('candidatiTerritori')->pluck('numero')->all());
|
|
|
|
$pagina->set('filtroZona', '')->set('cercaTerritorio', '41');
|
|
$this->assertSame(['41'], $pagina->viewData('candidatiTerritori')->pluck('numero')->all());
|
|
|
|
// "Seleziona tutti quelli mostrati" e aggiunta in blocco.
|
|
$pagina->set('cercaTerritorio', '')->set('soloPrioritari', true)
|
|
->call('selezionaMostrati')
|
|
->call('aggiungiTerritori');
|
|
$this->assertSame($gruppo->id, $t['40']->fresh()->gruppo_servizio_id);
|
|
$this->assertSame($gruppo->id, $t['42']->fresh()->gruppo_servizio_id);
|
|
$this->assertNull($t['41']->fresh()->gruppo_servizio_id);
|
|
}
|
|
|
|
public function test_il_sorvegliante_vede_il_pacchetto_in_ordine_di_priorita_e_lo_filtra(): void
|
|
{
|
|
['gruppo' => $gruppo, 'sorvegliante' => $s, 'verdi' => $verdi, 'territori' => $vecchi] = $this->gruppo();
|
|
$t = $this->territoriConPriorita();
|
|
app(GruppiServizio::class)->aggiungiAlPacchetto($gruppo, collect($t)->pluck('id')->all());
|
|
// Il pacchetto di gruppo() (12, 14, 35) va via, tranne il 12 che si dà.
|
|
app(GruppiServizio::class)->assegna($gruppo, $vecchi[0], $verdi, now());
|
|
app(GruppiServizio::class)->rimandaInReparto($gruppo, $vecchi[1]->fresh());
|
|
app(GruppiServizio::class)->rimandaInReparto($gruppo, $vecchi[2]->fresh());
|
|
$this->flushSession();
|
|
|
|
$pagina = Livewire::actingAs($s)->test(MioGruppo::class)
|
|
->assertSee('Sorvegliante: <strong>Mario Rossi</strong>', false);
|
|
// Da dare in ordine di priorità, poi quelli già dati.
|
|
$this->assertSame(['40', '42', '41', '43', '12'], $pagina->viewData('territori')->pluck('numero')->all());
|
|
|
|
$pagina->set('filtroStato', 'da_dare');
|
|
$this->assertSame(['40', '42', '41', '43'], $pagina->viewData('territori')->pluck('numero')->all());
|
|
$pagina->set('filtroStato', 'dati');
|
|
$this->assertSame(['12'], $pagina->viewData('territori')->pluck('numero')->all());
|
|
$pagina->set('filtroStato', '')->set('soloPrioritari', true);
|
|
$this->assertSame(['40', '42'], $pagina->viewData('territori')->pluck('numero')->all());
|
|
$pagina->set('soloPrioritari', false)->set('filtroZona', (string) $t['43']->zona_id);
|
|
$this->assertSame(['43'], $pagina->viewData('territori')->pluck('numero')->all());
|
|
}
|
|
|
|
// ─── Backup e azzeramento ───────────────────────────────────
|
|
|
|
public function test_l_azzeramento_toglie_anche_gruppi_e_utenza_del_sorvegliante(): void
|
|
{
|
|
['gruppo' => $gruppo, 'verdi' => $verdi, 'territori' => $territori, 'sorvegliante' => $s] = $this->gruppo();
|
|
app(GruppiServizio::class)->assegna($gruppo, $territori[0], $verdi, now());
|
|
|
|
$stato = app(\App\Services\BackupOperationState::class);
|
|
$stato->initialize('azzeramento-gruppi', 'azzeramento', 'In attesa');
|
|
(new \App\Jobs\ResetApplication('azzeramento-gruppi', $this->admin->id))->handle($stato, app(\App\Services\BackupArchive::class));
|
|
|
|
$this->assertSame('completed', $stato->get('azzeramento-gruppi')['status'] ?? null, json_encode($stato->get('azzeramento-gruppi')));
|
|
$this->assertSame(0, GruppoServizio::count());
|
|
$this->assertNull(User::find($s->id));
|
|
}
|
|
|
|
|
|
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('Zisa', Assegnazione::sole()->gruppo_nome);
|
|
$utenza = User::where('email', 'mrossi@termanager.it')->sole();
|
|
$this->assertTrue($utenza->hasRole('sorvegliante'));
|
|
$this->assertSame($g->sorvegliante_id, $utenza->proclamatore_id);
|
|
}
|
|
|
|
public function test_l_xml_conserva_il_nome_del_gruppo_sulle_assegnazioni_dopo_lo_scioglimento(): void
|
|
{
|
|
['gruppo' => $gruppo, 'verdi' => $verdi, 'territori' => $territori] = $this->gruppo();
|
|
app(GruppiServizio::class)->assegna($gruppo, $territori[0], $verdi, now());
|
|
app(GruppiServizio::class)->sciogli($gruppo);
|
|
|
|
$xml = (new XmlExchange())->currentXmlBackup();
|
|
$this->assertArrayNotHasKey('error', (new XmlExchange())->importXmlFromContent($xml));
|
|
|
|
$this->assertSame(0, GruppoServizio::count());
|
|
$this->assertNull(Assegnazione::sole()->gruppo_servizio_id);
|
|
$this->assertSame('Zisa', Assegnazione::sole()->gruppo_nome);
|
|
// Dell'ex sorvegliante non resta un'utenza: solo l'amministratore.
|
|
$this->assertSame([$this->admin->id], User::pluck('id')->all());
|
|
}
|
|
}
|