trypost/tests/Unit/Policies/PostPolicyTest.php
Paulo Castellano 5bb39da598 fix(permissions): enforce workspace roles across backend and UI
Viewers could mutate posts, automations and trigger AI write endpoints,
and every role saw create/manage affordances that 403'd on click.

Backend (security):
- PostPolicy update/delete now require member+ (was tenancy-only), which
  also gates the AI write endpoints that authorize('update')
- AutomationPolicy create/update/delete require member+; activate/pause
  delegate to update
- AutomationController authorizes index/store/show; AnalyticsController
  authorizes view
- Comments stay open to members incl. viewer (by design)

Frontend (UI gating via new useWorkspaceRole composable):
- Sidebar: create post / create workspace / automations / library nav
- Accounts grid: connect / disconnect / reconnect (admin+)
- Members: invite / change role / remove / cancel invite (admin+)
- Account billing tab (owner); posts index + calendar create affordances

Tests: PostPolicyTest, AutomationPolicyTest (all four roles) and an
end-to-end WorkspaceRolePermissionsTest; aligned the automation test
suites' account/workspace setup with role pivots.
2026-06-22 16:31:54 -03:00

56 lines
1.7 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\Account;
use App\Models\Post;
use App\Models\User;
use App\Models\Workspace;
use App\Policies\PostPolicy;
beforeEach(function () {
$this->policy = new PostPolicy;
});
/**
* Build a post + an actor with the given workspace role, both in one account.
*
* @return array{0: User, 1: Post}
*/
function postPolicyActor(string $role): array
{
$account = Account::factory()->create();
$owner = User::factory()->create(['account_id' => $account->id]);
$account->update(['owner_id' => $owner->id]);
$workspace = Workspace::factory()->create(['account_id' => $account->id, 'user_id' => $owner->id]);
$post = Post::factory()->create(['workspace_id' => $workspace->id]);
if ($role === 'owner') {
$actor = $owner;
} else {
$actor = User::factory()->create(['account_id' => $account->id]);
$workspace->members()->attach($actor->id, ['role' => $role]);
}
$actor->update(['current_workspace_id' => $workspace->id]);
return [$actor->refresh(), $post];
}
test('any workspace member (including viewer) can view a post', function (string $role) {
[$actor, $post] = postPolicyActor($role);
expect($this->policy->view($actor, $post))->toBeTrue();
})->with(['owner', 'admin', 'member', 'viewer']);
test('post update/delete is allowed for member+ and denied for viewer', function (string $role, bool $allowed) {
[$actor, $post] = postPolicyActor($role);
expect($this->policy->update($actor, $post))->toBe($allowed);
expect($this->policy->delete($actor, $post))->toBe($allowed);
})->with([
'owner' => ['owner', true],
'admin' => ['admin', true],
'member' => ['member', true],
'viewer' => ['viewer', false],
]);