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>
243 lines
7.8 KiB
PHP
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);
|
|
}
|
|
}
|