Manage redirects, missing URLs, repositories and settings through the API
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,13 +9,18 @@ use App\Models\Call;
|
||||
use App\Models\Category;
|
||||
use App\Models\Client;
|
||||
use App\Models\Member;
|
||||
use App\Models\MissingUrl;
|
||||
use App\Models\Page;
|
||||
use App\Models\Project;
|
||||
use App\Models\Redirect;
|
||||
use App\Models\Repository;
|
||||
use App\Models\Service;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Talk;
|
||||
use App\Models\Technology;
|
||||
use App\Models\Testimonial;
|
||||
use App\Models\User;
|
||||
use App\Support\Maintenance;
|
||||
use Database\Seeders\RolePermissionSeeder;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
@@ -82,6 +87,9 @@ class AdminApiContractTest extends TestCase
|
||||
'talks' => ['path' => '/v1/talks'],
|
||||
'calls' => ['path' => '/v1/calls'],
|
||||
'applications' => ['path' => '/v1/applications'],
|
||||
'redirects' => ['path' => '/v1/redirects'],
|
||||
'missing-urls' => ['path' => '/v1/missing-urls'],
|
||||
'repositories' => ['path' => '/v1/repositories'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -99,6 +107,10 @@ class AdminApiContractTest extends TestCase
|
||||
$talk = Talk::factory()->create();
|
||||
$call = Call::factory()->create();
|
||||
$application = Application::factory()->for($call)->create();
|
||||
$redirect = Redirect::factory()->create();
|
||||
$missingUrl = MissingUrl::factory()->create();
|
||||
$repository = Repository::factory()->create();
|
||||
Setting::set(Maintenance::KEY, ['enabled' => false]);
|
||||
|
||||
// One request, one documented path — the URL a real caller hits and
|
||||
// the path key that describes it, side by side rather than derived
|
||||
@@ -127,6 +139,15 @@ class AdminApiContractTest extends TestCase
|
||||
'call' => ["/v1/calls/{$call->id}", '/v1/calls/{call}'],
|
||||
'applications' => ['/v1/applications', '/v1/applications'],
|
||||
'application' => ["/v1/applications/{$application->id}", '/v1/applications/{application}'],
|
||||
'redirects' => ['/v1/redirects', '/v1/redirects'],
|
||||
'redirect' => ["/v1/redirects/{$redirect->id}", '/v1/redirects/{redirect}'],
|
||||
'missing-urls' => ['/v1/missing-urls', '/v1/missing-urls'],
|
||||
'missing-url' => ["/v1/missing-urls/{$missingUrl->id}", '/v1/missing-urls/{missing_url}'],
|
||||
'repositories' => ['/v1/repositories', '/v1/repositories'],
|
||||
'repository' => ["/v1/repositories/{$repository->id}", '/v1/repositories/{repository}'],
|
||||
'settings' => ['/v1/settings', '/v1/settings'],
|
||||
'setting' => ['/v1/settings/'.Maintenance::KEY, '/v1/settings/{setting}'],
|
||||
'maintenance' => ['/v1/maintenance', '/v1/maintenance'],
|
||||
'users' => ['/v1/users', '/v1/users'],
|
||||
'user' => ["/v1/users/{$this->caller->id}", '/v1/users/{user}'],
|
||||
'me' => ['/v1/me', '/v1/me'],
|
||||
|
||||
221
tests/Feature/Api/SiteAdministrationApiTest.php
Normal file
221
tests/Feature/Api/SiteAdministrationApiTest.php
Normal file
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Api;
|
||||
|
||||
use App\Enums\MissingUrlStatus;
|
||||
use App\Jobs\SyncRepository;
|
||||
use App\Models\MissingUrl;
|
||||
use App\Models\Repository;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use App\Support\ContentVersion;
|
||||
use App\Support\Maintenance;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Redirects, missing URLs, repositories, settings and maintenance through the
|
||||
* admin API — the panel's administration screens.
|
||||
*/
|
||||
class SiteAdministrationApiTest extends TestCase
|
||||
{
|
||||
use FakesPireon, RefreshDatabase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->seed(RolePermissionSeeder::class);
|
||||
}
|
||||
|
||||
protected function actingWithRole(string $role): User
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$user->syncRoles([$role]);
|
||||
|
||||
$this->withHeaders($this->apiHeadersFor($user));
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function a_redirect_is_normalised_and_closes_the_report_it_answers(): void
|
||||
{
|
||||
$this->actingWithRole('editor');
|
||||
|
||||
MissingUrl::report('/proyectos/viejo', 'es');
|
||||
|
||||
$this->postJson('/api/v1/redirects', [
|
||||
'from' => 'proyectos/viejo/',
|
||||
'to' => '/proyectos/nuevo?x=1',
|
||||
])
|
||||
->assertCreated()
|
||||
->assertJsonPath('data.from', '/proyectos/viejo')
|
||||
->assertJsonPath('data.to', '/proyectos/nuevo')
|
||||
->assertJsonPath('data.status', 301)
|
||||
->assertJsonPath('data.automatic', false);
|
||||
|
||||
$this->assertSame(MissingUrlStatus::Resolved, MissingUrl::query()->sole()->status);
|
||||
|
||||
// The same address in the same language is one rule, not two.
|
||||
$this->postJson('/api/v1/redirects', ['from' => '/proyectos/viejo', 'to' => '/otra'])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors('from');
|
||||
|
||||
$this->postJson('/api/v1/redirects', ['from' => '/a', 'to' => '/a/'])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors('to');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function a_missing_url_is_given_a_destination(): void
|
||||
{
|
||||
$this->actingWithRole('editor');
|
||||
|
||||
MissingUrl::report('/en/old', 'en');
|
||||
$missing = MissingUrl::query()->sole();
|
||||
|
||||
$this->getJson('/api/v1/missing-urls')->assertJsonCount(1, 'data');
|
||||
|
||||
$this->postJson("/api/v1/missing-urls/{$missing->id}/redirect", ['to' => '/en/new'])
|
||||
->assertCreated()
|
||||
->assertJsonPath('data.from', '/en/old')
|
||||
->assertJsonPath('data.locale', 'en');
|
||||
|
||||
$this->assertSame(MissingUrlStatus::Resolved, $missing->fresh()->status);
|
||||
$this->getJson('/api/v1/missing-urls')->assertJsonCount(0, 'data');
|
||||
|
||||
$this->postJson("/api/v1/missing-urls/{$missing->id}/redirect", ['to' => '/en/other'])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors('locale');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function a_missing_url_can_be_ignored_but_not_declared_resolved(): void
|
||||
{
|
||||
$this->actingWithRole('editor');
|
||||
|
||||
$missing = MissingUrl::factory()->create();
|
||||
|
||||
$this->patchJson("/api/v1/missing-urls/{$missing->id}", ['status' => 'ignored'])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.status', 'ignored');
|
||||
|
||||
$this->patchJson("/api/v1/missing-urls/{$missing->id}", ['status' => 'resolved'])
|
||||
->assertUnprocessable();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function a_repository_is_added_and_synced_on_the_queue(): void
|
||||
{
|
||||
Queue::fake();
|
||||
|
||||
$this->actingWithRole('admin');
|
||||
|
||||
$id = $this->postJson('/api/v1/repositories', [
|
||||
'provider' => 'github',
|
||||
'full_name' => 'pireon/landing',
|
||||
'show_publicly' => true,
|
||||
// Synced columns are not the caller's to set.
|
||||
'stars' => 9999,
|
||||
])
|
||||
->assertCreated()
|
||||
->assertJsonPath('data.full_name', 'pireon/landing')
|
||||
->assertJsonPath('data.stars', 0)
|
||||
->json('data.id');
|
||||
|
||||
$this->postJson('/api/v1/repositories', ['provider' => 'github', 'full_name' => 'pireon/landing'])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors('full_name');
|
||||
|
||||
$this->postJson("/api/v1/repositories/{$id}/sync")->assertAccepted();
|
||||
|
||||
Queue::assertPushed(SyncRepository::class);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function an_editor_reads_repositories_but_cannot_sync_them(): void
|
||||
{
|
||||
Queue::fake();
|
||||
|
||||
$this->actingWithRole('editor');
|
||||
|
||||
$repository = Repository::factory()->create();
|
||||
|
||||
$this->getJson('/api/v1/repositories')->assertOk();
|
||||
$this->postJson("/api/v1/repositories/{$repository->id}/sync")->assertForbidden();
|
||||
|
||||
Queue::assertNothingPushed();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function a_setting_is_replaced_whole(): void
|
||||
{
|
||||
$this->actingWithRole('admin');
|
||||
|
||||
Setting::set('site.contact', ['email' => 'hola@example.org']);
|
||||
|
||||
$this->getJson('/api/v1/settings')->assertOk()->assertJsonPath('data.0.key', 'site.contact');
|
||||
|
||||
$this->patchJson('/api/v1/settings/site.contact', ['value' => ['email' => 'nuevo@example.org']])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.value.email', 'nuevo@example.org');
|
||||
|
||||
$this->assertSame(['email' => 'nuevo@example.org'], Setting::get('site.contact'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function the_content_version_is_not_edited_by_hand(): void
|
||||
{
|
||||
$this->actingWithRole('admin');
|
||||
|
||||
ContentVersion::bump();
|
||||
|
||||
$this->patchJson('/api/v1/settings/'.ContentVersion::KEY, ['value' => 1])->assertForbidden();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function an_editor_sees_settings_but_changes_none(): void
|
||||
{
|
||||
$this->actingWithRole('editor');
|
||||
|
||||
Setting::set(Maintenance::KEY, ['enabled' => false]);
|
||||
|
||||
$this->getJson('/api/v1/settings')->assertOk();
|
||||
$this->getJson('/api/v1/maintenance')->assertOk()->assertJsonPath('data.enabled', false);
|
||||
|
||||
$this->patchJson('/api/v1/settings/'.Maintenance::KEY, ['value' => ['enabled' => true]])->assertForbidden();
|
||||
$this->putJson('/api/v1/maintenance', ['enabled' => true])->assertForbidden();
|
||||
|
||||
$this->assertFalse(Maintenance::isEnabled());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function maintenance_is_switched_on_and_off_keeping_its_wording(): void
|
||||
{
|
||||
$this->actingWithRole('admin');
|
||||
|
||||
$this->putJson('/api/v1/maintenance', [
|
||||
'enabled' => true,
|
||||
'message' => ['es' => 'Volvemos enseguida.', 'en' => ''],
|
||||
'until' => now()->addHour()->toIso8601String(),
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.enabled', true)
|
||||
->assertJsonPath('data.message', ['es' => 'Volvemos enseguida.']);
|
||||
|
||||
$this->assertTrue(Maintenance::isEnabled());
|
||||
|
||||
$this->putJson('/api/v1/maintenance', ['enabled' => false])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.enabled', false)
|
||||
->assertJsonPath('data.until', null)
|
||||
->assertJsonPath('data.message.es', 'Volvemos enseguida.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user