advance_submissions.advance_section_id FK changed from cascadeOnDelete
to nullOnDelete; column made nullable. Aligns implementation with
RFC v0.2 §5.4 audit-immutability ("submissions remain for retention
compliance") — when ArtistEngagementObserver::deleted hard-deletes a
section, its submissions persist as orphans rather than disappearing.
Migration edited in place (branch unpushed, dev-only). Observer
docblock + test assertion updated to match. Removed pre-existing
follow-up comment that documented the deviation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
116 lines
4.1 KiB
PHP
116 lines
4.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Feature\Artist;
|
|
|
|
use App\Enums\Artist\ArtistEngagementStatus;
|
|
use App\Exceptions\Artist\CrossTenantEngagementException;
|
|
use App\Models\AdvanceSection;
|
|
use App\Models\AdvanceSubmission;
|
|
use App\Models\Artist;
|
|
use App\Models\ArtistEngagement;
|
|
use App\Models\Event;
|
|
use App\Models\Organisation;
|
|
use App\Models\Performance;
|
|
use App\Models\Scopes\OrganisationScope;
|
|
use App\Models\Stage;
|
|
use Carbon\CarbonImmutable;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
final class ArtistEngagementObserverTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
public function test_creating_auto_fills_organisation_id_from_artist(): void
|
|
{
|
|
$org = Organisation::factory()->create();
|
|
$event = Event::factory()->for($org)->create();
|
|
$artist = Artist::factory()->create(['organisation_id' => $org->id]);
|
|
|
|
$eng = ArtistEngagement::create([
|
|
'artist_id' => $artist->id,
|
|
'event_id' => $event->id,
|
|
'booking_status' => ArtistEngagementStatus::Draft->value,
|
|
]);
|
|
|
|
$this->assertSame($org->id, $eng->fresh()->organisation_id);
|
|
}
|
|
|
|
public function test_cross_tenant_engagement_throws(): void
|
|
{
|
|
$orgA = Organisation::factory()->create();
|
|
$orgB = Organisation::factory()->create();
|
|
$artist = Artist::factory()->create(['organisation_id' => $orgA->id]);
|
|
$eventInOtherOrg = Event::factory()->for($orgB)->create();
|
|
|
|
$this->expectException(CrossTenantEngagementException::class);
|
|
|
|
ArtistEngagement::create([
|
|
'artist_id' => $artist->id,
|
|
'event_id' => $eventInOtherOrg->id,
|
|
'booking_status' => ArtistEngagementStatus::Draft->value,
|
|
]);
|
|
}
|
|
|
|
public function test_soft_delete_cascades_to_performances_and_hard_deletes_advance_sections(): void
|
|
{
|
|
$org = Organisation::factory()->create();
|
|
$event = Event::factory()->for($org)->create();
|
|
$artist = Artist::factory()->create(['organisation_id' => $org->id]);
|
|
$eng = ArtistEngagement::create([
|
|
'artist_id' => $artist->id,
|
|
'event_id' => $event->id,
|
|
'booking_status' => ArtistEngagementStatus::Draft->value,
|
|
]);
|
|
$stage = Stage::factory()->for($event)->create();
|
|
$start = CarbonImmutable::now();
|
|
|
|
$perf = Performance::create([
|
|
'engagement_id' => $eng->id,
|
|
'event_id' => $event->id,
|
|
'stage_id' => $stage->id,
|
|
'start_at' => $start,
|
|
'end_at' => $start->addHour(),
|
|
]);
|
|
|
|
$section = AdvanceSection::create([
|
|
'engagement_id' => $eng->id,
|
|
'name' => 'Production',
|
|
'type' => 'production',
|
|
]);
|
|
|
|
$submission = AdvanceSubmission::create([
|
|
'advance_section_id' => $section->id,
|
|
'submitted_by_name' => 'TM',
|
|
'submitted_by_email' => 'tm@example.test',
|
|
'submitted_at' => now(),
|
|
'status' => 'pending',
|
|
'data' => [],
|
|
]);
|
|
|
|
$submissionCountBefore = AdvanceSubmission::withoutGlobalScope(OrganisationScope::class)->count();
|
|
|
|
$eng->delete();
|
|
|
|
// Performance is soft-deleted (trashed, not removed).
|
|
$this->assertNotNull(Performance::withoutGlobalScope(OrganisationScope::class)->withTrashed()->find($perf->id));
|
|
$this->assertSoftDeleted($perf);
|
|
|
|
// AdvanceSection is hard-deleted.
|
|
$this->assertNull(AdvanceSection::withoutGlobalScope(OrganisationScope::class)->find($section->id));
|
|
|
|
// AdvanceSubmission survives as audit-orphan per RFC §5.4: row
|
|
// preserved, advance_section_id nulled by FK ON DELETE SET NULL.
|
|
$this->assertSame(
|
|
$submissionCountBefore,
|
|
AdvanceSubmission::withoutGlobalScope(OrganisationScope::class)->count(),
|
|
'advance_submissions must persist for retention compliance'
|
|
);
|
|
$orphan = AdvanceSubmission::withoutGlobalScope(OrganisationScope::class)->find($submission->id);
|
|
$this->assertNotNull($orphan);
|
|
$this->assertNull($orphan->advance_section_id);
|
|
}
|
|
}
|