Files
crewli/api/tests/Feature/Person/PersonTest.php
bert.hausmans 5d8a749cb3 fix: seeder creates User accounts for approved/no_show persons
The DevSeeder was creating approved persons without linked User
accounts, which can't happen in production (approval flow always
creates accounts). Added linkUsersToApprovedPersons() helper that
runs after person creation in each event seeder, creating User
accounts via firstOrCreate for approved and no_show persons that
lack user_id.

Also added safeguard tests verifying the approval flow creates
user accounts and reuses existing ones.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 20:42:47 +02:00

243 lines
7.8 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Feature\Person;
use App\Models\CrowdType;
use App\Models\Event;
use App\Models\Organisation;
use App\Models\Person;
use App\Models\User;
use Database\Seeders\RoleSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Sanctum\Sanctum;
use Tests\TestCase;
class PersonTest extends TestCase
{
use RefreshDatabase;
private User $orgAdmin;
private User $outsider;
private Organisation $organisation;
private Organisation $otherOrganisation;
private Event $event;
private CrowdType $crowdType;
protected function setUp(): void
{
parent::setUp();
$this->seed(RoleSeeder::class);
$this->organisation = Organisation::factory()->create();
$this->otherOrganisation = Organisation::factory()->create();
$this->orgAdmin = User::factory()->create();
$this->organisation->users()->attach($this->orgAdmin, ['role' => 'org_admin']);
$this->outsider = User::factory()->create();
$this->otherOrganisation->users()->attach($this->outsider, ['role' => 'org_admin']);
$this->event = Event::factory()->create(['organisation_id' => $this->organisation->id]);
$this->crowdType = CrowdType::factory()->systemType('VOLUNTEER')->create([
'organisation_id' => $this->organisation->id,
]);
}
public function test_index_returns_persons_for_event(): void
{
Person::factory()->count(3)->create([
'event_id' => $this->event->id,
'crowd_type_id' => $this->crowdType->id,
]);
Sanctum::actingAs($this->orgAdmin);
$response = $this->getJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/persons");
$response->assertOk();
$this->assertCount(3, $response->json('data'));
}
public function test_index_filters_by_crowd_type_id(): void
{
$otherCrowdType = CrowdType::factory()->systemType('CREW')->create([
'organisation_id' => $this->organisation->id,
]);
Person::factory()->count(2)->create([
'event_id' => $this->event->id,
'crowd_type_id' => $this->crowdType->id,
]);
Person::factory()->create([
'event_id' => $this->event->id,
'crowd_type_id' => $otherCrowdType->id,
]);
Sanctum::actingAs($this->orgAdmin);
$response = $this->getJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/persons?crowd_type_id={$this->crowdType->id}");
$response->assertOk();
$this->assertCount(2, $response->json('data'));
}
public function test_index_other_event_returns_403(): void
{
$otherEvent = Event::factory()->create(['organisation_id' => $this->otherOrganisation->id]);
Sanctum::actingAs($this->outsider);
// Outsider tries to access event from other org
Sanctum::actingAs($this->outsider);
$response = $this->getJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/persons");
$response->assertForbidden();
}
public function test_show_returns_person_with_crowd_type(): void
{
$person = Person::factory()->create([
'event_id' => $this->event->id,
'crowd_type_id' => $this->crowdType->id,
]);
Sanctum::actingAs($this->orgAdmin);
$response = $this->getJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/persons/{$person->id}");
$response->assertOk()
->assertJsonPath('data.crowd_type.system_type', 'VOLUNTEER');
}
public function test_store_creates_person(): void
{
Sanctum::actingAs($this->orgAdmin);
$response = $this->postJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/persons", [
'crowd_type_id' => $this->crowdType->id,
'first_name' => 'Jan',
'last_name' => 'de Vries',
'email' => 'jan@test.nl',
'phone' => '0612345678',
]);
$response->assertCreated()
->assertJson(['data' => ['first_name' => 'Jan', 'last_name' => 'de Vries', 'email' => 'jan@test.nl', 'status' => 'pending']]);
$this->assertDatabaseHas('persons', [
'event_id' => $this->event->id,
'first_name' => 'Jan',
'last_name' => 'de Vries',
'email' => 'jan@test.nl',
]);
}
public function test_update_status(): void
{
$person = Person::factory()->create([
'event_id' => $this->event->id,
'crowd_type_id' => $this->crowdType->id,
'status' => 'pending',
]);
Sanctum::actingAs($this->orgAdmin);
$response = $this->putJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/persons/{$person->id}", [
'status' => 'approved',
]);
$response->assertOk()
->assertJsonPath('data.status', 'approved');
}
public function test_approve_sets_status_to_approved(): void
{
$person = Person::factory()->create([
'event_id' => $this->event->id,
'crowd_type_id' => $this->crowdType->id,
'status' => 'pending',
]);
Sanctum::actingAs($this->orgAdmin);
$response = $this->postJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/persons/{$person->id}/approve");
$response->assertOk()
->assertJsonPath('data.status', 'approved');
$this->assertDatabaseHas('persons', [
'id' => $person->id,
'status' => 'approved',
]);
}
public function test_destroy_soft_deletes_person(): void
{
$person = Person::factory()->create([
'event_id' => $this->event->id,
'crowd_type_id' => $this->crowdType->id,
]);
Sanctum::actingAs($this->orgAdmin);
$response = $this->deleteJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/persons/{$person->id}");
$response->assertNoContent();
$this->assertSoftDeleted('persons', ['id' => $person->id]);
}
public function test_unauthenticated_returns_401(): void
{
$response = $this->getJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/persons");
$response->assertUnauthorized();
}
public function test_approve_creates_user_account_for_person(): void
{
$person = Person::factory()->create([
'event_id' => $this->event->id,
'crowd_type_id' => $this->crowdType->id,
'status' => 'pending',
'email' => 'volunteer@example.com',
]);
$this->assertNull($person->user_id);
Sanctum::actingAs($this->orgAdmin);
$response = $this->postJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/persons/{$person->id}/approve");
$response->assertOk();
$person->refresh();
$this->assertNotNull($person->user_id);
$this->assertDatabaseHas('users', ['email' => 'volunteer@example.com']);
}
public function test_approve_reuses_existing_user_account(): void
{
$existingUser = User::factory()->create(['email' => 'existing@example.com']);
$person = Person::factory()->create([
'event_id' => $this->event->id,
'crowd_type_id' => $this->crowdType->id,
'status' => 'pending',
'email' => 'existing@example.com',
]);
Sanctum::actingAs($this->orgAdmin);
$this->postJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/persons/{$person->id}/approve")
->assertOk();
$person->refresh();
$this->assertEquals($existingUser->id, $person->user_id);
}
}