onboarded()->create(); $space = $owner->ownedSpaces()->create(['name' => 'Acme', 'personal' => false]); $member = User::factory()->onboarded()->create(); $space->members()->attach($member->id, ['role' => 'member']); $member->forceFill(['current_space_id' => $space->id])->save(); return [$owner, $space, $member]; } it('lets a member open an owner-owned account detail page', function () { [$owner, $space, $member] = memberOf(); $account = Account::factory()->for($owner)->create(['space_id' => $space->id]); $this->actingAs($member)->get("/accounts/{$account->id}") ->assertOk() ->assertInertia(fn (AssertableInertia $page) => $page->component('Accounts/Show')); }); it('lets a member recategorize a transaction using the space\'s category', function () { [$owner, $space, $member] = memberOf(); $account = Account::factory()->for($owner)->create(['space_id' => $space->id]); $transaction = Transaction::factory()->for($account)->create(['user_id' => $owner->id]); $category = Category::factory()->for($owner)->create(['space_id' => $space->id]); // Exercises both the space-membership policy and the space-scoped ownership // validation on category_id (which used to be user_id-scoped → 422 here). $this->actingAs($member) ->patchJson("/transactions/{$transaction->id}", ['category_id' => $category->id]) ->assertOk(); expect($transaction->fresh()->category_id)->toBe($category->id); }); it('denies a removed member access to the space\'s data', function () { [$owner, $space, $member] = memberOf(); $account = Account::factory()->for($owner)->create(['space_id' => $space->id]); $space->members()->detach($member->id); $this->actingAs($member)->get("/accounts/{$account->id}") ->assertForbidden(); });