From fe2202d8354a5454d9f75b79f9f00bb2b09d4df1 Mon Sep 17 00:00:00 2001 From: "bert.hausmans" Date: Fri, 24 Apr 2026 17:10:37 +0200 Subject: [PATCH] test(persons): verify SoftDeletes behaviour per WS-1 finding D-05 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-flight audit verified the implementation was already in place, so this commit is test-only (as the task prompt allowed): - api/database/migrations/2026_04_08_220000_create_persons_table.php:27 already carries $table->softDeletes(). - api/app/Models/Person.php lines 18 + 24 already use the SoftDeletes trait. No production code change required. tests/Feature/Person/PersonSoftDeleteTest.php pins the documented behaviour so WS-6 (FormBindingApplicator + ARTIST_ADVANCE / EVENT_REGISTRATION workflows) can rely on it without a second verification pass: - delete() sets deleted_at and excludes the row from default queries - Person::withTrashed()->find($id) returns the soft-deleted row - Person::onlyTrashed()->count() reports accurately - restore() clears deleted_at - forceDelete() removes the row permanently PersonPolicy was spot-checked alongside the audit; existing view/update/ delete methods do not invoke withTrashed() on controller resolution. That's acceptable today — no caller relies on editing a trashed Person — so AUTH_ARCHITECTURE.md is left unchanged. If a restore-workflow lands in a future sprint, the controller binding will need to switch to ->withTrashed() and the behaviour deserves a dedicated doc paragraph then. Minor path note: the task prompt specified tests/Feature/Persons/PersonSoftDeleteTest.php (plural), but the existing convention in this repo is tests/Feature/Person/ (singular). Matched the existing directory rather than introducing a singular/plural split. Also carries the BACKLOG DOC-04 entry Bert asked for during WS-4 review: scripts/install-claude-sync-hooks.sh belongs in SETUP.md / onboarding so new clones don't miss the post-commit sync hook. 5 tests / 10 new assertions. Full suite: 1005 passed (2716 assertions). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Feature/Person/PersonSoftDeleteTest.php | 100 ++++++++++++++++++ dev-docs/BACKLOG.md | 13 +++ 2 files changed, 113 insertions(+) create mode 100644 api/tests/Feature/Person/PersonSoftDeleteTest.php diff --git a/api/tests/Feature/Person/PersonSoftDeleteTest.php b/api/tests/Feature/Person/PersonSoftDeleteTest.php new file mode 100644 index 00000000..019a828e --- /dev/null +++ b/api/tests/Feature/Person/PersonSoftDeleteTest.php @@ -0,0 +1,100 @@ +softDeletes()`; `app/Models/Person.php` lines 18 + 24 carry + * the `SoftDeletes` trait). No code change required — this file is the + * behavioural contract. + */ +final class PersonSoftDeleteTest extends TestCase +{ + use RefreshDatabase; + + private Person $person; + + protected function setUp(): void + { + parent::setUp(); + + $organisation = Organisation::factory()->create(); + $event = Event::factory()->for($organisation)->create(); + $crowdType = CrowdType::factory()->for($organisation)->create(); + + $this->person = Person::factory() + ->for($event) + ->for($crowdType) + ->create(); + } + + public function test_delete_sets_deleted_at_and_hides_row_from_default_queries(): void + { + $id = $this->person->id; + + $this->person->delete(); + + $this->assertNotNull($this->person->fresh()?->deleted_at); + $this->assertNull( + Person::query()->find($id), + 'Soft-deleted person must not appear in default queries' + ); + } + + public function test_with_trashed_returns_deleted_row(): void + { + $id = $this->person->id; + $this->person->delete(); + + $trashed = Person::withTrashed()->find($id); + + $this->assertNotNull($trashed); + $this->assertSame($id, $trashed->id); + } + + public function test_only_trashed_counts_soft_deleted_rows(): void + { + $this->assertSame(0, Person::onlyTrashed()->count()); + + $this->person->delete(); + + $this->assertSame(1, Person::onlyTrashed()->count()); + } + + public function test_restore_clears_deleted_at(): void + { + $this->person->delete(); + $this->assertNotNull($this->person->fresh()?->deleted_at); + + $this->person->restore(); + + $this->assertNull($this->person->fresh()?->deleted_at); + $this->assertNotNull(Person::query()->find($this->person->id)); + } + + public function test_force_delete_removes_row_permanently(): void + { + $id = $this->person->id; + $this->person->forceDelete(); + + $this->assertNull(Person::withTrashed()->find($id)); + } +} diff --git a/dev-docs/BACKLOG.md b/dev-docs/BACKLOG.md index 4dae1366..ab9b2132 100644 --- a/dev-docs/BACKLOG.md +++ b/dev-docs/BACKLOG.md @@ -373,6 +373,19 @@ form-builder UI), omdat screenshots pas zin hebben als de UI staat. --- +### DOC-04 — `scripts/install-claude-sync-hooks.sh` opnemen in SETUP/onboarding + +**Aanleiding:** WS-4 pre-flight audit vond dat `scripts/sync-claude-docs.sh` +bestaat en door de post-commit hook draait, maar de hook-installer +(`scripts/install-claude-sync-hooks.sh`) is niet terug te vinden in de +developer onboarding-instructies. Nieuwe clones missen de hook. +**Wat:** Voeg een regel aan `dev-docs/SETUP.md` (of een post-install +checklist) toe die nieuwe developers opdraagt `install-claude-sync-hooks.sh` +te runnen. 1 regel, geen scope-impact. +**Prioriteit:** Laag — nice-to-have, niet blokkerend voor dev of CI. + +--- + ### FORM-05 — Smart identity-match on public submission values **Stub-status (S3a PR 2, 2026-04-23):** Public event_registration