seed(RoleSeeder::class); $this->org = Organisation::factory()->create(); $this->orgAdmin = User::factory()->create(); $this->org->users()->attach($this->orgAdmin, ['role' => 'org_admin']); $this->event = Event::factory()->create([ 'organisation_id' => $this->org->id, 'start_date' => CarbonImmutable::now()->subDay(), 'end_date' => CarbonImmutable::now()->addDays(30), ]); $this->stage = Stage::factory()->create(['event_id' => $this->event->id]); StageDay::query()->create(['stage_id' => $this->stage->id, 'event_id' => $this->event->id]); $artist = Artist::factory()->create(['organisation_id' => $this->org->id]); $eng = ArtistEngagement::factory()->create([ 'artist_id' => $artist->id, 'event_id' => $this->event->id, ]); $start = CarbonImmutable::now()->addDays(2)->setTime(20, 0); $this->perf = Performance::factory()->create([ 'engagement_id' => $eng->id, 'event_id' => $this->event->id, 'stage_id' => $this->stage->id, 'lane' => 0, 'start_at' => $start, 'end_at' => $start->addHour(), 'version' => 0, ]); } private function url(): string { return "/api/v1/organisations/{$this->org->id}/events/{$this->event->id}/timetable/move"; } public function test_move_succeeds_with_idempotency_key(): void { Sanctum::actingAs($this->orgAdmin); $newStart = CarbonImmutable::parse((string) $this->perf->start_at)->addHour(); $response = $this->postJson( $this->url(), [ 'performance_id' => $this->perf->id, 'target_stage_id' => $this->stage->id, 'target_start_at' => $newStart->format('Y-m-d H:i:s'), 'target_end_at' => $newStart->addHour()->format('Y-m-d H:i:s'), 'target_lane' => 0, 'version' => 0, ], ['Idempotency-Key' => 'test-1'], ); $response->assertOk(); } public function test_move_without_idempotency_key_returns_400(): void { Sanctum::actingAs($this->orgAdmin); $response = $this->postJson($this->url(), [ 'performance_id' => $this->perf->id, 'target_stage_id' => $this->stage->id, 'target_start_at' => '2026-07-10 22:00:00', 'target_end_at' => '2026-07-10 23:00:00', 'target_lane' => 0, 'version' => 0, ]); $response->assertStatus(400); } public function test_version_mismatch_returns_409(): void { Sanctum::actingAs($this->orgAdmin); $newStart = CarbonImmutable::parse((string) $this->perf->start_at)->addHour(); $response = $this->postJson( $this->url(), [ 'performance_id' => $this->perf->id, 'target_stage_id' => $this->stage->id, 'target_start_at' => $newStart->format('Y-m-d H:i:s'), 'target_end_at' => $newStart->addHour()->format('Y-m-d H:i:s'), 'target_lane' => 0, 'version' => 99, ], ['Idempotency-Key' => 'test-2'], ); $response->assertStatus(409); $this->assertSame('version_mismatch', $response->json('errors.conflict')); } }