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:
2026-08-19 17:55:27 +02:00
commit 3897113313
534 changed files with 64050 additions and 0 deletions

View 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);
}
}