makeEngagement(['advancing_completed_count' => 0, 'advancing_total_count' => 0]); AdvanceSection::factory()->create(['engagement_id' => $engagement->id]); $fresh = $engagement->fresh(); $this->assertSame(1, (int) $fresh->advancing_total_count); $this->assertSame(0, (int) $fresh->advancing_completed_count); } public function test_status_transition_to_approved_increments_completed(): void { $engagement = $this->makeEngagement(); $section = AdvanceSection::factory()->create([ 'engagement_id' => $engagement->id, 'submission_status' => AdvanceSectionSubmissionStatus::Pending, ]); $section->submission_status = AdvanceSectionSubmissionStatus::Approved->value; $section->save(); $fresh = $engagement->fresh(); $this->assertSame(1, (int) $fresh->advancing_total_count); $this->assertSame(1, (int) $fresh->advancing_completed_count); } public function test_status_transition_away_from_approved_decrements_completed(): void { $engagement = $this->makeEngagement(); $section = AdvanceSection::factory()->create([ 'engagement_id' => $engagement->id, 'submission_status' => AdvanceSectionSubmissionStatus::Approved, ]); $this->assertSame(1, (int) $engagement->fresh()->advancing_completed_count); $section->submission_status = AdvanceSectionSubmissionStatus::Pending->value; $section->save(); $this->assertSame(0, (int) $engagement->fresh()->advancing_completed_count); $this->assertSame(1, (int) $engagement->fresh()->advancing_total_count); } public function test_delete_decrements_total(): void { $engagement = $this->makeEngagement(); $sectionA = AdvanceSection::factory()->create(['engagement_id' => $engagement->id]); $sectionB = AdvanceSection::factory()->create(['engagement_id' => $engagement->id]); $this->assertSame(2, (int) $engagement->fresh()->advancing_total_count); $sectionA->delete(); $this->assertSame(1, (int) $engagement->fresh()->advancing_total_count); } public function test_is_open_toggle_does_not_recompute(): void { $engagement = $this->makeEngagement(); $section = AdvanceSection::factory()->create([ 'engagement_id' => $engagement->id, 'is_open' => false, ]); $startTotal = (int) $engagement->fresh()->advancing_total_count; $startCompleted = (int) $engagement->fresh()->advancing_completed_count; $section->is_open = true; $section->save(); $this->assertSame($startTotal, (int) $engagement->fresh()->advancing_total_count); $this->assertSame($startCompleted, (int) $engagement->fresh()->advancing_completed_count); } public function test_recompute_skips_when_engagement_already_force_deleted(): void { $engagement = $this->makeEngagement(); $section = AdvanceSection::factory()->create(['engagement_id' => $engagement->id]); ArtistEngagement::query() ->withoutGlobalScope(OrganisationScope::class) ->whereKey($engagement->id) ->forceDelete(); // Force-deleting via raw query bypasses cascade observer; section // is now orphaned. The observer should no-op rather than crash // when the parent is gone. $section->delete(); $this->expectNotToPerformAssertions(); } public function test_counter_writes_do_not_emit_activity(): void { $engagement = $this->makeEngagement(); $logsBefore = \Spatie\Activitylog\Models\Activity::query() ->where('subject_id', $engagement->id) ->count(); AdvanceSection::factory()->create(['engagement_id' => $engagement->id]); $logsAfter = \Spatie\Activitylog\Models\Activity::query() ->where('subject_id', $engagement->id) ->count(); $this->assertSame($logsBefore, $logsAfter, 'Counter sync must not emit activity-log entries on the engagement.'); } /** * @param array $overrides */ private function makeEngagement(array $overrides = []): ArtistEngagement { $org = Organisation::factory()->create(); $event = Event::factory()->for($org)->create(); $artist = Artist::factory()->for($org)->create(); return ArtistEngagement::factory()->create(array_merge([ 'organisation_id' => $org->id, 'artist_id' => $artist->id, 'event_id' => $event->id, ], $overrides)); } }