Laravel 13 + Filament 5 admin panel and REST API for a non-profit developer collective, backed by the Pireon OAuth identity server. The public site is a separate, server-rendered application consuming /api/public/v1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
156 lines
4.8 KiB
PHP
156 lines
4.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Feature\Panel;
|
|
|
|
use App\Filament\Resources\Calls\CallResource;
|
|
use App\Filament\Resources\Members\MemberResource;
|
|
use App\Filament\Resources\Projects\ProjectResource;
|
|
use App\Filament\Resources\Repositories\RepositoryResource;
|
|
use App\Filament\Resources\Technologies\TechnologyResource;
|
|
use App\Models\Call;
|
|
use App\Models\Member;
|
|
use App\Models\Project;
|
|
use App\Models\Repository;
|
|
use App\Models\Technology;
|
|
use App\Models\User;
|
|
use Database\Seeders\RolePermissionSeeder;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use Tests\FakesPireon;
|
|
use Tests\TestCase;
|
|
|
|
class CollectiveResourcesTest extends TestCase
|
|
{
|
|
use FakesPireon, RefreshDatabase;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->seed(RolePermissionSeeder::class);
|
|
}
|
|
|
|
#[Test]
|
|
public function every_collective_resource_renders(): void
|
|
{
|
|
Project::factory()->count(2)->create();
|
|
Member::factory()->count(2)->create();
|
|
Technology::factory()->count(2)->create();
|
|
Call::factory()->create(['project_id' => null]);
|
|
Repository::factory()->create();
|
|
|
|
$this->actingAs($this->userWithRoles('admin'));
|
|
|
|
foreach ([
|
|
ProjectResource::class,
|
|
MemberResource::class,
|
|
TechnologyResource::class,
|
|
CallResource::class,
|
|
RepositoryResource::class,
|
|
] as $resource) {
|
|
$this->get($resource::getUrl('index'))
|
|
->assertSuccessful();
|
|
}
|
|
}
|
|
|
|
#[Test]
|
|
public function an_editor_can_manage_content_but_not_sync_repositories(): void
|
|
{
|
|
$editor = $this->userWithRoles('editor');
|
|
|
|
$this->actingAs($editor);
|
|
|
|
$this->assertTrue($editor->can('projects.create'));
|
|
$this->assertTrue($editor->can('calls.create'));
|
|
$this->assertTrue($editor->can('members.update'));
|
|
|
|
// Syncing spends a shared rate limit, so it is gated separately from
|
|
// ordinary editing.
|
|
$this->assertTrue($editor->can('repositories.view_any'));
|
|
$this->assertFalse($editor->can('repositories.sync'));
|
|
$this->assertFalse($editor->can('repositories.create'));
|
|
}
|
|
|
|
#[Test]
|
|
public function a_viewer_cannot_reach_the_collective_editors(): void
|
|
{
|
|
$viewer = $this->userWithRoles('viewer');
|
|
|
|
$this->actingAs($viewer);
|
|
|
|
$this->assertTrue(ProjectResource::canViewAny());
|
|
$this->assertFalse(ProjectResource::canCreate());
|
|
$this->assertFalse(MemberResource::canCreate());
|
|
$this->assertFalse(TechnologyResource::canCreate());
|
|
}
|
|
|
|
#[Test]
|
|
public function publishing_a_project_is_a_separate_permission_from_editing_it(): void
|
|
{
|
|
$editor = $this->userWithRoles('editor');
|
|
$editor->roles()->first()->revokePermissionTo('projects.publish');
|
|
$editor->forgetCachedPermissions();
|
|
|
|
$project = Project::factory()->draft()->create();
|
|
|
|
$this->assertTrue($editor->can('update', $project));
|
|
$this->assertFalse($editor->can('publish', $project));
|
|
}
|
|
|
|
#[Test]
|
|
public function a_member_may_always_edit_their_own_profile(): void
|
|
{
|
|
// Someone with no member-editing permission at all.
|
|
$user = $this->userWithRoles('viewer');
|
|
$own = Member::factory()->create(['user_id' => $user->id]);
|
|
$other = Member::factory()->create();
|
|
|
|
$this->assertTrue($user->can('update', $own));
|
|
$this->assertFalse($user->can('update', $other));
|
|
}
|
|
|
|
#[Test]
|
|
public function a_technology_still_in_use_cannot_be_deleted(): void
|
|
{
|
|
$admin = $this->userWithRoles('admin');
|
|
|
|
$used = Technology::factory()->create();
|
|
$unused = Technology::factory()->create();
|
|
|
|
Project::factory()->create()->technologies()->attach($used);
|
|
|
|
// Deleting it would silently strip the tag across the whole site.
|
|
$this->assertFalse($admin->can('delete', $used));
|
|
$this->assertTrue($admin->can('delete', $unused));
|
|
}
|
|
|
|
#[Test]
|
|
public function a_member_is_not_a_panel_user(): void
|
|
{
|
|
$member = Member::factory()->create();
|
|
|
|
// The two are deliberately separate: most members never need admin
|
|
// access, and revoking admin must not remove someone from the website.
|
|
$this->assertNull($member->user_id);
|
|
$this->assertDatabaseCount('users', 0);
|
|
$this->assertTrue($member->is_public);
|
|
}
|
|
|
|
#[Test]
|
|
public function removing_a_panel_user_leaves_the_member_on_the_site(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$member = Member::factory()->create(['user_id' => $user->id]);
|
|
|
|
$user->delete();
|
|
|
|
$member->refresh();
|
|
|
|
$this->assertTrue($member->exists);
|
|
$this->assertNull($member->user_id);
|
|
$this->assertTrue($member->is_public);
|
|
}
|
|
}
|