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:
198
tests/Feature/Panel/RevisionsTest.php
Normal file
198
tests/Feature/Panel/RevisionsTest.php
Normal 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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user