Review, publish, preview and undo changes through the API
Revisions now call publishing only what publishes: a project's status is whether the work is active, and a service is published by its flag. The fake identity server answers by token, so a test can sign in a second person. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -114,6 +114,7 @@ class AdminApiContractTest extends TestCase
|
||||
$repository = Repository::factory()->create();
|
||||
Setting::set(Maintenance::KEY, ['enabled' => false]);
|
||||
$media = Media::factory()->create();
|
||||
$page->comment('Primera nota', $this->caller);
|
||||
|
||||
// One request, one documented path — the URL a real caller hits and
|
||||
// the path key that describes it, side by side rather than derived
|
||||
@@ -153,6 +154,8 @@ class AdminApiContractTest extends TestCase
|
||||
'maintenance' => ['/v1/maintenance', '/v1/maintenance'],
|
||||
'media' => ['/v1/media', '/v1/media'],
|
||||
'medium' => ["/v1/media/{$media->id}", '/v1/media/{media}'],
|
||||
'revisions' => ["/v1/projects/{$project->id}/revisions", '/v1/projects/{project}/revisions'],
|
||||
'review thread' => ["/v1/pages/{$page->id}/reviews", '/v1/pages/{page}/reviews'],
|
||||
'users' => ['/v1/users', '/v1/users'],
|
||||
'user' => ["/v1/users/{$this->caller->id}", '/v1/users/{user}'],
|
||||
'me' => ['/v1/me', '/v1/me'],
|
||||
|
||||
211
tests/Feature/Api/ReviewAndHistoryApiTest.php
Normal file
211
tests/Feature/Api/ReviewAndHistoryApiTest.php
Normal file
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Api;
|
||||
|
||||
use App\Enums\PageStatus;
|
||||
use App\Models\Page;
|
||||
use App\Models\Project;
|
||||
use App\Models\Service;
|
||||
use App\Models\User;
|
||||
use App\Support\PreviewLink;
|
||||
use Database\Seeders\RolePermissionSeeder;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\FakesPireon;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* History, review, publication and preview links through the admin API.
|
||||
*/
|
||||
class ReviewAndHistoryApiTest extends TestCase
|
||||
{
|
||||
use FakesPireon, RefreshDatabase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->seed(RolePermissionSeeder::class);
|
||||
Notification::fake();
|
||||
}
|
||||
|
||||
protected function actingWithRole(string $role): User
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$user->syncRoles([$role]);
|
||||
|
||||
$this->withHeaders($this->apiHeadersFor($user));
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function the_history_lists_each_change_with_who_made_it(): void
|
||||
{
|
||||
$editor = $this->actingWithRole('editor');
|
||||
|
||||
$project = Project::factory()->create(['status' => 'active']);
|
||||
|
||||
$this->patchJson("/api/v1/projects/{$project->id}", ['status' => 'paused'])->assertOk();
|
||||
|
||||
$this->getJson("/api/v1/projects/{$project->id}/revisions")
|
||||
->assertOk()
|
||||
->assertJsonPath('data.0.event', 'updated')
|
||||
->assertJsonPath('data.0.author.id', $editor->id)
|
||||
->assertJsonPath('data.0.changes.status.new', 'paused');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function a_change_is_undone_and_the_undoing_is_recorded(): void
|
||||
{
|
||||
$this->actingWithRole('editor');
|
||||
|
||||
$service = Service::factory()->create(['tagline' => ['es' => 'Antes']]);
|
||||
|
||||
$this->patchJson("/api/v1/services/{$service->id}", ['tagline' => ['es' => 'Después']])->assertOk();
|
||||
|
||||
$revision = $service->revisions()->first();
|
||||
|
||||
$this->postJson("/api/v1/revisions/{$revision->id}/restore")
|
||||
->assertOk()
|
||||
->assertJsonPath('data.changes.tagline.new.es', 'Antes');
|
||||
|
||||
$this->assertSame('Antes', $service->fresh()->getTranslation('tagline', 'es'));
|
||||
$this->assertSame(3, $service->revisions()->count());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function undoing_a_publication_takes_the_publish_permission(): void
|
||||
{
|
||||
$page = Page::factory()->draft()->create();
|
||||
|
||||
$this->actingAs($editor = User::factory()->create()->syncRoles(['editor']));
|
||||
$page->update(['status' => PageStatus::Published, 'published_at' => now()]);
|
||||
|
||||
$this->actingWithRole('writer');
|
||||
|
||||
$this->postJson('/api/v1/revisions/'.$page->revisions()->first()->id.'/restore')
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors('revision');
|
||||
|
||||
$this->assertSame(PageStatus::Published, $page->fresh()->status);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function a_writer_asks_for_review_and_an_editor_sees_it_waiting(): void
|
||||
{
|
||||
$writer = $this->actingWithRole('writer');
|
||||
|
||||
$page = Page::factory()->draft()->create();
|
||||
|
||||
$this->postJson("/api/v1/pages/{$page->id}/review", ['note' => 'Lista para revisar.'])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.awaiting_review', true)
|
||||
->assertJsonPath('data.requested_by.id', $writer->id);
|
||||
|
||||
$this->assertSame(PageStatus::Review, $page->fresh()->status);
|
||||
|
||||
$this->postJson("/api/v1/pages/{$page->id}/review")->assertUnprocessable();
|
||||
|
||||
$this->actingWithRole('editor');
|
||||
|
||||
$this->getJson('/api/v1/reviews')
|
||||
->assertOk()
|
||||
->assertJsonCount(1, 'data')
|
||||
->assertJsonPath('data.0.type', 'pages')
|
||||
->assertJsonPath('data.0.id', $page->id);
|
||||
|
||||
$this->getJson("/api/v1/pages/{$page->id}/reviews")
|
||||
->assertJsonPath('data.0.event', 'requested')
|
||||
->assertJsonPath('data.0.body', 'Lista para revisar.');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function an_editor_returns_it_with_a_reason(): void
|
||||
{
|
||||
$this->actingAs(User::factory()->create()->syncRoles(['writer']));
|
||||
$page = Page::factory()->draft()->create();
|
||||
$page->requestReview();
|
||||
|
||||
$this->actingWithRole('editor');
|
||||
|
||||
$this->postJson("/api/v1/pages/{$page->id}/return", [])->assertUnprocessable();
|
||||
|
||||
$this->postJson("/api/v1/pages/{$page->id}/return", ['note' => 'Falta la imagen.'])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.awaiting_review', false);
|
||||
|
||||
$this->assertSame(PageStatus::Draft, $page->fresh()->status);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function publishing_later_answers_the_review_now(): void
|
||||
{
|
||||
$this->actingAs(User::factory()->create()->syncRoles(['writer']));
|
||||
$project = Project::factory()->draft()->create();
|
||||
$project->requestReview();
|
||||
|
||||
$this->actingWithRole('editor');
|
||||
|
||||
$at = now()->addDays(3)->startOfMinute();
|
||||
|
||||
$this->postJson("/api/v1/projects/{$project->id}/publish", ['at' => $at->toIso8601String()])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.published', false)
|
||||
->assertJsonPath('data.awaiting_review', false)
|
||||
->assertJsonPath('data.published_at', $at->toIso8601String());
|
||||
|
||||
$this->postJson("/api/v1/projects/{$project->id}/unpublish")
|
||||
->assertOk()
|
||||
->assertJsonPath('data.published_at', null);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function a_writer_cannot_publish_or_return(): void
|
||||
{
|
||||
$this->actingWithRole('writer');
|
||||
|
||||
$service = Service::factory()->create(['is_published' => false]);
|
||||
|
||||
$this->postJson("/api/v1/services/{$service->id}/publish")->assertForbidden();
|
||||
$this->postJson("/api/v1/services/{$service->id}/return", ['note' => 'x'])->assertForbidden();
|
||||
|
||||
$this->assertFalse($service->fresh()->is_published);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function preview_links_open_that_draft_only(): void
|
||||
{
|
||||
config(['site.url' => 'https://pireon.test']);
|
||||
|
||||
$this->actingWithRole('writer');
|
||||
|
||||
$page = Page::factory()->draft()->create();
|
||||
|
||||
$links = $this->postJson("/api/v1/pages/{$page->id}/preview")
|
||||
->assertCreated()
|
||||
->json('data.links');
|
||||
|
||||
$this->assertArrayHasKey('es', $links);
|
||||
$this->assertStringStartsWith('https://pireon.test/', $links['es']);
|
||||
|
||||
parse_str((string) parse_url($links['es'], PHP_URL_QUERY), $query);
|
||||
|
||||
$this->assertSame(['type' => 'page', 'id' => $page->id, 'locale' => 'es'], PreviewLink::open($query['preview']));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function comments_join_the_thread(): void
|
||||
{
|
||||
$this->actingWithRole('editor');
|
||||
|
||||
$page = Page::factory()->create();
|
||||
|
||||
$this->postJson("/api/v1/pages/{$page->id}/comments", ['body' => '¿Revisamos el título?'])
|
||||
->assertCreated()
|
||||
->assertJsonPath('data.event', 'commented');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user