Initial commit: Pireon Admin — headless admin panel and public API
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>
This commit is contained in:
155
tests/Feature/Panel/CollectiveResourcesTest.php
Normal file
155
tests/Feature/Panel/CollectiveResourcesTest.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user