trypost/tests/Feature/Permissions/WorkspaceRolePermissionsTest.php
Paulo Castellano 1c9ab462d0 feat(permissions): viewers review drafts in a read-only editor; lock /accounts to admins
Viewers are typically the client: they need to open a draft in the editor
to use the comments tab, but must not change anything.

- post editor (edit) now authorizes view, so viewers can open it; the
  composer + schedule tab render read-only and the comments tab stays
  interactive (defaults to the comments tab for viewers)
- all mutations stay member+ (update/delete) — the autosave/save/publish/
  schedule/delete affordances are hidden and the PUT is still 403 for
  viewers; SyncPostPlatforms only runs for users who can update
- drafts route to the editor for everyone again (reverts the read-only
  Show detour); Show stays the published-post view
- /accounts now authorizes manageAccounts (admin+), so viewers and members
  get 403; the Connections sidebar item is admin+ only and the connect/
  disconnect grid is reverted to main (no per-button gating needed)

Tests: draft→editor redirect for every member, viewer can open the editor,
viewer cannot save, and only admins+ can open /accounts.
2026-06-22 17:04:09 -03:00

97 lines
3.3 KiB
PHP

<?php
declare(strict_types=1);
use App\Enums\Post\Status;
use App\Enums\UserWorkspace\Role;
use App\Models\Post;
use App\Models\User;
use App\Models\Workspace;
beforeEach(function () {
$this->owner = User::factory()->create();
$this->workspace = Workspace::factory()->create([
'account_id' => $this->owner->account_id,
'user_id' => $this->owner->id,
]);
$this->owner->update(['current_workspace_id' => $this->workspace->id]);
$this->viewer = User::factory()->create([
'account_id' => $this->owner->account_id,
'current_workspace_id' => $this->workspace->id,
]);
$this->workspace->members()->attach($this->viewer->id, ['role' => Role::Viewer->value]);
$this->member = User::factory()->create([
'account_id' => $this->owner->account_id,
'current_workspace_id' => $this->workspace->id,
]);
$this->workspace->members()->attach($this->member->id, ['role' => Role::Member->value]);
$this->admin = User::factory()->create([
'account_id' => $this->owner->account_id,
'current_workspace_id' => $this->workspace->id,
]);
$this->workspace->members()->attach($this->admin->id, ['role' => Role::Admin->value]);
$this->post = Post::factory()->create(['workspace_id' => $this->workspace->id]);
});
test('a viewer cannot delete a post', function () {
$this->actingAs($this->viewer)
->delete(route('app.posts.destroy', $this->post))
->assertForbidden();
$this->assertDatabaseHas('posts', ['id' => $this->post->id]);
});
test('a viewer cannot create an automation', function () {
$this->actingAs($this->viewer)
->post(route('app.automations.store'))
->assertForbidden();
});
test('a member can create an automation', function () {
$this->actingAs($this->member)
->post(route('app.automations.store'))
->assertRedirect();
});
test('a viewer can comment on a post', function () {
$this->actingAs($this->viewer)
->postJson(route('app.posts.comments.store', $this->post), ['body' => 'Looks good!'])
->assertSuccessful();
$this->assertDatabaseHas('post_comments', [
'post_id' => $this->post->id,
'user_id' => $this->viewer->id,
]);
});
test('opening a draft post redirects to the editor for every workspace member', function (string $actor) {
$this->actingAs($this->{$actor})
->get(route('app.posts.show', $this->post))
->assertRedirect(route('app.posts.edit', $this->post));
})->with(['admin', 'member', 'viewer']);
test('a viewer can open the post editor to review and comment', function () {
$this->actingAs($this->viewer)
->get(route('app.posts.edit', $this->post))
->assertOk();
});
test('a viewer cannot save changes to a post', function () {
$this->actingAs($this->viewer)
->put(route('app.posts.update', $this->post), ['status' => Status::Draft->value])
->assertForbidden();
});
test('only admins and above can open the connections screen', function (string $actor, bool $allowed) {
$response = $this->actingAs($this->{$actor})->get(route('app.accounts'));
$allowed ? $response->assertOk() : $response->assertForbidden();
})->with([
'admin' => ['admin', true],
'member' => ['member', false],
'viewer' => ['viewer', false],
]);