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,195 @@
<?php
declare(strict_types=1);
namespace Tests\Feature\Panel;
use App\Models\Page;
use App\Models\Project;
use App\Support\Health\Finding;
use App\Support\Health\HealthReport;
use App\Support\TranslationFreshness;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
/**
* Translations the original has moved on without.
*
* The fault this catches is the one nothing else can see: the text is there in
* every language, so every screen calls the record finished, and one of those
* languages has been saying something the source stopped saying. It is worked
* out from the change history, so what these tests really hold still is that
* the history is being read the way it is written.
*/
class StaleTranslationsTest extends TestCase
{
use RefreshDatabase;
protected function translated(): Page
{
return Page::factory()->published()->create([
'title' => ['es' => 'Uno', 'en' => 'One', 'ca' => 'U'],
'slug' => ['es' => 'uno', 'en' => 'one', 'ca' => 'u'],
'body' => ['es' => 'Texto', 'en' => 'Text', 'ca' => 'Text'],
]);
}
#[Test]
public function a_record_nobody_has_edited_is_not_out_of_date(): void
{
$page = $this->translated();
// Written in three languages at once: whatever else is true of it,
// none of them is behind another.
$this->assertSame([], TranslationFreshness::for($page));
}
#[Test]
public function editing_the_original_leaves_the_other_languages_behind(): void
{
$page = $this->translated();
$this->travel(1)->days();
$page->setTranslation('title', 'es', 'Uno corregido')->save();
$stale = TranslationFreshness::for($page->fresh());
$this->assertSame(['en', 'ca'], array_keys($stale));
// And it says since when, which is what decides how urgent it is.
$this->assertTrue($stale['ca']->isToday());
}
#[Test]
public function catching_a_translation_up_clears_it(): void
{
$page = $this->translated();
$this->travel(1)->days();
$page->setTranslation('body', 'es', 'Texto nuevo')->save();
$this->travel(1)->days();
$page->fresh()->setTranslation('body', 'ca', 'Text nou')->save();
$stale = TranslationFreshness::for($page->fresh());
// Catalan caught up; English never did.
$this->assertArrayNotHasKey('ca', $stale);
$this->assertArrayHasKey('en', $stale);
}
/**
* A language with nothing in it is not out of date, it is untranslated —
* and there is already a check that says so. Reporting both about one
* record would just make two lists that never empty.
*/
#[Test]
public function a_missing_translation_is_not_a_stale_one(): void
{
$page = Page::factory()->published()->create([
'title' => ['es' => 'Uno'],
'slug' => ['es' => 'uno'],
]);
$this->travel(1)->days();
$page->setTranslation('title', 'es', 'Uno corregido')->save();
$this->assertSame([], TranslationFreshness::for($page->fresh()));
}
/**
* Renaming the Spanish address does not make the Catalan text less true,
* and slugs are the one translatable field that routinely changes on their
* own — counting them would mark half the site out of date the first time
* somebody tidied a URL.
*/
#[Test]
public function renaming_a_slug_does_not_make_a_translation_stale(): void
{
$page = $this->translated();
$this->travel(1)->days();
$page->setTranslation('slug', 'es', 'uno-mejor')->save();
$this->assertSame([], TranslationFreshness::for($page->fresh()));
}
#[Test]
public function editing_a_translation_does_not_make_the_original_stale(): void
{
$page = $this->translated();
$this->travel(1)->days();
$page->setTranslation('title', 'ca', 'U corregit')->save();
// The source language is never reported against itself, and touching
// Catalan says nothing about English.
$this->assertSame([], TranslationFreshness::for($page->fresh()));
}
#[Test]
public function the_health_check_reports_it(): void
{
$page = $this->translated();
$this->travel(1)->days();
$page->setTranslation('body', 'es', 'Texto corregido')->save();
$finding = app(HealthReport::class)->runAll()->firstWhere('key', 'stale-translations');
$this->assertInstanceOf(Finding::class, $finding);
$this->assertSame(1, $finding->count);
$this->assertStringContainsString('ca', $finding->samples[0]);
}
#[Test]
public function it_works_for_every_translated_type(): void
{
$project = Project::factory()->published()->create([
'title' => ['es' => 'Proyecto', 'ca' => 'Projecte'],
'slug' => ['es' => 'proyecto', 'ca' => 'projecte'],
]);
$this->travel(1)->days();
$project->setTranslation('title', 'es', 'Proyecto nuevo')->save();
$this->assertArrayHasKey('ca', TranslationFreshness::for($project->fresh()));
}
/**
* The filter's half: the answer cannot be a SQL condition, so it is worked
* out per record and handed to the query as a list.
*/
#[Test]
public function the_panel_can_ask_for_the_ones_that_fell_behind(): void
{
$behind = $this->translated();
$fine = $this->translated();
$this->travel(1)->days();
$behind->setTranslation('title', 'es', 'Uno corregido')->save();
$ids = TranslationFreshness::staleIds(Page::query());
$this->assertContains($behind->id, $ids);
$this->assertNotContains($fine->id, $ids);
}
/**
* The answer is cached against the record's `updated_at`, so it has to be
* recomputed the moment anything about that record changes.
*/
#[Test]
public function the_cached_answer_does_not_survive_the_next_edit(): void
{
$page = $this->translated();
$this->assertSame([], TranslationFreshness::for($page));
$this->travel(1)->days();
$page->setTranslation('title', 'es', 'Uno corregido')->save();
$this->assertNotSame([], TranslationFreshness::for($page->fresh()));
}
}