Files
2026-04-18 17:02:56 +02:00

168 lines
6.9 KiB
PHP

<?php
use App\Models\Group;
use App\Models\GroupMember;
use App\Models\User;
use App\Policies\GroupPolicy;
use App\Services\GroupMembershipService;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
function attachGroupMember(Group $group, User $user, string $role): GroupMember
{
return GroupMember::query()->create([
'group_id' => $group->id,
'user_id' => $user->id,
'invited_by_user_id' => $group->owner_user_id,
'role' => $role,
'status' => Group::STATUS_ACTIVE,
'invited_at' => now(),
'accepted_at' => now(),
]);
}
it('allows contributors into studio but not management or publishing policy actions', function () {
$owner = User::factory()->create();
$contributor = User::factory()->create();
$group = Group::factory()->for($owner, 'owner')->create();
app(GroupMembershipService::class)->ensureOwnerMembership($group);
attachGroupMember($group, $contributor, Group::ROLE_MEMBER);
$policy = app(GroupPolicy::class);
expect($policy->viewStudio($contributor, $group))->toBeTrue()
->and($policy->update($contributor, $group))->toBeFalse()
->and($policy->manageMembers($contributor, $group))->toBeFalse()
->and($policy->publishArtworks($contributor, $group))->toBeFalse()
->and($policy->manageCollections($contributor, $group))->toBeFalse();
});
it('allows editors to publish artworks and manage collections without member administration', function () {
$owner = User::factory()->create();
$editor = User::factory()->create();
$group = Group::factory()->for($owner, 'owner')->create();
app(GroupMembershipService::class)->ensureOwnerMembership($group);
attachGroupMember($group, $editor, Group::ROLE_EDITOR);
$policy = app(GroupPolicy::class);
expect($policy->viewStudio($editor, $group))->toBeTrue()
->and($policy->publishArtworks($editor, $group))->toBeTrue()
->and($policy->manageCollections($editor, $group))->toBeTrue()
->and($policy->manageMembers($editor, $group))->toBeFalse()
->and($policy->archive($editor, $group))->toBeFalse();
});
it('allows admins to manage group settings and members but not archive ownership actions', function () {
$owner = User::factory()->create();
$admin = User::factory()->create();
$group = Group::factory()->for($owner, 'owner')->create();
app(GroupMembershipService::class)->ensureOwnerMembership($group);
attachGroupMember($group, $admin, Group::ROLE_ADMIN);
$policy = app(GroupPolicy::class);
expect($policy->viewStudio($admin, $group))->toBeTrue()
->and($policy->update($admin, $group))->toBeTrue()
->and($policy->manageMembers($admin, $group))->toBeTrue()
->and($policy->publishArtworks($admin, $group))->toBeTrue()
->and($policy->archive($admin, $group))->toBeFalse();
});
it('blocks studio access for suspended groups even for active non-owner members', function () {
$owner = User::factory()->create();
$editor = User::factory()->create();
$group = Group::factory()->for($owner, 'owner')->create([
'status' => Group::LIFECYCLE_SUSPENDED,
]);
app(GroupMembershipService::class)->ensureOwnerMembership($group);
attachGroupMember($group, $editor, Group::ROLE_EDITOR);
$policy = app(GroupPolicy::class);
expect($policy->viewStudio($editor, $group))->toBeFalse()
->and($policy->publishArtworks($editor, $group))->toBeFalse()
->and($policy->manageCollections($editor, $group))->toBeFalse();
});
it('keeps archive authority with the owner', function () {
$owner = User::factory()->create();
$group = Group::factory()->for($owner, 'owner')->create();
app(GroupMembershipService::class)->ensureOwnerMembership($group);
$policy = app(GroupPolicy::class);
expect($policy->update($owner, $group))->toBeTrue()
->and($policy->manageMembers($owner, $group))->toBeTrue()
->and($policy->publishArtworks($owner, $group))->toBeTrue()
->and($policy->archive($owner, $group))->toBeTrue();
});
it('does not allow admins or editors to transfer ownership through policy update access alone', function () {
$owner = User::factory()->create();
$admin = User::factory()->create();
$editor = User::factory()->create();
$group = Group::factory()->for($owner, 'owner')->create();
app(GroupMembershipService::class)->ensureOwnerMembership($group);
attachGroupMember($group, $admin, Group::ROLE_ADMIN);
attachGroupMember($group, $editor, Group::ROLE_EDITOR);
$policy = app(GroupPolicy::class);
expect($policy->update($admin, $group))->toBeTrue()
->and($policy->archive($admin, $group))->toBeFalse()
->and($policy->manageMembers($editor, $group))->toBeFalse();
});
it('exposes explicit v3 event and private access policy hooks', function () {
$owner = User::factory()->create();
$editor = User::factory()->create();
$member = User::factory()->create();
$group = Group::factory()->for($owner, 'owner')->create();
app(GroupMembershipService::class)->ensureOwnerMembership($group);
attachGroupMember($group, $editor, Group::ROLE_EDITOR);
attachGroupMember($group, $member, Group::ROLE_MEMBER);
$policy = app(GroupPolicy::class);
expect($policy->publishEventUpdates($editor, $group))->toBeTrue()
->and($policy->viewInternalEvents($editor, $group))->toBeTrue()
->and($policy->viewPrivateProject($editor, $group))->toBeTrue()
->and($policy->participateInChallenge($member, $group))->toBeTrue()
->and($policy->publishEventUpdates($member, $group))->toBeFalse();
});
it('exposes explicit v4 release, milestone, badge, and trust policy hooks', function () {
$owner = User::factory()->create();
$admin = User::factory()->create();
$editor = User::factory()->create();
$member = User::factory()->create();
$group = Group::factory()->for($owner, 'owner')->create();
app(GroupMembershipService::class)->ensureOwnerMembership($group);
attachGroupMember($group, $admin, Group::ROLE_ADMIN);
attachGroupMember($group, $editor, Group::ROLE_EDITOR);
attachGroupMember($group, $member, Group::ROLE_MEMBER);
$policy = app(GroupPolicy::class);
expect($policy->manageReleases($editor, $group))->toBeTrue()
->and($policy->publishReleases($editor, $group))->toBeTrue()
->and($policy->moveReleaseStage($editor, $group))->toBeTrue()
->and($policy->manageMilestones($editor, $group))->toBeTrue()
->and($policy->assignReleaseLead($editor, $group))->toBeTrue()
->and($policy->viewReputationDashboard($editor, $group))->toBeFalse()
->and($policy->manageBadges($admin, $group))->toBeTrue()
->and($policy->viewInternalTrustMetrics($admin, $group))->toBeTrue()
->and($policy->featureRelease($admin, $group))->toBeTrue()
->and($policy->manageReleases($member, $group))->toBeFalse()
->and($policy->viewReputationDashboard($member, $group))->toBeFalse();
});