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,198 @@
<?php
declare(strict_types=1);
namespace Tests\Feature\Panel;
use App\Enums\PageStatus;
use App\Filament\Resources\Pages\PageResource;
use App\Models\Page;
use App\Models\Project;
use App\Models\Revision;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
use PHPUnit\Framework\Attributes\Test;
use Tests\FakesPireon;
use Tests\TestCase;
/**
* Who changed what.
*
* The gap this fills is publishing above all: `pages.publish` is a separate
* permission precisely because putting something on the public site is a
* bigger act than writing it, and nothing recorded who did it.
*/
class RevisionsTest extends TestCase
{
use FakesPireon, RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
$this->seed(RolePermissionSeeder::class);
Queue::fake();
}
#[Test]
public function creating_something_is_recorded(): void
{
$page = Page::factory()->create();
$this->assertSame('created', $page->revisions()->sole()->event);
}
#[Test]
public function it_records_who_made_the_change_by_name(): void
{
$user = $this->userWithRoles('admin', ['name' => 'María']);
$this->actingAs($user);
$page = Page::factory()->create();
$page->update(['title' => ['es' => 'Nuevo título']]);
$revision = $page->revisions()->where('event', 'updated')->sole();
$this->assertSame($user->id, $revision->user_id);
// The name is kept beside the id because people leave and accounts get
// deleted, and "somebody published this" is worth less than a name.
$this->assertSame('María', $revision->user_name);
$this->assertSame('María', $revision->author());
}
#[Test]
public function a_change_made_by_no_one_says_so(): void
{
$page = Page::factory()->create();
$page->update(['title' => ['es' => 'Cambiado por un comando']]);
// The scheduler and the console have no user, and blaming the last
// person who logged in would be a lie.
$this->assertNull($page->revisions()->first()->user_id);
$this->assertSame(__('panel.revisions.system'), $page->revisions()->first()->author());
}
#[Test]
public function publishing_reads_differently_from_editing(): void
{
$this->actingAs($this->userWithRoles('admin'));
$page = Page::factory()->create([
'status' => PageStatus::Draft,
'published_at' => null,
]);
$page->update(['status' => PageStatus::Published, 'published_at' => now()->subMinute()]);
$this->assertSame('published', $page->revisions()->first()->event);
$page->update(['status' => PageStatus::Draft]);
// A history where publishing looks like any other save hides exactly
// the thing somebody came looking for.
$this->assertSame('unpublished', $page->revisions()->first()->event);
}
#[Test]
public function it_keeps_the_old_value_beside_the_new_one(): void
{
$page = Page::factory()->create(['title' => ['es' => 'Antes']]);
$page->update(['title' => ['es' => 'Después']]);
$revision = $page->revisions()->where('event', 'updated')->sole();
$diff = $revision->diff('title');
$this->assertSame(['es' => 'Antes'], $diff['old']);
$this->assertSame(['es' => 'Después'], $diff['new']);
}
#[Test]
public function it_records_only_what_changed(): void
{
$page = Page::factory()->create(['title' => ['es' => 'Uno'], 'is_featured' => false]);
$page->update(['is_featured' => true]);
$revision = $page->revisions()->where('event', 'updated')->sole();
// Storing whole records would make the history larger than the content
// it describes.
$this->assertSame(['is_featured'], $revision->fields());
}
#[Test]
public function saving_without_changing_anything_records_nothing(): void
{
$page = Page::factory()->create();
$before = $page->revisions()->count();
$page->update(['title' => $page->getTranslations('title')]);
$this->assertSame($before, $page->revisions()->count());
}
#[Test]
public function a_previous_state_can_be_put_back(): void
{
$page = Page::factory()->create(['title' => ['es' => 'Original']]);
$page->update(['title' => ['es' => 'Equivocado']]);
$revision = $page->revisions()->where('event', 'updated')->sole();
$page->forceFill($revision->previousValues())->save();
$this->assertSame('Original', $page->fresh()->getTranslation('title', 'es'));
// Restoring is itself a change, so the way back is never lost.
$this->assertSame('updated', $page->fresh()->revisions()->first()->event);
}
#[Test]
public function deleting_and_erasing_are_told_apart(): void
{
$page = Page::factory()->create();
$page->delete();
$this->assertSame('deleted', $page->revisions()->first()->event);
$page->restore();
$this->assertSame('restored', $page->revisions()->first()->event);
$page->forceDelete();
// The record is gone; its history says what happened to it.
$this->assertSame('erased', Revision::query()
->where('revisionable_type', $page->getMorphClass())
->where('revisionable_id', $page->id)
// By id, not by timestamp: these all land in the same second.
->orderByDesc('id')
->first()
->event);
}
#[Test]
public function the_history_shows_on_the_record(): void
{
$this->actingAs($this->userWithRoles('admin'));
$page = Page::factory()->create();
$page->update(['title' => ['es' => 'Cambiado']]);
$this->get(PageResource::getUrl('edit', ['record' => $page]))->assertSuccessful();
}
#[Test]
public function every_content_type_keeps_one(): void
{
// Pinned: the factory randomises this one, and updating a value to
// what it already was is correctly not a change.
$project = Project::factory()->create(['is_featured' => false]);
$project->update(['is_featured' => true]);
$this->assertSame(2, $project->revisions()->count());
}
}