diff --git a/app/Actions/FetchArticleDataAction.php b/app/Actions/FetchArticleDataAction.php new file mode 100644 index 00000000..73b0f9de --- /dev/null +++ b/app/Actions/FetchArticleDataAction.php @@ -0,0 +1,36 @@ + + */ + public function execute(Article $article): array + { + try { + $html = HttpFetcher::fetchHtml($article->url); + $parser = ArticleParserFactory::getParser($article->url); + + return $parser->extractData($html); + } catch (Exception $e) { + $this->logSaver->error('Exception while fetching article data', null, [ + 'url' => $article->url, + 'error' => $e->getMessage(), + ]); + + return []; + } + } +} diff --git a/app/Actions/FetchFeedArticlesAction.php b/app/Actions/FetchFeedArticlesAction.php new file mode 100644 index 00000000..43d127de --- /dev/null +++ b/app/Actions/FetchFeedArticlesAction.php @@ -0,0 +1,36 @@ + + */ + public function execute(Feed $feed): Collection + { + if ($feed->type === 'rss') { + return $this->fetchRssArticles->execute($feed); + } elseif ($feed->type === 'website') { + return $this->fetchWebsiteArticles->execute($feed); + } + + $this->logSaver->warning('Unsupported feed type', null, [ + 'feed_id' => $feed->id, + 'feed_type' => $feed->type, + ]); + + return collect(); + } +} diff --git a/app/Actions/PublishRouteArticleAction.php b/app/Actions/PublishRouteArticleAction.php index 185eb0cc..8f85bf3d 100644 --- a/app/Actions/PublishRouteArticleAction.php +++ b/app/Actions/PublishRouteArticleAction.php @@ -12,7 +12,6 @@ use App\Exceptions\PublishException; use App\Models\Article; use App\Models\RouteArticle; -use App\Services\Article\ArticleFetcher; use App\Services\Notification\NotificationService; use App\Services\Publishing\ArticlePublishingService; use App\Services\Publishing\PublishOutcome; @@ -21,7 +20,7 @@ class PublishRouteArticleAction { public function __construct( - private ArticleFetcher $articleFetcher, + private FetchArticleDataAction $fetchArticleData, private ArticlePublishingService $publishingService, private NotificationService $notificationService, ) {} @@ -90,7 +89,7 @@ private function hasPublishableContent(array $extractedData): bool private function resolvePublishData(Article $article): array { if (empty($article->description) && empty($article->image_url)) { - return $this->articleFetcher->fetchArticleData($article); + return $this->fetchArticleData->execute($article); } return [ diff --git a/app/Actions/ValidateArticleAction.php b/app/Actions/ValidateArticleAction.php index 05a49b4c..253618c9 100644 --- a/app/Actions/ValidateArticleAction.php +++ b/app/Actions/ValidateArticleAction.php @@ -3,12 +3,11 @@ namespace App\Actions; use App\Models\Article; -use App\Services\Article\ArticleFetcher; class ValidateArticleAction { public function __construct( - private ArticleFetcher $articleFetcher, + private FetchArticleDataAction $fetchArticleData, private CreateRouteArticlesAction $createRouteArticles, ) {} @@ -16,7 +15,7 @@ public function execute(Article $article): Article { logger('Validating article for routes: '.$article->id); - $articleData = $this->articleFetcher->fetchArticleData($article); + $articleData = $this->fetchArticleData->execute($article); $updateData = []; diff --git a/app/Jobs/ArticleDiscoveryForFeedJob.php b/app/Jobs/ArticleDiscoveryForFeedJob.php index 7ecc3e26..8a124e1a 100644 --- a/app/Jobs/ArticleDiscoveryForFeedJob.php +++ b/app/Jobs/ArticleDiscoveryForFeedJob.php @@ -2,13 +2,13 @@ namespace App\Jobs; +use App\Actions\FetchFeedArticlesAction; use App\Enums\ActivityTypeEnum; use App\Enums\NotificationSeverityEnum; use App\Enums\NotificationTypeEnum; use App\Events\ActivityLogged; use App\Models\Feed; use App\Models\Notification; -use App\Services\Article\ArticleFetcher; use App\Services\Log\LogSaver; use App\Services\Notification\NotificationService; use Illuminate\Contracts\Queue\ShouldQueue; @@ -26,7 +26,7 @@ public function __construct( $this->onQueue('feed-discovery'); } - public function handle(LogSaver $logSaver, ArticleFetcher $articleFetcher, NotificationService $notificationService): void + public function handle(LogSaver $logSaver, FetchFeedArticlesAction $fetchFeedArticles, NotificationService $notificationService): void { $logSaver->info('Starting feed article fetch', null, [ 'feed_id' => $this->feed->id, @@ -34,7 +34,7 @@ public function handle(LogSaver $logSaver, ArticleFetcher $articleFetcher, Notif 'feed_url' => $this->feed->url, ]); - $articles = $articleFetcher->getArticlesFromFeed($this->feed); + $articles = $fetchFeedArticles->execute($this->feed); $logSaver->info('Feed article fetch completed', null, [ 'feed_id' => $this->feed->id, diff --git a/app/Services/Article/ArticleFetcher.php b/app/Services/Article/ArticleFetcher.php deleted file mode 100644 index e801993d..00000000 --- a/app/Services/Article/ArticleFetcher.php +++ /dev/null @@ -1,61 +0,0 @@ - - */ - public function getArticlesFromFeed(Feed $feed): Collection - { - if ($feed->type === 'rss') { - return $this->fetchRssArticles->execute($feed); - } elseif ($feed->type === 'website') { - return $this->fetchWebsiteArticles->execute($feed); - } - - $this->logSaver->warning('Unsupported feed type', null, [ - 'feed_id' => $feed->id, - 'feed_type' => $feed->type, - ]); - - return collect(); - } - - /** - * @return array - */ - public function fetchArticleData(Article $article): array - { - try { - $html = HttpFetcher::fetchHtml($article->url); - $parser = ArticleParserFactory::getParser($article->url); - - return $parser->extractData($html); - } catch (Exception $e) { - $this->logSaver->error('Exception while fetching article data', null, [ - 'url' => $article->url, - 'error' => $e->getMessage(), - ]); - - return []; - } - } -} diff --git a/tests/Feature/DuplicatePublishTest.php b/tests/Feature/DuplicatePublishTest.php index 1d589a27..862079b6 100644 --- a/tests/Feature/DuplicatePublishTest.php +++ b/tests/Feature/DuplicatePublishTest.php @@ -2,6 +2,7 @@ namespace Tests\Feature; +use App\Actions\FetchArticleDataAction; use App\Actions\PublishRouteArticleAction; use App\Enums\PublishStatusEnum; use App\Events\RouteArticleApproved; @@ -15,7 +16,6 @@ use App\Models\Route; use App\Models\RouteArticle; use App\Modules\Lemmy\Services\LemmyPublisher; -use App\Services\Article\ArticleFetcher; use App\Services\Log\LogSaver; use App\Services\Notification\NotificationService; use App\Services\Publishing\ArticlePublishingService; @@ -93,8 +93,8 @@ private function makeListener(): PublishApprovedArticleListener $service->shouldAllowMockingProtectedMethods(); $service->shouldReceive('makePublisher')->andReturn($publisher); - $fetcher = Mockery::mock(ArticleFetcher::class); - $fetcher->shouldNotReceive('fetchArticleData'); + $fetcher = Mockery::mock(FetchArticleDataAction::class); + $fetcher->shouldNotReceive('execute'); return new PublishApprovedArticleListener(new PublishRouteArticleAction($fetcher, $service, new NotificationService)); } @@ -159,8 +159,8 @@ public function test_two_queued_listeners_create_only_one_remote_post(): void $service->shouldAllowMockingProtectedMethods(); $service->shouldReceive('makePublisher')->andReturn($publisher); - $fetcher = Mockery::mock(ArticleFetcher::class); - $fetcher->shouldNotReceive('fetchArticleData'); + $fetcher = Mockery::mock(FetchArticleDataAction::class); + $fetcher->shouldNotReceive('execute'); $listener = new PublishApprovedArticleListener(new PublishRouteArticleAction($fetcher, $service, new NotificationService)); $listener->handle(new RouteArticleApproved($routeArticle)); diff --git a/tests/Feature/Jobs/ArticleDiscoveryForFeedJobEmptyFetchTest.php b/tests/Feature/Jobs/ArticleDiscoveryForFeedJobEmptyFetchTest.php index 6f697573..9e36c4de 100644 --- a/tests/Feature/Jobs/ArticleDiscoveryForFeedJobEmptyFetchTest.php +++ b/tests/Feature/Jobs/ArticleDiscoveryForFeedJobEmptyFetchTest.php @@ -2,6 +2,7 @@ namespace Tests\Feature\Jobs; +use App\Actions\FetchFeedArticlesAction; use App\Enums\ActivityTypeEnum; use App\Enums\NotificationSeverityEnum; use App\Enums\NotificationTypeEnum; @@ -10,7 +11,6 @@ use App\Models\Article; use App\Models\Feed; use App\Models\Notification; -use App\Services\Article\ArticleFetcher; use App\Services\Log\LogSaver; use App\Services\Notification\NotificationService; use Illuminate\Foundation\Testing\RefreshDatabase; @@ -27,8 +27,8 @@ class ArticleDiscoveryForFeedJobEmptyFetchTest extends TestCase */ private function runJobForFeed(Feed $feed, ?Collection $articles = null): void { - $fetcher = Mockery::mock(ArticleFetcher::class); - $fetcher->shouldReceive('getArticlesFromFeed') + $fetcher = Mockery::mock(FetchFeedArticlesAction::class); + $fetcher->shouldReceive('execute') ->andReturn($articles ?? collect()); (new ArticleDiscoveryForFeedJob($feed))->handle( diff --git a/tests/Feature/JobsAndEventsTest.php b/tests/Feature/JobsAndEventsTest.php index d6b9c794..cd617cb9 100644 --- a/tests/Feature/JobsAndEventsTest.php +++ b/tests/Feature/JobsAndEventsTest.php @@ -2,6 +2,8 @@ namespace Tests\Feature; +use App\Actions\FetchArticleDataAction; +use App\Actions\FetchFeedArticlesAction; use App\Enums\ApprovalStatusEnum; use App\Enums\LogLevelEnum; use App\Events\ActionPerformed; @@ -23,7 +25,6 @@ use App\Models\Route; use App\Models\RouteArticle; use App\Models\Setting; -use App\Services\Article\ArticleFetcher; use App\Services\Log\LogSaver; use App\Services\Notification\NotificationService; use Illuminate\Foundation\Testing\RefreshDatabase; @@ -63,18 +64,18 @@ public function test_article_discovery_for_feed_job_processes_feed(): void 'is_active' => true, ]); - // Mock the ArticleFetcher service in the container - $mockFetcher = \Mockery::mock(ArticleFetcher::class); + // Mock the feed fetch in the container + $mockFetcher = \Mockery::mock(FetchFeedArticlesAction::class); $article1 = Article::factory()->create(['url' => 'https://example.com/article1', 'feed_id' => $feed->id]); $article2 = Article::factory()->create(['url' => 'https://example.com/article2', 'feed_id' => $feed->id]); - $mockFetcher->shouldReceive('getArticlesFromFeed') + $mockFetcher->shouldReceive('execute') ->with($feed) ->andReturn(collect([$article1, $article2])); - $this->app->instance(ArticleFetcher::class, $mockFetcher); + $this->app->instance(FetchFeedArticlesAction::class, $mockFetcher); $logSaver = app(LogSaver::class); - $articleFetcher = app(ArticleFetcher::class); + $articleFetcher = app(FetchFeedArticlesAction::class); $job = new ArticleDiscoveryForFeedJob($feed); $job->handle($logSaver, $articleFetcher, app(NotificationService::class)); @@ -167,10 +168,10 @@ public function test_validate_article_listener_processes_new_article(): void 'feed_id' => $feed->id, ]); - // Mock ArticleFetcher to return valid article data - $mockFetcher = \Mockery::mock(ArticleFetcher::class); - $this->app->instance(ArticleFetcher::class, $mockFetcher); - $mockFetcher->shouldReceive('fetchArticleData') + // Mock the article-data fetch to return valid data + $mockFetcher = \Mockery::mock(FetchArticleDataAction::class); + $this->app->instance(FetchArticleDataAction::class, $mockFetcher); + $mockFetcher->shouldReceive('execute') ->with($article) ->andReturn([ 'title' => 'Belgian News', diff --git a/tests/Feature/Listeners/PublishApprovedArticleListenerTest.php b/tests/Feature/Listeners/PublishApprovedArticleListenerTest.php index 3e80de61..d3b7545b 100644 --- a/tests/Feature/Listeners/PublishApprovedArticleListenerTest.php +++ b/tests/Feature/Listeners/PublishApprovedArticleListenerTest.php @@ -2,6 +2,7 @@ namespace Tests\Feature\Listeners; +use App\Actions\FetchArticleDataAction; use App\Actions\PublishRouteArticleAction; use App\Enums\NotificationSeverityEnum; use App\Enums\NotificationTypeEnum; @@ -13,7 +14,6 @@ use App\Models\Notification; 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; @@ -48,8 +48,8 @@ public function test_exception_during_publishing_creates_error_notification(): v { $routeArticle = $this->createApprovedRouteArticle(); - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); - $articleFetcherMock->shouldReceive('fetchArticleData') + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); + $articleFetcherMock->shouldReceive('execute') ->once() ->andThrow(new Exception('Connection refused')); @@ -76,8 +76,8 @@ public function test_no_publication_created_creates_warning_notification(): void $extractedData = ['title' => 'Test Article', 'description' => 'Test description']; - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); - $articleFetcherMock->shouldReceive('fetchArticleData') + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); + $articleFetcherMock->shouldReceive('execute') ->once() ->andReturn($extractedData); @@ -106,8 +106,8 @@ public function test_successful_publish_does_not_create_notification(): void $extractedData = ['title' => 'Test Article', 'description' => 'Test description']; - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); - $articleFetcherMock->shouldReceive('fetchArticleData') + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); + $articleFetcherMock->shouldReceive('execute') ->once() ->andReturn($extractedData); @@ -131,8 +131,8 @@ public function test_skips_already_published_to_channel(): void 'platform_channel_id' => $routeArticle->platform_channel_id, ]); - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); - $articleFetcherMock->shouldNotReceive('fetchArticleData'); + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); + $articleFetcherMock->shouldNotReceive('execute'); $publishingServiceMock = Mockery::mock(ArticlePublishingService::class); $publishingServiceMock->shouldNotReceive('publishRouteArticle'); diff --git a/tests/Feature/MirrorDuplicateDetectionTest.php b/tests/Feature/MirrorDuplicateDetectionTest.php index ccd6b2ad..09bd6a5e 100644 --- a/tests/Feature/MirrorDuplicateDetectionTest.php +++ b/tests/Feature/MirrorDuplicateDetectionTest.php @@ -2,6 +2,7 @@ namespace Tests\Feature; +use App\Actions\FetchArticleDataAction; use App\Actions\PublishRouteArticleAction; use App\Enums\NotificationTypeEnum; use App\Enums\PublishStatusEnum; @@ -17,7 +18,6 @@ use App\Models\RouteArticle; use App\Modules\Lemmy\Services\LemmyApiService; use App\Modules\Lemmy\Services\LemmyPublisher; -use App\Services\Article\ArticleFetcher; use App\Services\Log\LogSaver; use App\Services\Notification\NotificationService; use App\Services\Publishing\ArticlePublishingService; @@ -124,8 +124,8 @@ public function test_a_skipped_duplicate_is_not_reported_as_a_publish_failure(): PlatformChannelPost::storePost($channel, '555', $article->url, 'Already Posted'); - $fetcher = Mockery::mock(ArticleFetcher::class); - $fetcher->shouldNotReceive('fetchArticleData'); + $fetcher = Mockery::mock(FetchArticleDataAction::class); + $fetcher->shouldNotReceive('execute'); $publisher = Mockery::mock(LemmyPublisher::class); $publisher->shouldNotReceive('publishToChannel'); @@ -141,8 +141,8 @@ public function test_a_genuine_failure_is_still_reported(): void { [$routeArticle] = $this->fixture; - $fetcher = Mockery::mock(ArticleFetcher::class); - $fetcher->shouldNotReceive('fetchArticleData'); + $fetcher = Mockery::mock(FetchArticleDataAction::class); + $fetcher->shouldNotReceive('execute'); $publisher = Mockery::mock(LemmyPublisher::class); $publisher->shouldReceive('publishToChannel')->andThrow(new \RuntimeException('Lemmy rejected the post')); diff --git a/tests/Feature/RouteArticleDecisionStampingTest.php b/tests/Feature/RouteArticleDecisionStampingTest.php index 54b61770..cb2bc0eb 100644 --- a/tests/Feature/RouteArticleDecisionStampingTest.php +++ b/tests/Feature/RouteArticleDecisionStampingTest.php @@ -3,6 +3,7 @@ namespace Tests\Feature; use App\Actions\CreateRouteArticlesAction; +use App\Actions\FetchArticleDataAction; use App\Actions\ValidateArticleAction; use App\Enums\ApprovalStatusEnum; use App\Livewire\Articles; @@ -13,7 +14,6 @@ use App\Models\Route; use App\Models\RouteArticle; use App\Models\User; -use App\Services\Article\ArticleFetcher; use Illuminate\Foundation\Testing\RefreshDatabase; use Livewire\Livewire; use Mockery; @@ -125,8 +125,8 @@ public function test_pending_articles_are_not_stamped_on_creation(): void private function validate(Article $article): void { - $fetcher = Mockery::mock(ArticleFetcher::class); - $fetcher->shouldReceive('fetchArticleData') + $fetcher = Mockery::mock(FetchArticleDataAction::class); + $fetcher->shouldReceive('execute') ->with($article) ->once() ->andReturn([ diff --git a/tests/Feature/ValidateArticleListenerTest.php b/tests/Feature/ValidateArticleListenerTest.php index cb232e6f..dd491355 100644 --- a/tests/Feature/ValidateArticleListenerTest.php +++ b/tests/Feature/ValidateArticleListenerTest.php @@ -3,6 +3,7 @@ namespace Tests\Feature; use App\Actions\CreateRouteArticlesAction; +use App\Actions\FetchArticleDataAction; use App\Actions\ValidateArticleAction; use App\Enums\ApprovalStatusEnum; use App\Events\NewArticleFetched; @@ -13,7 +14,6 @@ use App\Models\Keyword; use App\Models\Route; use App\Models\RouteArticle; -use App\Services\Article\ArticleFetcher; use Illuminate\Foundation\Testing\RefreshDatabase; use Mockery; use Tests\TestCase; @@ -24,8 +24,8 @@ class ValidateArticleListenerTest extends TestCase private function createListenerWithMockedFetcher(?string $content = 'Some article content'): ValidateArticleListener { - $articleFetcher = Mockery::mock(ArticleFetcher::class); - $articleFetcher->shouldReceive('fetchArticleData')->andReturn( + $articleFetcher = Mockery::mock(FetchArticleDataAction::class); + $articleFetcher->shouldReceive('execute')->andReturn( $content ? [ 'title' => 'Test Title', 'description' => 'Test description', @@ -111,8 +111,8 @@ public function test_listener_skips_articles_with_existing_publication(): void public function test_listener_handles_validation_errors_gracefully(): void { - $articleFetcher = Mockery::mock(ArticleFetcher::class); - $articleFetcher->shouldReceive('fetchArticleData')->andThrow(new \Exception('Fetch failed')); + $articleFetcher = Mockery::mock(FetchArticleDataAction::class); + $articleFetcher->shouldReceive('execute')->andThrow(new \Exception('Fetch failed')); $listener = new ValidateArticleListener( new ValidateArticleAction($articleFetcher, new CreateRouteArticlesAction) diff --git a/tests/Traits/CreatesArticleFetcher.php b/tests/Traits/CreatesFetchActions.php similarity index 59% rename from tests/Traits/CreatesArticleFetcher.php rename to tests/Traits/CreatesFetchActions.php index 4783b680..310dc9da 100644 --- a/tests/Traits/CreatesArticleFetcher.php +++ b/tests/Traits/CreatesFetchActions.php @@ -2,44 +2,42 @@ namespace Tests\Traits; +use App\Actions\FetchArticleDataAction; +use App\Actions\FetchFeedArticlesAction; use App\Actions\FetchRssArticlesAction; use App\Actions\FetchWebsiteArticlesAction; use App\Actions\SaveArticleAction; -use App\Services\Article\ArticleFetcher; use App\Services\Log\LogSaver; use Mockery; use Mockery\MockInterface; -trait CreatesArticleFetcher +trait CreatesFetchActions { - protected function createArticleFetcher(?LogSaver $logSaver = null): ArticleFetcher - { - if (! $logSaver) { - $logSaver = $this->mockLogSaver(); - } - - return $this->articleFetcherWith($logSaver); - } - - /** @return array{ArticleFetcher, MockInterface} */ - protected function createArticleFetcherWithMockedLogSaver(): array - { - $logSaver = $this->mockLogSaver(); - - return [$this->articleFetcherWith($logSaver), $logSaver]; - } - - private function articleFetcherWith(LogSaver $logSaver): ArticleFetcher + protected function createFeedFetcher(?LogSaver $logSaver = null): FetchFeedArticlesAction { + $logSaver ??= $this->mockLogSaver(); $saveArticle = new SaveArticleAction($logSaver); - return new ArticleFetcher( + return new FetchFeedArticlesAction( $logSaver, new FetchRssArticlesAction($logSaver, $saveArticle), new FetchWebsiteArticlesAction($logSaver, $saveArticle), ); } + protected function createArticleDataFetcher(?LogSaver $logSaver = null): FetchArticleDataAction + { + return new FetchArticleDataAction($logSaver ?? $this->mockLogSaver()); + } + + /** @return array{FetchFeedArticlesAction, MockInterface} */ + protected function createFeedFetcherWithMockedLogSaver(): array + { + $logSaver = $this->mockLogSaver(); + + return [$this->createFeedFetcher($logSaver), $logSaver]; + } + /** @return LogSaver&MockInterface */ private function mockLogSaver(): MockInterface { diff --git a/tests/Unit/Services/ArticleFetcherTest.php b/tests/Unit/Actions/FetchFeedArticlesActionTest.php similarity index 76% rename from tests/Unit/Services/ArticleFetcherTest.php rename to tests/Unit/Actions/FetchFeedArticlesActionTest.php index 7ca7df96..92e478a6 100644 --- a/tests/Unit/Services/ArticleFetcherTest.php +++ b/tests/Unit/Actions/FetchFeedArticlesActionTest.php @@ -1,20 +1,19 @@ Http::response('Mock HTML content', 200), ]); - // Create ArticleFetcher only when needed - tests will create their own } public function test_get_articles_from_feed_returns_collection(): void { - $articleFetcher = $this->createArticleFetcher(); + $articleFetcher = $this->createFeedFetcher(); $feed = Feed::factory()->create([ 'type' => 'rss', 'url' => 'https://example.com/feed.rss', ]); - $result = $articleFetcher->getArticlesFromFeed($feed); + $result = $articleFetcher->execute($feed); $this->assertInstanceOf(Collection::class, $result); } @@ -49,8 +47,8 @@ public function test_get_articles_from_rss_feed_returns_empty_collection(): void 'url' => 'https://example.com/feed.rss', ]); - $articleFetcher = $this->createArticleFetcher(); - $result = $articleFetcher->getArticlesFromFeed($feed); + $articleFetcher = $this->createFeedFetcher(); + $result = $articleFetcher->execute($feed); // RSS parsing is not implemented yet, should return empty collection $this->assertEmpty($result); @@ -63,8 +61,8 @@ public function test_get_articles_from_website_feed_handles_no_parser(): void 'url' => 'https://unsupported-site.com/', ]); - $articleFetcher = $this->createArticleFetcher(); - $result = $articleFetcher->getArticlesFromFeed($feed); + $articleFetcher = $this->createFeedFetcher(); + $result = $articleFetcher->execute($feed); // Should return empty collection when no parser is available $this->assertInstanceOf(Collection::class, $result); @@ -78,8 +76,8 @@ public function test_get_articles_from_unsupported_feed_type(): void 'url' => 'https://unsupported-feed-type.com/feed', ]); - $articleFetcher = $this->createArticleFetcher(); - $result = $articleFetcher->getArticlesFromFeed($feed); + $articleFetcher = $this->createFeedFetcher(); + $result = $articleFetcher->execute($feed); $this->assertInstanceOf(Collection::class, $result); $this->assertEmpty($result); @@ -91,8 +89,8 @@ public function test_fetch_article_data_returns_array(): void 'url' => 'https://example.com/article', ]); - $articleFetcher = $this->createArticleFetcher(); - $result = $articleFetcher->fetchArticleData($article); + $articleFetcher = $this->createArticleDataFetcher(); + $result = $articleFetcher->execute($article); $this->assertIsArray($result); // Will be empty array due to unsupported URL in test @@ -105,8 +103,8 @@ public function test_fetch_article_data_handles_invalid_url(): void 'url' => 'invalid-url', ]); - $articleFetcher = $this->createArticleFetcher(); - $result = $articleFetcher->fetchArticleData($article); + $articleFetcher = $this->createArticleDataFetcher(); + $result = $articleFetcher->execute($article); $this->assertIsArray($result); $this->assertEmpty($result); @@ -128,8 +126,8 @@ public function test_get_articles_from_feed_with_null_feed_type(): void $attributes['type'] = 'invalid_type'; $property->setValue($feed, $attributes); - $articleFetcher = $this->createArticleFetcher(); - $result = $articleFetcher->getArticlesFromFeed($feed); + $articleFetcher = $this->createFeedFetcher(); + $result = $articleFetcher->execute($feed); $this->assertInstanceOf(Collection::class, $result); $this->assertEmpty($result); @@ -148,8 +146,8 @@ public function test_get_articles_from_website_feed_with_supported_parser(): voi ]); // Test actual behavior - VRT parser should be available - $articleFetcher = $this->createArticleFetcher(); - $result = $articleFetcher->getArticlesFromFeed($feed); + $articleFetcher = $this->createFeedFetcher(); + $result = $articleFetcher->execute($feed); $this->assertInstanceOf(Collection::class, $result); // VRT parser will process the mocked HTML response @@ -164,8 +162,8 @@ public function test_get_articles_from_website_feed_handles_invalid_url(): void 'url' => 'https://invalid-domain-that-does-not-exist-12345.com/', ]); - $articleFetcher = $this->createArticleFetcher(); - $result = $articleFetcher->getArticlesFromFeed($feed); + $articleFetcher = $this->createFeedFetcher(); + $result = $articleFetcher->execute($feed); $this->assertInstanceOf(Collection::class, $result); $this->assertEmpty($result); @@ -183,8 +181,8 @@ public function test_fetch_article_data_with_supported_parser(): void ]); // Test actual behavior - VRT parser should be available - $articleFetcher = $this->createArticleFetcher(); - $result = $articleFetcher->fetchArticleData($article); + $articleFetcher = $this->createArticleDataFetcher(); + $result = $articleFetcher->execute($article); $this->assertIsArray($result); // VRT parser will process the mocked HTML response @@ -196,8 +194,8 @@ public function test_fetch_article_data_handles_unsupported_domain(): void 'url' => 'https://unsupported-domain.com/article', ]); - $articleFetcher = $this->createArticleFetcher(); - $result = $articleFetcher->fetchArticleData($article); + $articleFetcher = $this->createArticleDataFetcher(); + $result = $articleFetcher->execute($article); $this->assertIsArray($result); $this->assertEmpty($result); diff --git a/tests/Unit/Services/ArticleFetcherRssTest.php b/tests/Unit/Actions/FetchRssArticlesFeedTest.php similarity index 87% rename from tests/Unit/Services/ArticleFetcherRssTest.php rename to tests/Unit/Actions/FetchRssArticlesFeedTest.php index 95651cec..bf5295b3 100644 --- a/tests/Unit/Services/ArticleFetcherRssTest.php +++ b/tests/Unit/Actions/FetchRssArticlesFeedTest.php @@ -1,6 +1,6 @@ 'https://www.theguardian.com/international/rss', ]); - $fetcher = $this->createArticleFetcher(); - $result = $fetcher->getArticlesFromFeed($feed); + $fetcher = $this->createFeedFetcher(); + $result = $fetcher->execute($feed); $this->assertInstanceOf(Collection::class, $result); } @@ -70,8 +70,8 @@ public function test_get_articles_from_rss_feed_creates_articles(): void 'url' => 'https://www.theguardian.com/international/rss', ]); - $fetcher = $this->createArticleFetcher(); - $result = $fetcher->getArticlesFromFeed($feed); + $fetcher = $this->createFeedFetcher(); + $result = $fetcher->execute($feed); $this->assertCount(2, $result); $this->assertDatabaseHas('articles', [ @@ -99,8 +99,8 @@ public function test_get_articles_from_rss_feed_does_not_duplicate_existing(): v 'feed_id' => $feed->id, ]); - $fetcher = $this->createArticleFetcher(); - $result = $fetcher->getArticlesFromFeed($feed); + $fetcher = $this->createFeedFetcher(); + $result = $fetcher->execute($feed); $this->assertCount(2, $result); $this->assertEquals(1, Article::where('url', 'https://www.theguardian.com/world/2026/mar/08/first-article')->count()); @@ -127,8 +127,8 @@ public function test_get_articles_from_rss_feed_returns_known_articles_when_none $countBeforeFetch = Article::count(); - $fetcher = $this->createArticleFetcher(); - $result = $fetcher->getArticlesFromFeed($feed); + $fetcher = $this->createFeedFetcher(); + $result = $fetcher->execute($feed); $this->assertCount(2, $result); $this->assertSame($countBeforeFetch, Article::count()); @@ -144,8 +144,8 @@ public function test_get_articles_from_rss_feed_handles_invalid_xml(): void 'url' => 'https://www.theguardian.com/international/rss', ]); - $fetcher = $this->createArticleFetcher(); - $result = $fetcher->getArticlesFromFeed($feed); + $fetcher = $this->createFeedFetcher(); + $result = $fetcher->execute($feed); $this->assertInstanceOf(Collection::class, $result); $this->assertEmpty($result); @@ -163,8 +163,8 @@ public function test_get_articles_from_rss_feed_handles_empty_channel(): void 'url' => 'https://www.theguardian.com/international/rss', ]); - $fetcher = $this->createArticleFetcher(); - $result = $fetcher->getArticlesFromFeed($feed); + $fetcher = $this->createFeedFetcher(); + $result = $fetcher->execute($feed); $this->assertEmpty($result); } @@ -179,8 +179,8 @@ public function test_get_articles_from_rss_feed_handles_http_failure(): void 'url' => 'https://www.theguardian.com/international/rss', ]); - $fetcher = $this->createArticleFetcher(); - $result = $fetcher->getArticlesFromFeed($feed); + $fetcher = $this->createFeedFetcher(); + $result = $fetcher->execute($feed); $this->assertEmpty($result); } @@ -217,8 +217,8 @@ public function test_get_articles_from_belga_rss_feed_creates_articles(): void 'url' => 'https://www.belganewsagency.eu/feed', ]); - $fetcher = $this->createArticleFetcher(); - $result = $fetcher->getArticlesFromFeed($feed); + $fetcher = $this->createFeedFetcher(); + $result = $fetcher->execute($feed); $this->assertCount(2, $result); $this->assertDatabaseHas('articles', [ diff --git a/tests/Unit/Services/ArticleFetcherBelgaTest.php b/tests/Unit/Actions/FetchWebsiteArticlesBelgaTest.php similarity index 84% rename from tests/Unit/Services/ArticleFetcherBelgaTest.php rename to tests/Unit/Actions/FetchWebsiteArticlesBelgaTest.php index 8442dc05..176942f6 100644 --- a/tests/Unit/Services/ArticleFetcherBelgaTest.php +++ b/tests/Unit/Actions/FetchWebsiteArticlesBelgaTest.php @@ -1,6 +1,6 @@ Http::response($this->apiResponse(), 200)]); - $result = $this->createArticleFetcher()->getArticlesFromFeed($this->belgaFeed()); + $result = $this->createFeedFetcher()->execute($this->belgaFeed()); $this->assertCount(6, $result); $this->assertDatabaseHas('articles', [ @@ -62,7 +62,7 @@ public function test_associates_created_articles_with_the_feed(): void $feed = $this->belgaFeed(); - $this->createArticleFetcher()->getArticlesFromFeed($feed); + $this->createFeedFetcher()->execute($feed); $this->assertDatabaseHas('articles', [ 'url' => 'https://www.belganewsagency.eu/press-releases/35285/', @@ -76,7 +76,7 @@ public function test_returns_empty_collection_when_api_returns_no_articles(): vo // and must be a no-op rather than an error. Http::fake(['*' => Http::response('{"data":[],"_meta":{"total":0}}', 200)]); - $result = $this->createArticleFetcher()->getArticlesFromFeed($this->belgaFeed()); + $result = $this->createFeedFetcher()->execute($this->belgaFeed()); $this->assertEmpty($result); $this->assertDatabaseCount('articles', 0); @@ -87,7 +87,7 @@ public function test_returns_empty_collection_when_api_returns_an_error_page(): // The failure mode that caused #115: a 404 HTML body reaching the parser. Http::fake(['*' => Http::response('404 Not Found', 200)]); - $result = $this->createArticleFetcher()->getArticlesFromFeed($this->belgaFeed()); + $result = $this->createFeedFetcher()->execute($this->belgaFeed()); $this->assertEmpty($result); } diff --git a/tests/Unit/Actions/PublishRouteArticleActionTest.php b/tests/Unit/Actions/PublishRouteArticleActionTest.php index 7c9502ea..c1c597e5 100644 --- a/tests/Unit/Actions/PublishRouteArticleActionTest.php +++ b/tests/Unit/Actions/PublishRouteArticleActionTest.php @@ -2,6 +2,7 @@ namespace Tests\Unit\Actions; +use App\Actions\FetchArticleDataAction; use App\Actions\PublishRouteArticleAction; use App\Enums\NotificationTypeEnum; use App\Enums\PublishStatusEnum; @@ -10,7 +11,6 @@ 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; @@ -61,8 +61,8 @@ public function test_publish_uses_stored_article_data_without_fetching(): void 'image_url' => 'https://cdn.test/stored.jpg', ]); - $fetcher = Mockery::mock(ArticleFetcher::class); - $fetcher->shouldNotReceive('fetchArticleData'); + $fetcher = Mockery::mock(FetchArticleDataAction::class); + $fetcher->shouldNotReceive('execute'); $publishingService = Mockery::mock(ArticlePublishingService::class); $publishingService->shouldReceive('publishRouteArticle') @@ -87,8 +87,8 @@ public function test_publish_body_comes_from_description_not_content(): void 'content' => 'The very much longer full article body text', ]); - $fetcher = Mockery::mock(ArticleFetcher::class); - $fetcher->shouldNotReceive('fetchArticleData'); + $fetcher = Mockery::mock(FetchArticleDataAction::class); + $fetcher->shouldNotReceive('execute'); $captured = null; $publishingService = Mockery::mock(ArticlePublishingService::class); @@ -116,8 +116,8 @@ public function test_publish_falls_back_to_fetching_when_description_is_blank(): $fetched = ['title' => 'Fetched Title', 'description' => 'Fetched description']; - $fetcher = Mockery::mock(ArticleFetcher::class); - $fetcher->shouldReceive('fetchArticleData')->once()->andReturn($fetched); + $fetcher = Mockery::mock(FetchArticleDataAction::class); + $fetcher->shouldReceive('execute')->once()->andReturn($fetched); $publishingService = Mockery::mock(ArticlePublishingService::class); $publishingService->shouldReceive('publishRouteArticle') @@ -141,8 +141,8 @@ public function test_publish_falls_back_to_fetching_when_article_has_no_stored_d 'thumbnail' => 'https://cdn.test/fetched.jpg', ]; - $fetcher = Mockery::mock(ArticleFetcher::class); - $fetcher->shouldReceive('fetchArticleData')->once()->andReturn($fetched); + $fetcher = Mockery::mock(FetchArticleDataAction::class); + $fetcher->shouldReceive('execute')->once()->andReturn($fetched); $publishingService = Mockery::mock(ArticlePublishingService::class); $publishingService->shouldReceive('publishRouteArticle') @@ -160,8 +160,8 @@ public function test_publish_fails_when_fallback_fetch_returns_nothing(): void { $routeArticle = $this->createRouteArticle(['title' => 'Unreachable Article'], unvalidated: true); - $fetcher = Mockery::mock(ArticleFetcher::class); - $fetcher->shouldReceive('fetchArticleData')->once()->andReturn([]); + $fetcher = Mockery::mock(FetchArticleDataAction::class); + $fetcher->shouldReceive('execute')->once()->andReturn([]); $publishingService = Mockery::mock(ArticlePublishingService::class); $publishingService->shouldNotReceive('publishRouteArticle'); @@ -181,8 +181,8 @@ public function test_a_failure_stores_the_reason_and_stops_further_attempts(): v { $routeArticle = $this->createRouteArticle([], unvalidated: true); - $fetcher = Mockery::mock(ArticleFetcher::class); - $fetcher->shouldReceive('fetchArticleData')->andReturn([]); + $fetcher = Mockery::mock(FetchArticleDataAction::class); + $fetcher->shouldReceive('execute')->andReturn([]); $publishingService = Mockery::mock(ArticlePublishingService::class); @@ -205,8 +205,8 @@ public function test_a_successful_publish_clears_an_earlier_failure(): void ]); $routeArticle->recordPublishFailed('an earlier failure'); - $fetcher = Mockery::mock(ArticleFetcher::class); - $fetcher->shouldNotReceive('fetchArticleData'); + $fetcher = Mockery::mock(FetchArticleDataAction::class); + $fetcher->shouldNotReceive('execute'); $publishingService = Mockery::mock(ArticlePublishingService::class); $publishingService->shouldReceive('publishRouteArticle') @@ -226,8 +226,8 @@ public function test_publish_fails_when_fallback_recovers_only_a_title(): void { $routeArticle = $this->createRouteArticle([], unvalidated: true); - $fetcher = Mockery::mock(ArticleFetcher::class); - $fetcher->shouldReceive('fetchArticleData')->once()->andReturn(['title' => 'Recovered Title']); + $fetcher = Mockery::mock(FetchArticleDataAction::class); + $fetcher->shouldReceive('execute')->once()->andReturn(['title' => 'Recovered Title']); $publishingService = Mockery::mock(ArticlePublishingService::class); $publishingService->shouldNotReceive('publishRouteArticle'); @@ -245,8 +245,8 @@ public function test_publish_succeeds_when_fallback_returns_a_description_withou $fetched = ['title' => 'Recovered Title', 'description' => 'Recovered description']; - $fetcher = Mockery::mock(ArticleFetcher::class); - $fetcher->shouldReceive('fetchArticleData')->once()->andReturn($fetched); + $fetcher = Mockery::mock(FetchArticleDataAction::class); + $fetcher->shouldReceive('execute')->once()->andReturn($fetched); $publishingService = Mockery::mock(ArticlePublishingService::class); $publishingService->shouldReceive('publishRouteArticle') @@ -268,8 +268,8 @@ public function test_publish_does_not_refetch_for_articles_stored_before_image_u 'image_url' => null, ]); - $fetcher = Mockery::mock(ArticleFetcher::class); - $fetcher->shouldNotReceive('fetchArticleData'); + $fetcher = Mockery::mock(FetchArticleDataAction::class); + $fetcher->shouldNotReceive('execute'); $publishingService = Mockery::mock(ArticlePublishingService::class); $publishingService->shouldReceive('publishRouteArticle') diff --git a/tests/Unit/Actions/ValidateArticleActionTest.php b/tests/Unit/Actions/ValidateArticleActionTest.php index fa753b2e..3b699d68 100644 --- a/tests/Unit/Actions/ValidateArticleActionTest.php +++ b/tests/Unit/Actions/ValidateArticleActionTest.php @@ -3,6 +3,7 @@ namespace Tests\Unit\Actions; use App\Actions\CreateRouteArticlesAction; +use App\Actions\FetchArticleDataAction; use App\Actions\ValidateArticleAction; use App\Enums\ApprovalStatusEnum; use App\Models\Article; @@ -12,7 +13,6 @@ use App\Models\Route; use App\Models\RouteArticle; use App\Models\Setting; -use App\Services\Article\ArticleFetcher; use Illuminate\Foundation\Testing\RefreshDatabase; use Mockery; use Mockery\MockInterface; @@ -29,7 +29,7 @@ class ValidateArticleActionTest extends TestCase protected function setUp(): void { parent::setUp(); - $this->articleFetcher = Mockery::mock(ArticleFetcher::class); + $this->articleFetcher = Mockery::mock(FetchArticleDataAction::class); $this->validateArticle = new ValidateArticleAction($this->articleFetcher, new CreateRouteArticlesAction); } @@ -56,7 +56,7 @@ private function mockFetchReturning(Article $article, ?string $content, ?string } $this->articleFetcher - ->shouldReceive('fetchArticleData') + ->shouldReceive('execute') ->with($article) ->once() ->andReturn($data); diff --git a/tests/Unit/Jobs/ArticleDiscoveryForFeedJobTest.php b/tests/Unit/Jobs/ArticleDiscoveryForFeedJobTest.php index 242f1ee5..58326c95 100644 --- a/tests/Unit/Jobs/ArticleDiscoveryForFeedJobTest.php +++ b/tests/Unit/Jobs/ArticleDiscoveryForFeedJobTest.php @@ -2,10 +2,10 @@ namespace Tests\Unit\Jobs; +use App\Actions\FetchFeedArticlesAction; use App\Jobs\ArticleDiscoveryForFeedJob; use App\Models\Article; use App\Models\Feed; -use App\Services\Article\ArticleFetcher; use App\Services\Log\LogSaver; use App\Services\Notification\NotificationService; use Illuminate\Contracts\Queue\ShouldQueue; @@ -63,9 +63,9 @@ public function test_handle_fetches_articles_and_updates_feed(): void $mockArticles = collect(['article1', 'article2']); - // Mock ArticleFetcher - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); - $articleFetcherMock->shouldReceive('getArticlesFromFeed') + // Mock the feed fetch + $articleFetcherMock = Mockery::mock(FetchFeedArticlesAction::class); + $articleFetcherMock->shouldReceive('execute') ->once() ->with($feed) ->andReturn($mockArticles); @@ -189,9 +189,9 @@ public function test_handle_logs_start_message_with_correct_context(): void $mockArticles = collect([new Article]); - // Mock ArticleFetcher - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); - $articleFetcherMock->shouldReceive('getArticlesFromFeed') + // Mock the feed fetch + $articleFetcherMock = Mockery::mock(FetchFeedArticlesAction::class); + $articleFetcherMock->shouldReceive('execute') ->once() ->andReturn($mockArticles); diff --git a/tests/Unit/Jobs/PublishNextArticleJobTest.php b/tests/Unit/Jobs/PublishNextArticleJobTest.php index 04931a6f..c813d9d6 100644 --- a/tests/Unit/Jobs/PublishNextArticleJobTest.php +++ b/tests/Unit/Jobs/PublishNextArticleJobTest.php @@ -2,6 +2,7 @@ namespace Tests\Unit\Jobs; +use App\Actions\FetchArticleDataAction; use App\Actions\PublishRouteArticleAction; use App\Enums\NotificationSeverityEnum; use App\Enums\NotificationTypeEnum; @@ -15,7 +16,6 @@ use App\Models\Route; use App\Models\RouteArticle; use App\Models\Setting; -use App\Services\Article\ArticleFetcher; use App\Services\Notification\NotificationService; use App\Services\Publishing\ArticlePublishingService; use App\Services\Publishing\PublishOutcome; @@ -94,7 +94,7 @@ public function test_job_uses_queueable_trait(): void public function test_handle_returns_early_when_no_approved_route_articles(): void { - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); $publishingServiceMock = Mockery::mock(ArticlePublishingService::class); $job = new PublishNextArticleJob; @@ -113,7 +113,7 @@ public function test_handle_returns_early_when_no_unpublished_approved_route_art 'platform_channel_id' => $routeArticle->platform_channel_id, ]); - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); $publishingServiceMock = Mockery::mock(ArticlePublishingService::class); $job = new PublishNextArticleJob; @@ -131,7 +131,7 @@ public function test_handle_skips_non_approved_route_articles(): void RouteArticle::factory()->forRoute($route)->pending()->create(['article_id' => $article->id]); - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); $publishingServiceMock = Mockery::mock(ArticlePublishingService::class); $job = new PublishNextArticleJob; @@ -160,8 +160,8 @@ public function test_handle_publishes_oldest_approved_route_article(): void $extractedData = ['title' => 'Test Article', 'description' => 'Test description']; - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); - $articleFetcherMock->shouldReceive('fetchArticleData') + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); + $articleFetcherMock->shouldReceive('execute') ->once() ->with(Mockery::on(fn ($article) => $article->id === $olderArticle->id)) ->andReturn($extractedData); @@ -189,8 +189,8 @@ public function test_handle_throws_exception_on_publishing_failure(): void $extractedData = ['title' => 'Test Article', 'description' => 'Test description']; $publishException = new PublishException($article, null); - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); - $articleFetcherMock->shouldReceive('fetchArticleData') + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); + $articleFetcherMock->shouldReceive('execute') ->once() ->andReturn($extractedData); @@ -215,10 +215,10 @@ public function test_handle_skips_publishing_when_last_publication_within_interv ]); Setting::setArticlePublishingInterval(10); - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); $publishingServiceMock = Mockery::mock(ArticlePublishingService::class); - $articleFetcherMock->shouldNotReceive('fetchArticleData'); + $articleFetcherMock->shouldNotReceive('execute'); $publishingServiceMock->shouldNotReceive('publishRouteArticle'); $job = new PublishNextArticleJob; @@ -235,10 +235,10 @@ public function test_handle_skips_publishing_when_daily_cap_reached(): void Setting::setArticlePublishingInterval(0); Setting::setDailyPublishCap(3); - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); $publishingServiceMock = Mockery::mock(ArticlePublishingService::class); - $articleFetcherMock->shouldNotReceive('fetchArticleData'); + $articleFetcherMock->shouldNotReceive('execute'); $publishingServiceMock->shouldNotReceive('publishRouteArticle'); $job = new PublishNextArticleJob; @@ -255,8 +255,8 @@ public function test_handle_publishes_when_below_daily_cap(): void Setting::setArticlePublishingInterval(0); Setting::setDailyPublishCap(3); - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); - $articleFetcherMock->shouldReceive('fetchArticleData') + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); + $articleFetcherMock->shouldReceive('execute') ->once() ->andReturn(['title' => 'Test Article', 'description' => 'Test description']); @@ -279,8 +279,8 @@ public function test_handle_publishes_when_daily_cap_is_zero(): void Setting::setArticlePublishingInterval(0); Setting::setDailyPublishCap(0); - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); - $articleFetcherMock->shouldReceive('fetchArticleData') + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); + $articleFetcherMock->shouldReceive('execute') ->once() ->andReturn(['title' => 'Test Article', 'description' => 'Test description']); @@ -307,10 +307,10 @@ public function test_daily_cap_counts_each_channel_publication_separately(): voi Setting::setArticlePublishingInterval(0); Setting::setDailyPublishCap(3); - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); $publishingServiceMock = Mockery::mock(ArticlePublishingService::class); - $articleFetcherMock->shouldNotReceive('fetchArticleData'); + $articleFetcherMock->shouldNotReceive('execute'); $publishingServiceMock->shouldNotReceive('publishRouteArticle'); $job = new PublishNextArticleJob; @@ -327,8 +327,8 @@ public function test_handle_ignores_publications_from_previous_days(): void Setting::setArticlePublishingInterval(0); Setting::setDailyPublishCap(3); - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); - $articleFetcherMock->shouldReceive('fetchArticleData') + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); + $articleFetcherMock->shouldReceive('execute') ->once() ->andReturn(['title' => 'Test Article', 'description' => 'Test description']); @@ -354,8 +354,8 @@ public function test_handle_publishes_when_last_publication_beyond_interval(): v $extractedData = ['title' => 'Test Article', 'description' => 'Test description']; - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); - $articleFetcherMock->shouldReceive('fetchArticleData') + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); + $articleFetcherMock->shouldReceive('execute') ->once() ->andReturn($extractedData); @@ -381,8 +381,8 @@ public function test_handle_publishes_when_interval_is_zero(): void $extractedData = ['title' => 'Test Article', 'description' => 'Test description']; - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); - $articleFetcherMock->shouldReceive('fetchArticleData') + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); + $articleFetcherMock->shouldReceive('execute') ->once() ->andReturn($extractedData); @@ -408,8 +408,8 @@ public function test_handle_publishes_when_last_publication_exactly_at_interval( $extractedData = ['title' => 'Test Article', 'description' => 'Test description']; - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); - $articleFetcherMock->shouldReceive('fetchArticleData') + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); + $articleFetcherMock->shouldReceive('execute') ->once() ->andReturn($extractedData); @@ -432,8 +432,8 @@ public function test_handle_publishes_when_no_previous_publications_exist(): voi $extractedData = ['title' => 'Test Article', 'description' => 'Test description']; - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); - $articleFetcherMock->shouldReceive('fetchArticleData') + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); + $articleFetcherMock->shouldReceive('execute') ->once() ->andReturn($extractedData); @@ -454,8 +454,8 @@ public function test_handle_creates_warning_notification_when_no_publication_cre $extractedData = ['title' => 'No Route Article', 'description' => 'Test description']; - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); - $articleFetcherMock->shouldReceive('fetchArticleData') + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); + $articleFetcherMock->shouldReceive('execute') ->once() ->andReturn($extractedData); @@ -486,8 +486,8 @@ public function test_handle_creates_notification_on_publish_exception(): void $extractedData = ['title' => 'Failing Article', 'description' => 'Test description']; $publishException = new PublishException($article, null); - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); - $articleFetcherMock->shouldReceive('fetchArticleData') + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); + $articleFetcherMock->shouldReceive('execute') ->once() ->andReturn($extractedData); @@ -520,8 +520,8 @@ public function test_handle_skips_route_articles_that_previously_failed(): void $routeArticle = $this->createApprovedRouteArticle(); $routeArticle->recordPublishFailed('couldnt_find_community'); - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); - $articleFetcherMock->shouldNotReceive('fetchArticleData'); + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); + $articleFetcherMock->shouldNotReceive('execute'); $publishingServiceMock = Mockery::mock(ArticlePublishingService::class); $publishingServiceMock->shouldNotReceive('publishRouteArticle'); @@ -541,8 +541,8 @@ public function test_handle_publishes_a_later_article_when_the_oldest_has_failed $next = $this->createApprovedRouteArticle(['title' => 'Next Article']); $next->update(['created_at' => now()->subDay()]); - $articleFetcherMock = Mockery::mock(ArticleFetcher::class); - $articleFetcherMock->shouldReceive('fetchArticleData') + $articleFetcherMock = Mockery::mock(FetchArticleDataAction::class); + $articleFetcherMock->shouldReceive('execute') ->once() ->andReturn(['title' => 'Next Article', 'description' => 'Test description']);