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:
225
tests/Feature/Site/PublicationWindowTest.php
Normal file
225
tests/Feature/Site/PublicationWindowTest.php
Normal file
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Site;
|
||||
|
||||
use App\Enums\CallStatus;
|
||||
use App\Enums\PageStatus;
|
||||
use App\Models\Call;
|
||||
use App\Models\Page;
|
||||
use App\Models\Project;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Content that is only true between two dates.
|
||||
*
|
||||
* The window is enforced by the scopes rather than by a scheduled job, so it
|
||||
* holds even when nothing is running — a queue worker being down must not put
|
||||
* last month's notice back on the site.
|
||||
*/
|
||||
class PublicationWindowTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->withHeaders(['Accept-Language' => 'es']);
|
||||
Queue::fake();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function a_page_stays_off_the_site_until_its_date(): void
|
||||
{
|
||||
Page::factory()->create([
|
||||
'status' => PageStatus::Published,
|
||||
'title' => ['es' => 'El anuncio'],
|
||||
'slug' => ['es' => 'el-anuncio'],
|
||||
'published_at' => now()->addDay(),
|
||||
]);
|
||||
|
||||
$this->getJson('/api/public/v1/pages/el-anuncio')->assertNotFound();
|
||||
|
||||
$this->travel(2)->days();
|
||||
|
||||
$this->getJson('/api/public/v1/pages/el-anuncio')->assertOk();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function a_page_drops_off_the_site_on_its_end_date(): void
|
||||
{
|
||||
Page::factory()->create([
|
||||
'status' => PageStatus::Published,
|
||||
'title' => ['es' => 'Taller de octubre'],
|
||||
'slug' => ['es' => 'taller'],
|
||||
'published_at' => now()->subWeek(),
|
||||
'unpublish_at' => now()->addDay(),
|
||||
]);
|
||||
|
||||
$this->getJson('/api/public/v1/pages/taller')->assertOk();
|
||||
|
||||
$this->travel(2)->days();
|
||||
|
||||
// Nobody has to remember to take it down, which is the whole point:
|
||||
// nobody ever does.
|
||||
$this->getJson('/api/public/v1/pages/taller')->assertNotFound();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function the_window_holds_without_anything_running(): void
|
||||
{
|
||||
Page::factory()->create([
|
||||
'status' => PageStatus::Published,
|
||||
'title' => ['es' => 'Caducada'],
|
||||
'slug' => ['es' => 'caducada'],
|
||||
'published_at' => now()->subMonth(),
|
||||
'unpublish_at' => now()->subDay(),
|
||||
]);
|
||||
|
||||
// No command, no queue, no scheduler — the scope is what decides.
|
||||
$this->getJson('/api/public/v1/pages')
|
||||
->assertOk()
|
||||
->assertJsonCount(0, 'data');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function an_expired_project_leaves_the_url_list_too(): void
|
||||
{
|
||||
Project::factory()->published()->create([
|
||||
'title' => ['es' => 'Terminado'],
|
||||
'slug' => ['es' => 'terminado'],
|
||||
'unpublish_at' => now()->subDay(),
|
||||
]);
|
||||
|
||||
$slugs = $this->getJson('/api/public/v1/slugs')->assertOk()->json('data');
|
||||
|
||||
// Otherwise the frontend would keep it in the sitemap and point search
|
||||
// engines at a 404.
|
||||
$this->assertEmpty($slugs['projects'] ?? []);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function preview_still_reaches_content_outside_its_window(): void
|
||||
{
|
||||
config()->set('site.preview_token', 'secreto');
|
||||
|
||||
Page::factory()->create([
|
||||
'status' => PageStatus::Published,
|
||||
'title' => ['es' => 'Aún no'],
|
||||
'slug' => ['es' => 'aun-no'],
|
||||
'published_at' => now()->addWeek(),
|
||||
]);
|
||||
|
||||
// Checking a scheduled page before its date is exactly what preview is
|
||||
// for.
|
||||
$this->withHeaders(['X-Preview-Token' => 'secreto'])
|
||||
->getJson('/api/public/v1/pages/aun-no')
|
||||
->assertOk();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function the_command_closes_a_call_whose_date_has_passed(): void
|
||||
{
|
||||
$call = Call::factory()->create([
|
||||
'project_id' => null,
|
||||
'status' => CallStatus::Open,
|
||||
'closes_at' => now()->subWeek(),
|
||||
]);
|
||||
|
||||
$this->artisan('content:schedule')->assertSuccessful();
|
||||
|
||||
// The site already stopped offering it; this is about the panel not
|
||||
// claiming otherwise, and a counter that stays worth reading.
|
||||
$this->assertSame(CallStatus::Closed, $call->refresh()->status);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function the_command_leaves_a_call_that_is_still_open(): void
|
||||
{
|
||||
$open = Call::factory()->create([
|
||||
'project_id' => null,
|
||||
'status' => CallStatus::Open,
|
||||
'closes_at' => now()->addWeek(),
|
||||
]);
|
||||
|
||||
$undated = Call::factory()->create([
|
||||
'project_id' => null,
|
||||
'status' => CallStatus::Open,
|
||||
'closes_at' => null,
|
||||
]);
|
||||
|
||||
$this->artisan('content:schedule')->assertSuccessful();
|
||||
|
||||
$this->assertSame(CallStatus::Open, $open->refresh()->status);
|
||||
$this->assertSame(CallStatus::Open, $undated->refresh()->status);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function a_dry_run_changes_nothing(): void
|
||||
{
|
||||
$call = Call::factory()->create([
|
||||
'project_id' => null,
|
||||
'status' => CallStatus::Open,
|
||||
'closes_at' => now()->subWeek(),
|
||||
]);
|
||||
|
||||
$this->artisan('content:schedule', ['--dry-run' => true])->assertSuccessful();
|
||||
|
||||
$this->assertSame(CallStatus::Open, $call->refresh()->status);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function the_panel_can_tell_the_three_states_apart(): void
|
||||
{
|
||||
$scheduled = Page::factory()->create([
|
||||
'status' => PageStatus::Published,
|
||||
'published_at' => now()->addDays(3),
|
||||
]);
|
||||
|
||||
$expired = Page::factory()->create([
|
||||
'status' => PageStatus::Published,
|
||||
'published_at' => now()->subMonth(),
|
||||
'unpublish_at' => now()->subDay(),
|
||||
]);
|
||||
|
||||
$live = Page::factory()->create([
|
||||
'status' => PageStatus::Published,
|
||||
'published_at' => now()->subDay(),
|
||||
]);
|
||||
|
||||
// A panel that reports "Published" for something the site is not
|
||||
// showing is lying to whoever wrote it.
|
||||
$this->assertTrue($scheduled->isScheduled());
|
||||
$this->assertFalse($scheduled->hasExpired());
|
||||
|
||||
$this->assertTrue($expired->hasExpired());
|
||||
$this->assertFalse($expired->isScheduled());
|
||||
|
||||
$this->assertFalse($live->isScheduled());
|
||||
$this->assertFalse($live->hasExpired());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function content_about_to_go_can_be_spotted_in_advance(): void
|
||||
{
|
||||
$soon = Page::factory()->create([
|
||||
'status' => PageStatus::Published,
|
||||
'published_at' => now()->subMonth(),
|
||||
'unpublish_at' => now()->addDays(2),
|
||||
]);
|
||||
|
||||
$later = Page::factory()->create([
|
||||
'status' => PageStatus::Published,
|
||||
'published_at' => now()->subMonth(),
|
||||
'unpublish_at' => now()->addMonths(2),
|
||||
]);
|
||||
|
||||
$this->assertTrue($soon->expiresSoon());
|
||||
$this->assertFalse($later->expiresSoon());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user