237 lines
7.4 KiB
PHP
237 lines
7.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Feature\Event;
|
|
|
|
use App\Models\Event;
|
|
use App\Models\FestivalSection;
|
|
use App\Models\Organisation;
|
|
use App\Models\TimeSlot;
|
|
use App\Models\User;
|
|
use Database\Seeders\RoleSeeder;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Laravel\Sanctum\Sanctum;
|
|
use Tests\TestCase;
|
|
|
|
class EventStatusTransitionTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private User $orgAdmin;
|
|
private Organisation $organisation;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->seed(RoleSeeder::class);
|
|
|
|
$this->organisation = Organisation::factory()->create();
|
|
|
|
$this->orgAdmin = User::factory()->create();
|
|
$this->organisation->users()->attach($this->orgAdmin, ['role' => 'org_admin']);
|
|
}
|
|
|
|
private function transitionUrl(Event $event): string
|
|
{
|
|
return "/api/v1/organisations/{$this->organisation->id}/events/{$event->id}/transition";
|
|
}
|
|
|
|
// --- Valid transitions ---
|
|
|
|
public function test_transition_draft_to_published(): void
|
|
{
|
|
$event = Event::factory()->create([
|
|
'organisation_id' => $this->organisation->id,
|
|
'status' => 'draft',
|
|
]);
|
|
|
|
Sanctum::actingAs($this->orgAdmin);
|
|
|
|
$response = $this->postJson($this->transitionUrl($event), ['status' => 'published']);
|
|
|
|
$response->assertOk();
|
|
$this->assertDatabaseHas('events', ['id' => $event->id, 'status' => 'published']);
|
|
}
|
|
|
|
public function test_transition_published_to_registration_open(): void
|
|
{
|
|
$event = Event::factory()->create([
|
|
'organisation_id' => $this->organisation->id,
|
|
'status' => 'published',
|
|
]);
|
|
|
|
// Create prerequisites: time slot + section
|
|
TimeSlot::factory()->create(['event_id' => $event->id]);
|
|
FestivalSection::factory()->create(['event_id' => $event->id]);
|
|
|
|
Sanctum::actingAs($this->orgAdmin);
|
|
|
|
$response = $this->postJson($this->transitionUrl($event), ['status' => 'registration_open']);
|
|
|
|
$response->assertOk();
|
|
$this->assertDatabaseHas('events', ['id' => $event->id, 'status' => 'registration_open']);
|
|
}
|
|
|
|
public function test_transition_published_back_to_draft(): void
|
|
{
|
|
$event = Event::factory()->create([
|
|
'organisation_id' => $this->organisation->id,
|
|
'status' => 'published',
|
|
]);
|
|
|
|
Sanctum::actingAs($this->orgAdmin);
|
|
|
|
$response = $this->postJson($this->transitionUrl($event), ['status' => 'draft']);
|
|
|
|
$response->assertOk();
|
|
$this->assertDatabaseHas('events', ['id' => $event->id, 'status' => 'draft']);
|
|
}
|
|
|
|
// --- Invalid transitions ---
|
|
|
|
public function test_transition_draft_to_showday_fails(): void
|
|
{
|
|
$event = Event::factory()->create([
|
|
'organisation_id' => $this->organisation->id,
|
|
'status' => 'draft',
|
|
]);
|
|
|
|
Sanctum::actingAs($this->orgAdmin);
|
|
|
|
$response = $this->postJson($this->transitionUrl($event), ['status' => 'showday']);
|
|
|
|
$response->assertUnprocessable()
|
|
->assertJsonPath('current_status', 'draft')
|
|
->assertJsonPath('requested_status', 'showday');
|
|
}
|
|
|
|
public function test_transition_closed_to_anything_fails(): void
|
|
{
|
|
$event = Event::factory()->create([
|
|
'organisation_id' => $this->organisation->id,
|
|
'status' => 'closed',
|
|
]);
|
|
|
|
Sanctum::actingAs($this->orgAdmin);
|
|
|
|
$response = $this->postJson($this->transitionUrl($event), ['status' => 'draft']);
|
|
|
|
$response->assertUnprocessable()
|
|
->assertJsonPath('allowed_transitions', []);
|
|
}
|
|
|
|
// --- Prerequisite checks ---
|
|
|
|
public function test_transition_registration_open_without_timeslots_fails(): void
|
|
{
|
|
$event = Event::factory()->create([
|
|
'organisation_id' => $this->organisation->id,
|
|
'status' => 'published',
|
|
]);
|
|
|
|
// Create a section but no time slot
|
|
FestivalSection::factory()->create(['event_id' => $event->id]);
|
|
|
|
Sanctum::actingAs($this->orgAdmin);
|
|
|
|
$response = $this->postJson($this->transitionUrl($event), ['status' => 'registration_open']);
|
|
|
|
$response->assertUnprocessable();
|
|
$this->assertContains(
|
|
'At least one time slot must exist before opening registration.',
|
|
$response->json('errors')
|
|
);
|
|
}
|
|
|
|
// --- Regular update cannot change status ---
|
|
|
|
public function test_regular_update_cannot_change_status(): void
|
|
{
|
|
$event = Event::factory()->create([
|
|
'organisation_id' => $this->organisation->id,
|
|
'status' => 'draft',
|
|
]);
|
|
|
|
Sanctum::actingAs($this->orgAdmin);
|
|
|
|
$this->putJson("/api/v1/organisations/{$this->organisation->id}/events/{$event->id}", [
|
|
'status' => 'published',
|
|
]);
|
|
|
|
// Status should remain draft — the field is ignored by UpdateEventRequest
|
|
$this->assertDatabaseHas('events', ['id' => $event->id, 'status' => 'draft']);
|
|
}
|
|
|
|
// --- Festival cascade ---
|
|
|
|
public function test_festival_showday_cascades_to_children(): void
|
|
{
|
|
$festival = Event::factory()->festival()->create([
|
|
'organisation_id' => $this->organisation->id,
|
|
'status' => 'buildup',
|
|
]);
|
|
|
|
$childDraft = Event::factory()->subEvent($festival)->create(['status' => 'draft']);
|
|
$childPublished = Event::factory()->subEvent($festival)->create(['status' => 'published']);
|
|
$childBuildup = Event::factory()->subEvent($festival)->create(['status' => 'buildup']);
|
|
|
|
Sanctum::actingAs($this->orgAdmin);
|
|
|
|
$response = $this->postJson($this->transitionUrl($festival), ['status' => 'showday']);
|
|
|
|
$response->assertOk();
|
|
|
|
// All children in earlier statuses should be cascaded to showday
|
|
$this->assertDatabaseHas('events', ['id' => $childDraft->id, 'status' => 'showday']);
|
|
$this->assertDatabaseHas('events', ['id' => $childPublished->id, 'status' => 'showday']);
|
|
$this->assertDatabaseHas('events', ['id' => $childBuildup->id, 'status' => 'showday']);
|
|
}
|
|
|
|
// --- EventResource includes allowed_transitions ---
|
|
|
|
public function test_allowed_transitions_in_resource(): void
|
|
{
|
|
$event = Event::factory()->create([
|
|
'organisation_id' => $this->organisation->id,
|
|
'status' => 'draft',
|
|
]);
|
|
|
|
Sanctum::actingAs($this->orgAdmin);
|
|
|
|
$response = $this->getJson("/api/v1/organisations/{$this->organisation->id}/events/{$event->id}");
|
|
|
|
$response->assertOk()
|
|
->assertJsonPath('data.allowed_transitions', ['published']);
|
|
}
|
|
|
|
// --- Auth ---
|
|
|
|
public function test_unauthenticated_user_cannot_transition(): void
|
|
{
|
|
$event = Event::factory()->create([
|
|
'organisation_id' => $this->organisation->id,
|
|
'status' => 'draft',
|
|
]);
|
|
|
|
$response = $this->postJson($this->transitionUrl($event), ['status' => 'published']);
|
|
|
|
$response->assertUnauthorized();
|
|
}
|
|
|
|
public function test_outsider_cannot_transition(): void
|
|
{
|
|
$event = Event::factory()->create([
|
|
'organisation_id' => $this->organisation->id,
|
|
'status' => 'draft',
|
|
]);
|
|
|
|
$outsider = User::factory()->create();
|
|
Sanctum::actingAs($outsider);
|
|
|
|
$response = $this->postJson($this->transitionUrl($event), ['status' => 'published']);
|
|
|
|
$response->assertForbidden();
|
|
}
|
|
}
|