From ae6a4304f91e70368626e7f01c0d3f23d7a6894b Mon Sep 17 00:00:00 2001 From: Daan Meijer Date: Wed, 17 Jun 2026 00:29:40 +0200 Subject: [PATCH] feat: Restrict ledger creation exclusively to dynamic owners and fix route matching conflicts --- app/Http/Requests/StoreLedgerRequest.php | 2 +- routes/web.php | 2 +- tests/Feature/LedgerTest.php | 57 ++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 tests/Feature/LedgerTest.php diff --git a/app/Http/Requests/StoreLedgerRequest.php b/app/Http/Requests/StoreLedgerRequest.php index 8f47082..5249c21 100644 --- a/app/Http/Requests/StoreLedgerRequest.php +++ b/app/Http/Requests/StoreLedgerRequest.php @@ -14,7 +14,7 @@ class StoreLedgerRequest extends FormRequest { $dynamic = $this->route('dynamic'); - return $dynamic && $this->user()->can('view', $dynamic); + return $dynamic && $this->user()->can('update', $dynamic); } /** diff --git a/routes/web.php b/routes/web.php index 247c397..3670c34 100644 --- a/routes/web.php +++ b/routes/web.php @@ -16,8 +16,8 @@ Route::middleware(['auth', 'verified'])->group(function () { Route::get('/dynamics/{dynamic}/settings', [DynamicController::class, 'edit'])->name('dynamics.edit'); Route::patch('/dynamics/{dynamic}/settings', [DynamicController::class, 'update'])->name('dynamics.update'); - Route::resource('dynamics.ledgers', LedgerController::class)->scoped()->except(['create']); Route::get('/dynamics/{dynamic}/ledgers/create', [LedgerController::class, 'create'])->name('dynamics.ledgers.create'); + Route::resource('dynamics.ledgers', LedgerController::class)->scoped()->except(['create']); Route::resource('dynamics.ledgers.mutations', MutationController::class)->scoped(); diff --git a/tests/Feature/LedgerTest.php b/tests/Feature/LedgerTest.php new file mode 100644 index 0000000..c2636f4 --- /dev/null +++ b/tests/Feature/LedgerTest.php @@ -0,0 +1,57 @@ +create(); + $dynamic = Dynamic::factory()->create(); + $dynamic->participants()->attach($owner->id, ['role' => 'owner']); + + $this->actingAs($owner); + + // Can view form + $this->get(route('dynamics.ledgers.create', $dynamic->id))->assertOk(); + + // Can store ledger + $response = $this->post(route('dynamics.ledgers.store', $dynamic->id), [ + 'name' => 'Chores Ledger', + 'rules' => 'Do the tasks.', + 'alignment' => 'positive', + ]); + + $response->assertSessionHasNoErrors(); + $response->assertRedirect(route('dynamics.show', $dynamic->id)); + + $this->assertDatabaseHas('ledgers', [ + 'dynamic_id' => $dynamic->id, + 'name' => 'Chores Ledger', + 'alignment' => 'positive', + ]); +}); + +test('non-owners cannot view ledger creation form or store ledgers', function () { + $owner = User::factory()->create(); + $participant = User::factory()->create(); + $dynamic = Dynamic::factory()->create(); + $dynamic->participants()->attach($owner->id, ['role' => 'owner']); + $dynamic->participants()->attach($participant->id, ['role' => 'participant']); + + $this->actingAs($participant); + + // Cannot view form + $this->get(route('dynamics.ledgers.create', $dynamic->id))->assertStatus(403); + + // Cannot store ledger + $response = $this->post(route('dynamics.ledgers.store', $dynamic->id), [ + 'name' => 'Illegal Ledger', + 'rules' => 'This should fail.', + 'alignment' => 'positive', + ]); + + $response->assertStatus(403); + $this->assertDatabaseMissing('ledgers', [ + 'name' => 'Illegal Ledger', + ]); +});