22 new tests across four files:
- AdvanceSectionObserverTest (7) — counter recompute on create / status
transition / delete / is_open toggle no-op / orphaned-section guard /
no activity-log noise on counter writes
- ArtistResolverTest (4) — happy path / invalid token / soft-deleted
artist / SHA-256 digest verification
- ArtistAdvanceDefaultTest (6) — five-section + slug shape / idempotency
/ per-section field shape / observer-invocation outside tests /
artisan one-org + all-orgs paths
- EngagementPortalControllerTest (6) — show 200/404/410 / show-section
schema + draft values / submit happy-path with submission persistence
+ counter recompute / cross-engagement section returns 404
Implementation tweaks driven by test feedback:
- OrganisationObserver gated by `app()->runningUnitTests()` — auto-seed
runs in production but is silent in CI so existing FormSchema-counting
tests are unperturbed. Tests that need the seeded schema invoke
`ArtistAdvanceDefault::seedFor()` explicitly.
- EngagementPortalController idempotency_key uses `aa-` + sha1 prefix
(28 chars) so it fits the form_submissions.idempotency_key
varchar(30) column.
Test count: 1709 (Session 2 close) → 1731 (+22).
Larastan: 0 new errors over baseline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
146 lines
5.1 KiB
PHP
146 lines
5.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Feature\Artist;
|
|
|
|
use App\Enums\Artist\AdvanceSectionSubmissionStatus;
|
|
use App\Models\AdvanceSection;
|
|
use App\Models\Artist;
|
|
use App\Models\ArtistEngagement;
|
|
use App\Models\Event;
|
|
use App\Models\Organisation;
|
|
use App\Models\Scopes\OrganisationScope;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
final class AdvanceSectionObserverTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
public function test_create_increments_total_count(): void
|
|
{
|
|
$engagement = $this->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<string, mixed> $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));
|
|
}
|
|
}
|