seed(RoleSeeder::class); $this->service = $this->app->make(LaneCascadeService::class); $this->org = Organisation::factory()->create(); $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]); $this->engagement = ArtistEngagement::factory()->create([ 'artist_id' => $artist->id, 'event_id' => $this->event->id, ]); } public function test_simple_move_no_overlap_succeeds(): void { $perf = Performance::factory()->create([ 'engagement_id' => $this->engagement->id, 'event_id' => $this->event->id, 'stage_id' => $this->stage->id, 'lane' => 0, 'version' => 0, ]); $start = CarbonImmutable::now()->addDays(2)->setTime(20, 0); $result = $this->service->move( performance: $perf, targetStage: $this->stage, start: $start, end: $start->addHour(), targetLane: 0, clientVersion: 0, ); $this->assertSame([], $result->cascaded); $this->assertGreaterThan(0, $result->moved->version); } public function test_overlap_cascades_existing_to_higher_lane(): void { $start = CarbonImmutable::now()->addDays(3)->setTime(22, 0); $existing = Performance::factory()->create([ 'engagement_id' => $this->engagement->id, 'event_id' => $this->event->id, 'stage_id' => $this->stage->id, 'lane' => 0, 'start_at' => $start, 'end_at' => $start->addHour(), 'version' => 0, ]); $other = Performance::factory()->create([ 'engagement_id' => $this->engagement->id, 'event_id' => $this->event->id, 'stage_id' => null, // parked 'lane' => 0, 'start_at' => $start, 'end_at' => $start->addHour(), 'version' => 0, ]); $result = $this->service->move( performance: $other, targetStage: $this->stage, start: $start->addMinutes(15), end: $start->addMinutes(75), targetLane: 0, clientVersion: 0, ); $this->assertCount(1, $result->cascaded); $this->assertSame((string) $existing->id, (string) $result->cascaded[0]->id); $this->assertSame(1, (int) $result->cascaded[0]->lane); } public function test_version_mismatch_throws(): void { $perf = Performance::factory()->create([ 'engagement_id' => $this->engagement->id, 'event_id' => $this->event->id, 'stage_id' => $this->stage->id, 'lane' => 0, 'version' => 5, ]); $this->expectException(VersionMismatchException::class); $this->service->move( performance: $perf, targetStage: $this->stage, start: CarbonImmutable::parse((string) $perf->start_at), end: CarbonImmutable::parse((string) $perf->end_at), targetLane: 0, clientVersion: 4, ); } public function test_park_clears_stage_id(): void { $perf = Performance::factory()->create([ 'engagement_id' => $this->engagement->id, 'event_id' => $this->event->id, 'stage_id' => $this->stage->id, 'lane' => 2, 'version' => 0, ]); $result = $this->service->move( performance: $perf, targetStage: null, start: null, end: null, targetLane: null, clientVersion: 0, ); $this->assertNull($result->moved->stage_id); $this->assertSame([], $result->cascaded); $this->assertSame(2, (int) $result->moved->lane); } public function test_unpark_to_stage_succeeds(): void { $perf = Performance::factory()->create([ 'engagement_id' => $this->engagement->id, 'event_id' => $this->event->id, 'stage_id' => null, 'lane' => 0, 'version' => 0, ]); $start = CarbonImmutable::now()->addDays(4)->setTime(21, 0); $result = $this->service->move( performance: $perf, targetStage: $this->stage, start: $start, end: $start->addHour(), targetLane: 0, clientVersion: 0, ); $this->assertSame((string) $this->stage->id, (string) $result->moved->stage_id); } }