seed(RoleSeeder::class); $this->service = $this->app->make(ArtistEngagementService::class); $this->org = Organisation::factory()->create(); $this->event = Event::factory()->create(['organisation_id' => $this->org->id]); $this->artist = Artist::factory()->create(['organisation_id' => $this->org->id]); } public function test_rejected_is_terminal(): void { $eng = ArtistEngagement::factory()->create([ 'artist_id' => $this->artist->id, 'event_id' => $this->event->id, 'booking_status' => ArtistEngagementStatus::Rejected, ]); $this->expectException(InvalidStatusTransitionException::class); $this->service->transitionStatus($eng, ArtistEngagementStatus::Contracted); } public function test_declined_is_terminal(): void { $eng = ArtistEngagement::factory()->create([ 'artist_id' => $this->artist->id, 'event_id' => $this->event->id, 'booking_status' => ArtistEngagementStatus::Declined, ]); $this->expectException(InvalidStatusTransitionException::class); $this->service->transitionStatus($eng, ArtistEngagementStatus::Draft); } public function test_cancelled_is_terminal(): void { $eng = ArtistEngagement::factory()->create([ 'artist_id' => $this->artist->id, 'event_id' => $this->event->id, 'booking_status' => ArtistEngagementStatus::Cancelled, ]); $this->expectException(InvalidStatusTransitionException::class); $this->service->transitionStatus($eng, ArtistEngagementStatus::Confirmed); } public function test_option_requires_future_expiry(): void { $eng = ArtistEngagement::factory()->create([ 'artist_id' => $this->artist->id, 'event_id' => $this->event->id, 'booking_status' => ArtistEngagementStatus::Draft, 'option_expires_at' => null, ]); $this->expectException(InvalidStatusTransitionException::class); $this->service->transitionStatus($eng, ArtistEngagementStatus::Option); } public function test_option_with_past_expiry_blocked(): void { $eng = ArtistEngagement::factory()->create([ 'artist_id' => $this->artist->id, 'event_id' => $this->event->id, 'booking_status' => ArtistEngagementStatus::Draft, 'option_expires_at' => now()->subHour(), ]); $this->expectException(InvalidStatusTransitionException::class); $this->service->transitionStatus($eng, ArtistEngagementStatus::Option); } public function test_contracted_requires_fee(): void { $eng = ArtistEngagement::factory()->create([ 'artist_id' => $this->artist->id, 'event_id' => $this->event->id, 'booking_status' => ArtistEngagementStatus::Confirmed, 'fee_amount' => null, ]); $this->expectException(InvalidStatusTransitionException::class); $this->service->transitionStatus($eng, ArtistEngagementStatus::Contracted); } public function test_happy_path_sequence_permitted(): void { $eng = ArtistEngagement::factory()->create([ 'artist_id' => $this->artist->id, 'event_id' => $this->event->id, 'booking_status' => ArtistEngagementStatus::Draft, 'fee_amount' => 1500.00, 'option_expires_at' => now()->addDays(14), ]); foreach ([ ArtistEngagementStatus::Requested, ArtistEngagementStatus::Option, ArtistEngagementStatus::Offered, ArtistEngagementStatus::Confirmed, ArtistEngagementStatus::Contracted, ] as $next) { $this->service->transitionStatus($eng, $next); $this->assertSame($next, $eng->refresh()->booking_status); } } public function test_cancel_transitions_and_soft_deletes(): void { $eng = ArtistEngagement::factory()->create([ 'artist_id' => $this->artist->id, 'event_id' => $this->event->id, 'booking_status' => ArtistEngagementStatus::Confirmed, ]); $this->service->cancel($eng); $reloaded = ArtistEngagement::withoutGlobalScopes()->withTrashed()->find($eng->id); $this->assertNotNull($reloaded); $this->assertSame(ArtistEngagementStatus::Cancelled, $reloaded->booking_status); $this->assertNotNull($reloaded->deleted_at); } }