185 lines
6.7 KiB
PHP
185 lines
6.7 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace Tests\Unit\Actions;
|
||
|
|
|
||
|
|
use App\Actions\PublishRouteArticleAction;
|
||
|
|
use App\Enums\PublishStatusEnum;
|
||
|
|
use App\Models\Article;
|
||
|
|
use App\Models\ArticlePublication;
|
||
|
|
use App\Models\Feed;
|
||
|
|
use App\Models\Route;
|
||
|
|
use App\Models\RouteArticle;
|
||
|
|
use App\Services\Article\ArticleFetcher;
|
||
|
|
use App\Services\Notification\NotificationService;
|
||
|
|
use App\Services\Publishing\ArticlePublishingService;
|
||
|
|
use App\Services\Publishing\PublishOutcome;
|
||
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||
|
|
use Mockery;
|
||
|
|
use Tests\TestCase;
|
||
|
|
|
||
|
|
class PublishRouteArticleActionTest extends TestCase
|
||
|
|
{
|
||
|
|
use RefreshDatabase;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param array<string, mixed> $articleAttributes
|
||
|
|
*/
|
||
|
|
private function createRouteArticle(array $articleAttributes, bool $unvalidated = false): RouteArticle
|
||
|
|
{
|
||
|
|
$feed = Feed::factory()->create();
|
||
|
|
/** @var Route $route */
|
||
|
|
$route = Route::factory()->active()->create(['feed_id' => $feed->id]);
|
||
|
|
|
||
|
|
$factory = Article::factory();
|
||
|
|
if ($unvalidated) {
|
||
|
|
$factory = $factory->unvalidated();
|
||
|
|
}
|
||
|
|
$article = $factory->create(array_merge(['feed_id' => $feed->id], $articleAttributes));
|
||
|
|
|
||
|
|
/** @var RouteArticle $routeArticle */
|
||
|
|
$routeArticle = RouteArticle::factory()->forRoute($route)->approved()->create([
|
||
|
|
'article_id' => $article->id,
|
||
|
|
]);
|
||
|
|
|
||
|
|
return $routeArticle;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function makePublication(): ArticlePublication
|
||
|
|
{
|
||
|
|
/** @var ArticlePublication $publication */
|
||
|
|
$publication = ArticlePublication::factory()->create();
|
||
|
|
|
||
|
|
return $publication;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_publish_uses_stored_article_data_without_fetching(): void
|
||
|
|
{
|
||
|
|
$routeArticle = $this->createRouteArticle([
|
||
|
|
'title' => 'Stored Title',
|
||
|
|
'description' => 'Stored description',
|
||
|
|
'image_url' => 'https://cdn.test/stored.jpg',
|
||
|
|
]);
|
||
|
|
|
||
|
|
$fetcher = Mockery::mock(ArticleFetcher::class);
|
||
|
|
$fetcher->shouldNotReceive('fetchArticleData');
|
||
|
|
|
||
|
|
$publishingService = Mockery::mock(ArticlePublishingService::class);
|
||
|
|
$publishingService->shouldReceive('publishRouteArticle')
|
||
|
|
->once()
|
||
|
|
->with(Mockery::any(), [
|
||
|
|
'title' => 'Stored Title',
|
||
|
|
'description' => 'Stored description',
|
||
|
|
'thumbnail' => 'https://cdn.test/stored.jpg',
|
||
|
|
])
|
||
|
|
->andReturn(PublishOutcome::published($this->makePublication()));
|
||
|
|
|
||
|
|
(new PublishRouteArticleAction($fetcher, $publishingService, new NotificationService))
|
||
|
|
->execute($routeArticle);
|
||
|
|
|
||
|
|
$this->assertSame(PublishStatusEnum::PUBLISHED, $routeArticle->fresh()->publish_status);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_publish_body_comes_from_description_not_content(): void
|
||
|
|
{
|
||
|
|
$routeArticle = $this->createRouteArticle([
|
||
|
|
'description' => 'The short description',
|
||
|
|
'content' => 'The very much longer full article body text',
|
||
|
|
]);
|
||
|
|
|
||
|
|
$fetcher = Mockery::mock(ArticleFetcher::class);
|
||
|
|
$fetcher->shouldNotReceive('fetchArticleData');
|
||
|
|
|
||
|
|
$captured = null;
|
||
|
|
$publishingService = Mockery::mock(ArticlePublishingService::class);
|
||
|
|
$publishingService->shouldReceive('publishRouteArticle')
|
||
|
|
->once()
|
||
|
|
->andReturnUsing(function ($ra, $data) use (&$captured) {
|
||
|
|
$captured = $data;
|
||
|
|
|
||
|
|
return PublishOutcome::published($this->makePublication());
|
||
|
|
});
|
||
|
|
|
||
|
|
(new PublishRouteArticleAction($fetcher, $publishingService, new NotificationService))
|
||
|
|
->execute($routeArticle);
|
||
|
|
|
||
|
|
$this->assertSame('The short description', $captured['description']);
|
||
|
|
$this->assertArrayNotHasKey('content', $captured);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_publish_falls_back_to_fetching_when_description_is_blank(): void
|
||
|
|
{
|
||
|
|
$routeArticle = $this->createRouteArticle([
|
||
|
|
'description' => '',
|
||
|
|
'image_url' => null,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$fetched = ['title' => 'Fetched Title', 'description' => 'Fetched description'];
|
||
|
|
|
||
|
|
$fetcher = Mockery::mock(ArticleFetcher::class);
|
||
|
|
$fetcher->shouldReceive('fetchArticleData')->once()->andReturn($fetched);
|
||
|
|
|
||
|
|
$publishingService = Mockery::mock(ArticlePublishingService::class);
|
||
|
|
$publishingService->shouldReceive('publishRouteArticle')
|
||
|
|
->once()
|
||
|
|
->with(Mockery::any(), $fetched)
|
||
|
|
->andReturn(PublishOutcome::published($this->makePublication()));
|
||
|
|
|
||
|
|
(new PublishRouteArticleAction($fetcher, $publishingService, new NotificationService))
|
||
|
|
->execute($routeArticle);
|
||
|
|
|
||
|
|
$this->assertSame(PublishStatusEnum::PUBLISHED, $routeArticle->fresh()->publish_status);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_publish_falls_back_to_fetching_when_article_has_no_stored_data(): void
|
||
|
|
{
|
||
|
|
$routeArticle = $this->createRouteArticle([], unvalidated: true);
|
||
|
|
|
||
|
|
$fetched = [
|
||
|
|
'title' => 'Fetched Title',
|
||
|
|
'description' => 'Fetched description',
|
||
|
|
'thumbnail' => 'https://cdn.test/fetched.jpg',
|
||
|
|
];
|
||
|
|
|
||
|
|
$fetcher = Mockery::mock(ArticleFetcher::class);
|
||
|
|
$fetcher->shouldReceive('fetchArticleData')->once()->andReturn($fetched);
|
||
|
|
|
||
|
|
$publishingService = Mockery::mock(ArticlePublishingService::class);
|
||
|
|
$publishingService->shouldReceive('publishRouteArticle')
|
||
|
|
->once()
|
||
|
|
->with(Mockery::any(), $fetched)
|
||
|
|
->andReturn(PublishOutcome::published($this->makePublication()));
|
||
|
|
|
||
|
|
(new PublishRouteArticleAction($fetcher, $publishingService, new NotificationService))
|
||
|
|
->execute($routeArticle);
|
||
|
|
|
||
|
|
$this->assertSame(PublishStatusEnum::PUBLISHED, $routeArticle->fresh()->publish_status);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_publish_does_not_refetch_for_articles_stored_before_image_url_existed(): void
|
||
|
|
{
|
||
|
|
$routeArticle = $this->createRouteArticle([
|
||
|
|
'title' => 'Legacy Title',
|
||
|
|
'description' => 'Legacy description',
|
||
|
|
'image_url' => null,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$fetcher = Mockery::mock(ArticleFetcher::class);
|
||
|
|
$fetcher->shouldNotReceive('fetchArticleData');
|
||
|
|
|
||
|
|
$publishingService = Mockery::mock(ArticlePublishingService::class);
|
||
|
|
$publishingService->shouldReceive('publishRouteArticle')
|
||
|
|
->once()
|
||
|
|
->with(Mockery::any(), [
|
||
|
|
'title' => 'Legacy Title',
|
||
|
|
'description' => 'Legacy description',
|
||
|
|
'thumbnail' => null,
|
||
|
|
])
|
||
|
|
->andReturn(PublishOutcome::published($this->makePublication()));
|
||
|
|
|
||
|
|
(new PublishRouteArticleAction($fetcher, $publishingService, new NotificationService))
|
||
|
|
->execute($routeArticle);
|
||
|
|
|
||
|
|
$this->assertSame(PublishStatusEnum::PUBLISHED, $routeArticle->fresh()->publish_status);
|
||
|
|
}
|
||
|
|
}
|