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:
193
tests/Feature/Site/PruneAndRedirectHealthTest.php
Normal file
193
tests/Feature/Site/PruneAndRedirectHealthTest.php
Normal file
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Site;
|
||||
|
||||
use App\Models\Media;
|
||||
use App\Models\Page;
|
||||
use App\Models\Project;
|
||||
use App\Models\Redirect;
|
||||
use App\Models\Revision;
|
||||
use App\Support\Health\Finding;
|
||||
use App\Support\Health\HealthReport;
|
||||
use App\Support\Health\Severity;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Keeping the system from rotting: redirects that make things worse, and the
|
||||
* three kinds of debris that otherwise accumulate for ever.
|
||||
*/
|
||||
class PruneAndRedirectHealthTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Queue::fake();
|
||||
Storage::fake(config('media.disk'));
|
||||
}
|
||||
|
||||
protected function finding(string $key): Finding
|
||||
{
|
||||
return app(HealthReport::class)->runAll()->firstWhere('key', $key)
|
||||
?? throw new \RuntimeException("No finding for {$key}");
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_finds_a_redirect_that_chains_into_another(): void
|
||||
{
|
||||
Redirect::factory()->create(['from' => '/a', 'to' => '/b', 'locale' => 'es']);
|
||||
Redirect::factory()->create(['from' => '/b', 'to' => '/c', 'locale' => 'es']);
|
||||
|
||||
$finding = $this->finding('broken-redirects');
|
||||
|
||||
// The visitor is sent somewhere that sends them somewhere else: a
|
||||
// round trip per hop, and eventually a loop.
|
||||
$this->assertSame(Severity::Error, $finding->severity);
|
||||
$this->assertSame(1, $finding->count);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_finds_a_redirect_that_lands_on_nothing(): void
|
||||
{
|
||||
Redirect::factory()->create(['from' => '/viejo', 'to' => '/proyectos/no-existe']);
|
||||
|
||||
// Worse than the 404 it was meant to prevent: the visitor is bounced
|
||||
// and lands on the same nothing.
|
||||
$this->assertSame(1, $this->finding('redirects-to-nowhere')->count);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function a_redirect_to_a_real_page_is_fine(): void
|
||||
{
|
||||
Project::factory()->published()->create([
|
||||
'title' => ['es' => 'Vivo'], 'slug' => ['es' => 'vivo'],
|
||||
]);
|
||||
|
||||
Redirect::factory()->create(['from' => '/viejo', 'to' => '/proyectos/vivo']);
|
||||
|
||||
$this->assertSame(Severity::Ok, $this->finding('redirects-to-nowhere')->severity);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function a_redirect_off_this_site_is_not_ours_to_verify(): void
|
||||
{
|
||||
Redirect::factory()->create(['from' => '/viejo', 'to' => 'https://otra-web.example/algo']);
|
||||
|
||||
$this->assertSame(Severity::Ok, $this->finding('redirects-to-nowhere')->severity);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function pruning_keeps_the_first_and_last_of_a_history(): void
|
||||
{
|
||||
$page = Page::factory()->create();
|
||||
|
||||
foreach (range(1, 4) as $n) {
|
||||
$page->update(['title' => ['es' => "Versión {$n}"]]);
|
||||
}
|
||||
|
||||
Revision::query()->update(['created_at' => now()->subYears(2)]);
|
||||
|
||||
$this->artisan('content:prune')->assertSuccessful();
|
||||
|
||||
// Two facts survive whatever happens: when it appeared, and who
|
||||
// touched it last.
|
||||
$this->assertSame(2, $page->revisions()->count());
|
||||
// `reorder`, because the relation sorts newest-first by default and
|
||||
// an added orderBy would only be a tie-breaker behind it.
|
||||
$this->assertSame('created', $page->revisions()->reorder('id')->first()->event);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function recent_history_is_left_alone(): void
|
||||
{
|
||||
$page = Page::factory()->create();
|
||||
$page->update(['title' => ['es' => 'Reciente']]);
|
||||
|
||||
$this->artisan('content:prune')->assertSuccessful();
|
||||
|
||||
$this->assertSame(2, $page->revisions()->count());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_erases_what_has_been_in_the_bin_long_enough(): void
|
||||
{
|
||||
$old = Page::factory()->create();
|
||||
$old->delete();
|
||||
$old->forceFill(['deleted_at' => now()->subYear()])->save();
|
||||
|
||||
$recent = Page::factory()->create();
|
||||
$recent->delete();
|
||||
|
||||
$this->artisan('content:prune')->assertSuccessful();
|
||||
|
||||
$this->assertNull(Page::withTrashed()->find($old->id));
|
||||
$this->assertNotNull(Page::withTrashed()->find($recent->id));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function a_dry_run_changes_nothing(): void
|
||||
{
|
||||
$page = Page::factory()->create();
|
||||
$page->delete();
|
||||
$page->forceFill(['deleted_at' => now()->subYear()])->save();
|
||||
|
||||
$this->artisan('content:prune', ['--dry-run' => true])->assertSuccessful();
|
||||
|
||||
$this->assertNotNull(Page::withTrashed()->find($page->id));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_deletes_image_files_nothing_points_at(): void
|
||||
{
|
||||
$disk = Storage::disk(config('media.disk'));
|
||||
|
||||
$disk->put('projects/en-uso.jpg', 'x');
|
||||
$disk->put('projects/huerfana.jpg', 'x');
|
||||
|
||||
Project::factory()->create(['cover_image' => 'projects/en-uso.jpg']);
|
||||
Media::factory()->create(['path' => 'projects/huerfana.jpg']);
|
||||
|
||||
$this->artisan('content:prune', ['--media' => true])->assertSuccessful();
|
||||
|
||||
$this->assertTrue($disk->exists('projects/en-uso.jpg'));
|
||||
$this->assertFalse($disk->exists('projects/huerfana.jpg'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function an_image_belonging_to_something_in_the_bin_is_kept(): void
|
||||
{
|
||||
$disk = Storage::disk(config('media.disk'));
|
||||
$disk->put('projects/borrado.jpg', 'x');
|
||||
|
||||
$project = Project::factory()->create(['cover_image' => 'projects/borrado.jpg']);
|
||||
$project->delete();
|
||||
|
||||
$this->artisan('content:prune', ['--media' => true])->assertSuccessful();
|
||||
|
||||
// It can still be restored, and restoring it to a missing image would
|
||||
// be worse than keeping a file.
|
||||
$this->assertTrue($disk->exists('projects/borrado.jpg'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function files_are_left_alone_unless_asked_for(): void
|
||||
{
|
||||
$disk = Storage::disk(config('media.disk'));
|
||||
$disk->put('projects/huerfana.jpg', 'x');
|
||||
Media::factory()->create(['path' => 'projects/huerfana.jpg']);
|
||||
|
||||
$this->artisan('content:prune')->assertSuccessful();
|
||||
|
||||
// Deleting a file is the one thing here that restoring a row cannot
|
||||
// undo, so it never happens by routine.
|
||||
$this->assertTrue($disk->exists('projects/huerfana.jpg'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user