94 - Add publishing failure notifications to job and listener
Some checks failed
CI / ci (push) Failing after 5m7s
Some checks failed
CI / ci (push) Failing after 5m7s
This commit is contained in:
parent
062b00d01c
commit
d21c054250
4 changed files with 288 additions and 29 deletions
|
|
@ -3,12 +3,15 @@
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Enums\LogLevelEnum;
|
use App\Enums\LogLevelEnum;
|
||||||
|
use App\Enums\NotificationSeverityEnum;
|
||||||
|
use App\Enums\NotificationTypeEnum;
|
||||||
use App\Events\ActionPerformed;
|
use App\Events\ActionPerformed;
|
||||||
use App\Exceptions\PublishException;
|
use App\Exceptions\PublishException;
|
||||||
use App\Models\Article;
|
use App\Models\Article;
|
||||||
use App\Models\ArticlePublication;
|
use App\Models\ArticlePublication;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Services\Article\ArticleFetcher;
|
use App\Services\Article\ArticleFetcher;
|
||||||
|
use App\Services\Notification\NotificationService;
|
||||||
use App\Services\Publishing\ArticlePublishingService;
|
use App\Services\Publishing\ArticlePublishingService;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
|
@ -33,7 +36,7 @@ public function __construct()
|
||||||
*
|
*
|
||||||
* @throws PublishException
|
* @throws PublishException
|
||||||
*/
|
*/
|
||||||
public function handle(ArticleFetcher $articleFetcher, ArticlePublishingService $publishingService): void
|
public function handle(ArticleFetcher $articleFetcher, ArticlePublishingService $publishingService, NotificationService $notificationService): void
|
||||||
{
|
{
|
||||||
$interval = Setting::getArticlePublishingInterval();
|
$interval = Setting::getArticlePublishingInterval();
|
||||||
|
|
||||||
|
|
@ -62,22 +65,43 @@ public function handle(ArticleFetcher $articleFetcher, ArticlePublishingService
|
||||||
'created_at' => $article->created_at,
|
'created_at' => $article->created_at,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Fetch article data
|
|
||||||
$extractedData = $articleFetcher->fetchArticleData($article);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$publishingService->publishToRoutedChannels($article, $extractedData);
|
$extractedData = $articleFetcher->fetchArticleData($article);
|
||||||
|
$publications = $publishingService->publishToRoutedChannels($article, $extractedData);
|
||||||
|
|
||||||
|
if ($publications->isNotEmpty()) {
|
||||||
ActionPerformed::dispatch('Successfully published article', LogLevelEnum::INFO, [
|
ActionPerformed::dispatch('Successfully published article', LogLevelEnum::INFO, [
|
||||||
'article_id' => $article->id,
|
'article_id' => $article->id,
|
||||||
'title' => $article->title,
|
'title' => $article->title,
|
||||||
]);
|
]);
|
||||||
|
} else {
|
||||||
|
ActionPerformed::dispatch('No publications created for article', LogLevelEnum::WARNING, [
|
||||||
|
'article_id' => $article->id,
|
||||||
|
'title' => $article->title,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$notificationService->send(
|
||||||
|
NotificationTypeEnum::PUBLISH_FAILED,
|
||||||
|
NotificationSeverityEnum::WARNING,
|
||||||
|
"Publish failed: {$article->title}",
|
||||||
|
'No publications were created for this article. Check channel routing configuration.',
|
||||||
|
$article,
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (PublishException $e) {
|
} catch (PublishException $e) {
|
||||||
ActionPerformed::dispatch('Failed to publish article', LogLevelEnum::ERROR, [
|
ActionPerformed::dispatch('Failed to publish article', LogLevelEnum::ERROR, [
|
||||||
'article_id' => $article->id,
|
'article_id' => $article->id,
|
||||||
'error' => $e->getMessage(),
|
'error' => $e->getMessage(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$notificationService->send(
|
||||||
|
NotificationTypeEnum::PUBLISH_FAILED,
|
||||||
|
NotificationSeverityEnum::ERROR,
|
||||||
|
"Publish failed: {$article->title}",
|
||||||
|
$e->getMessage(),
|
||||||
|
$article,
|
||||||
|
);
|
||||||
|
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,12 @@
|
||||||
namespace App\Listeners;
|
namespace App\Listeners;
|
||||||
|
|
||||||
use App\Enums\LogLevelEnum;
|
use App\Enums\LogLevelEnum;
|
||||||
|
use App\Enums\NotificationSeverityEnum;
|
||||||
|
use App\Enums\NotificationTypeEnum;
|
||||||
use App\Events\ActionPerformed;
|
use App\Events\ActionPerformed;
|
||||||
use App\Events\ArticleApproved;
|
use App\Events\ArticleApproved;
|
||||||
use App\Services\Article\ArticleFetcher;
|
use App\Services\Article\ArticleFetcher;
|
||||||
|
use App\Services\Notification\NotificationService;
|
||||||
use App\Services\Publishing\ArticlePublishingService;
|
use App\Services\Publishing\ArticlePublishingService;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
|
@ -16,7 +19,8 @@ class PublishApprovedArticleListener implements ShouldQueue
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private ArticleFetcher $articleFetcher,
|
private ArticleFetcher $articleFetcher,
|
||||||
private ArticlePublishingService $publishingService
|
private ArticlePublishingService $publishingService,
|
||||||
|
private NotificationService $notificationService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function handle(ArticleApproved $event): void
|
public function handle(ArticleApproved $event): void
|
||||||
|
|
@ -53,6 +57,14 @@ public function handle(ArticleApproved $event): void
|
||||||
'article_id' => $article->id,
|
'article_id' => $article->id,
|
||||||
'title' => $article->title,
|
'title' => $article->title,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$this->notificationService->send(
|
||||||
|
NotificationTypeEnum::PUBLISH_FAILED,
|
||||||
|
NotificationSeverityEnum::WARNING,
|
||||||
|
"Publish failed: {$article->title}",
|
||||||
|
'No publications were created for this article. Check channel routing configuration.',
|
||||||
|
$article,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$article->update(['publish_status' => 'error']);
|
$article->update(['publish_status' => 'error']);
|
||||||
|
|
@ -61,6 +73,14 @@ public function handle(ArticleApproved $event): void
|
||||||
'article_id' => $article->id,
|
'article_id' => $article->id,
|
||||||
'error' => $e->getMessage(),
|
'error' => $e->getMessage(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$this->notificationService->send(
|
||||||
|
NotificationTypeEnum::PUBLISH_FAILED,
|
||||||
|
NotificationSeverityEnum::ERROR,
|
||||||
|
"Publish failed: {$article->title}",
|
||||||
|
$e->getMessage(),
|
||||||
|
$article,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
123
tests/Feature/Listeners/PublishApprovedArticleListenerTest.php
Normal file
123
tests/Feature/Listeners/PublishApprovedArticleListenerTest.php
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Listeners;
|
||||||
|
|
||||||
|
use App\Enums\NotificationSeverityEnum;
|
||||||
|
use App\Enums\NotificationTypeEnum;
|
||||||
|
use App\Events\ArticleApproved;
|
||||||
|
use App\Listeners\PublishApprovedArticleListener;
|
||||||
|
use App\Models\Article;
|
||||||
|
use App\Models\Feed;
|
||||||
|
use App\Models\Notification;
|
||||||
|
use App\Services\Article\ArticleFetcher;
|
||||||
|
use App\Services\Notification\NotificationService;
|
||||||
|
use App\Services\Publishing\ArticlePublishingService;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Mockery;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class PublishApprovedArticleListenerTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_exception_during_publishing_creates_error_notification(): void
|
||||||
|
{
|
||||||
|
$feed = Feed::factory()->create();
|
||||||
|
$article = Article::factory()->create([
|
||||||
|
'feed_id' => $feed->id,
|
||||||
|
'approval_status' => 'approved',
|
||||||
|
'title' => 'Test Article',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$articleFetcherMock = Mockery::mock(ArticleFetcher::class);
|
||||||
|
$articleFetcherMock->shouldReceive('fetchArticleData')
|
||||||
|
->once()
|
||||||
|
->andThrow(new Exception('Connection refused'));
|
||||||
|
|
||||||
|
$publishingServiceMock = Mockery::mock(ArticlePublishingService::class);
|
||||||
|
|
||||||
|
$listener = new PublishApprovedArticleListener($articleFetcherMock, $publishingServiceMock, new NotificationService);
|
||||||
|
$listener->handle(new ArticleApproved($article));
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('notifications', [
|
||||||
|
'type' => NotificationTypeEnum::PUBLISH_FAILED->value,
|
||||||
|
'severity' => NotificationSeverityEnum::ERROR->value,
|
||||||
|
'notifiable_type' => $article->getMorphClass(),
|
||||||
|
'notifiable_id' => $article->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$notification = Notification::first();
|
||||||
|
$this->assertStringContainsString('Test Article', $notification->title);
|
||||||
|
$this->assertStringContainsString('Connection refused', $notification->message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_no_publications_created_creates_warning_notification(): void
|
||||||
|
{
|
||||||
|
$feed = Feed::factory()->create();
|
||||||
|
$article = Article::factory()->create([
|
||||||
|
'feed_id' => $feed->id,
|
||||||
|
'approval_status' => 'approved',
|
||||||
|
'title' => 'Test Article',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extractedData = ['title' => 'Test Article'];
|
||||||
|
|
||||||
|
$articleFetcherMock = Mockery::mock(ArticleFetcher::class);
|
||||||
|
$articleFetcherMock->shouldReceive('fetchArticleData')
|
||||||
|
->once()
|
||||||
|
->andReturn($extractedData);
|
||||||
|
|
||||||
|
$publishingServiceMock = Mockery::mock(ArticlePublishingService::class);
|
||||||
|
$publishingServiceMock->shouldReceive('publishToRoutedChannels')
|
||||||
|
->once()
|
||||||
|
->andReturn(new Collection);
|
||||||
|
|
||||||
|
$listener = new PublishApprovedArticleListener($articleFetcherMock, $publishingServiceMock, new NotificationService);
|
||||||
|
$listener->handle(new ArticleApproved($article));
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('notifications', [
|
||||||
|
'type' => NotificationTypeEnum::PUBLISH_FAILED->value,
|
||||||
|
'severity' => NotificationSeverityEnum::WARNING->value,
|
||||||
|
'notifiable_type' => $article->getMorphClass(),
|
||||||
|
'notifiable_id' => $article->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$notification = Notification::first();
|
||||||
|
$this->assertStringContainsString('Test Article', $notification->title);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_successful_publish_does_not_create_notification(): void
|
||||||
|
{
|
||||||
|
$feed = Feed::factory()->create();
|
||||||
|
$article = Article::factory()->create([
|
||||||
|
'feed_id' => $feed->id,
|
||||||
|
'approval_status' => 'approved',
|
||||||
|
'title' => 'Test Article',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extractedData = ['title' => 'Test Article'];
|
||||||
|
|
||||||
|
$articleFetcherMock = Mockery::mock(ArticleFetcher::class);
|
||||||
|
$articleFetcherMock->shouldReceive('fetchArticleData')
|
||||||
|
->once()
|
||||||
|
->andReturn($extractedData);
|
||||||
|
|
||||||
|
$publishingServiceMock = Mockery::mock(ArticlePublishingService::class);
|
||||||
|
$publishingServiceMock->shouldReceive('publishToRoutedChannels')
|
||||||
|
->once()
|
||||||
|
->andReturn(new Collection(['publication']));
|
||||||
|
|
||||||
|
$listener = new PublishApprovedArticleListener($articleFetcherMock, $publishingServiceMock, new NotificationService);
|
||||||
|
$listener->handle(new ArticleApproved($article));
|
||||||
|
|
||||||
|
$this->assertDatabaseCount('notifications', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function tearDown(): void
|
||||||
|
{
|
||||||
|
Mockery::close();
|
||||||
|
parent::tearDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,15 +2,20 @@
|
||||||
|
|
||||||
namespace Tests\Unit\Jobs;
|
namespace Tests\Unit\Jobs;
|
||||||
|
|
||||||
|
use App\Enums\NotificationSeverityEnum;
|
||||||
|
use App\Enums\NotificationTypeEnum;
|
||||||
use App\Exceptions\PublishException;
|
use App\Exceptions\PublishException;
|
||||||
use App\Jobs\PublishNextArticleJob;
|
use App\Jobs\PublishNextArticleJob;
|
||||||
use App\Models\Article;
|
use App\Models\Article;
|
||||||
use App\Models\ArticlePublication;
|
use App\Models\ArticlePublication;
|
||||||
use App\Models\Feed;
|
use App\Models\Feed;
|
||||||
|
use App\Models\Notification;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Services\Article\ArticleFetcher;
|
use App\Services\Article\ArticleFetcher;
|
||||||
|
use App\Services\Notification\NotificationService;
|
||||||
use App\Services\Publishing\ArticlePublishingService;
|
use App\Services\Publishing\ArticlePublishingService;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Mockery;
|
use Mockery;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
|
@ -18,9 +23,12 @@ class PublishNextArticleJobTest extends TestCase
|
||||||
{
|
{
|
||||||
use RefreshDatabase;
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
private NotificationService $notificationService;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
$this->notificationService = new NotificationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_constructor_sets_correct_queue(): void
|
public function test_constructor_sets_correct_queue(): void
|
||||||
|
|
@ -71,7 +79,7 @@ public function test_handle_returns_early_when_no_approved_articles(): void
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
$publishingServiceMock = \Mockery::mock(ArticlePublishingService::class);
|
$publishingServiceMock = \Mockery::mock(ArticlePublishingService::class);
|
||||||
$job->handle($articleFetcherMock, $publishingServiceMock);
|
$job->handle($articleFetcherMock, $publishingServiceMock, $this->notificationService);
|
||||||
|
|
||||||
// Assert - Should complete without error
|
// Assert - Should complete without error
|
||||||
$this->assertTrue(true);
|
$this->assertTrue(true);
|
||||||
|
|
@ -96,7 +104,7 @@ public function test_handle_returns_early_when_no_unpublished_approved_articles(
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
$publishingServiceMock = \Mockery::mock(ArticlePublishingService::class);
|
$publishingServiceMock = \Mockery::mock(ArticlePublishingService::class);
|
||||||
$job->handle($articleFetcherMock, $publishingServiceMock);
|
$job->handle($articleFetcherMock, $publishingServiceMock, $this->notificationService);
|
||||||
|
|
||||||
// Assert - Should complete without error
|
// Assert - Should complete without error
|
||||||
$this->assertTrue(true);
|
$this->assertTrue(true);
|
||||||
|
|
@ -122,7 +130,7 @@ public function test_handle_skips_non_approved_articles(): void
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
$publishingServiceMock = \Mockery::mock(ArticlePublishingService::class);
|
$publishingServiceMock = \Mockery::mock(ArticlePublishingService::class);
|
||||||
$job->handle($articleFetcherMock, $publishingServiceMock);
|
$job->handle($articleFetcherMock, $publishingServiceMock, $this->notificationService);
|
||||||
|
|
||||||
// Assert - Should complete without error (no approved articles to process)
|
// Assert - Should complete without error (no approved articles to process)
|
||||||
$this->assertTrue(true);
|
$this->assertTrue(true);
|
||||||
|
|
@ -167,12 +175,13 @@ public function test_handle_publishes_oldest_approved_article(): void
|
||||||
return $article->id === $olderArticle->id;
|
return $article->id === $olderArticle->id;
|
||||||
}),
|
}),
|
||||||
$extractedData
|
$extractedData
|
||||||
);
|
)
|
||||||
|
->andReturn(new Collection(['publication']));
|
||||||
|
|
||||||
$job = new PublishNextArticleJob;
|
$job = new PublishNextArticleJob;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
$job->handle($articleFetcherMock, $publishingServiceMock);
|
$job->handle($articleFetcherMock, $publishingServiceMock, $this->notificationService);
|
||||||
|
|
||||||
// Assert - Mockery expectations are verified in tearDown
|
// Assert - Mockery expectations are verified in tearDown
|
||||||
$this->assertTrue(true);
|
$this->assertTrue(true);
|
||||||
|
|
@ -209,7 +218,7 @@ public function test_handle_throws_exception_on_publishing_failure(): void
|
||||||
$this->expectException(PublishException::class);
|
$this->expectException(PublishException::class);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
$job->handle($articleFetcherMock, $publishingServiceMock);
|
$job->handle($articleFetcherMock, $publishingServiceMock, $this->notificationService);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_handle_logs_publishing_start(): void
|
public function test_handle_logs_publishing_start(): void
|
||||||
|
|
@ -233,12 +242,14 @@ public function test_handle_logs_publishing_start(): void
|
||||||
|
|
||||||
// Mock ArticlePublishingService
|
// Mock ArticlePublishingService
|
||||||
$publishingServiceMock = Mockery::mock(ArticlePublishingService::class);
|
$publishingServiceMock = Mockery::mock(ArticlePublishingService::class);
|
||||||
$publishingServiceMock->shouldReceive('publishToRoutedChannels')->once();
|
$publishingServiceMock->shouldReceive('publishToRoutedChannels')
|
||||||
|
->once()
|
||||||
|
->andReturn(new Collection(['publication']));
|
||||||
|
|
||||||
$job = new PublishNextArticleJob;
|
$job = new PublishNextArticleJob;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
$job->handle($articleFetcherMock, $publishingServiceMock);
|
$job->handle($articleFetcherMock, $publishingServiceMock, $this->notificationService);
|
||||||
|
|
||||||
// Assert - Verify the job completes (logging is verified by observing no exceptions)
|
// Assert - Verify the job completes (logging is verified by observing no exceptions)
|
||||||
$this->assertTrue(true);
|
$this->assertTrue(true);
|
||||||
|
|
@ -278,12 +289,13 @@ public function test_handle_fetches_article_data_before_publishing(): void
|
||||||
$publishingServiceMock = Mockery::mock(ArticlePublishingService::class);
|
$publishingServiceMock = Mockery::mock(ArticlePublishingService::class);
|
||||||
$publishingServiceMock->shouldReceive('publishToRoutedChannels')
|
$publishingServiceMock->shouldReceive('publishToRoutedChannels')
|
||||||
->once()
|
->once()
|
||||||
->with(Mockery::type(Article::class), $extractedData);
|
->with(Mockery::type(Article::class), $extractedData)
|
||||||
|
->andReturn(new Collection(['publication']));
|
||||||
|
|
||||||
$job = new PublishNextArticleJob;
|
$job = new PublishNextArticleJob;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
$job->handle($articleFetcherMock, $publishingServiceMock);
|
$job->handle($articleFetcherMock, $publishingServiceMock, $this->notificationService);
|
||||||
|
|
||||||
// Assert - Mockery expectations verified in tearDown
|
// Assert - Mockery expectations verified in tearDown
|
||||||
$this->assertTrue(true);
|
$this->assertTrue(true);
|
||||||
|
|
@ -311,7 +323,7 @@ public function test_handle_skips_publishing_when_last_publication_within_interv
|
||||||
$publishingServiceMock->shouldNotReceive('publishToRoutedChannels');
|
$publishingServiceMock->shouldNotReceive('publishToRoutedChannels');
|
||||||
|
|
||||||
$job = new PublishNextArticleJob;
|
$job = new PublishNextArticleJob;
|
||||||
$job->handle($articleFetcherMock, $publishingServiceMock);
|
$job->handle($articleFetcherMock, $publishingServiceMock, $this->notificationService);
|
||||||
|
|
||||||
$this->assertTrue(true);
|
$this->assertTrue(true);
|
||||||
}
|
}
|
||||||
|
|
@ -339,10 +351,11 @@ public function test_handle_publishes_when_last_publication_beyond_interval(): v
|
||||||
|
|
||||||
$publishingServiceMock = Mockery::mock(ArticlePublishingService::class);
|
$publishingServiceMock = Mockery::mock(ArticlePublishingService::class);
|
||||||
$publishingServiceMock->shouldReceive('publishToRoutedChannels')
|
$publishingServiceMock->shouldReceive('publishToRoutedChannels')
|
||||||
->once();
|
->once()
|
||||||
|
->andReturn(new Collection(['publication']));
|
||||||
|
|
||||||
$job = new PublishNextArticleJob;
|
$job = new PublishNextArticleJob;
|
||||||
$job->handle($articleFetcherMock, $publishingServiceMock);
|
$job->handle($articleFetcherMock, $publishingServiceMock, $this->notificationService);
|
||||||
|
|
||||||
$this->assertTrue(true);
|
$this->assertTrue(true);
|
||||||
}
|
}
|
||||||
|
|
@ -370,10 +383,11 @@ public function test_handle_publishes_when_interval_is_zero(): void
|
||||||
|
|
||||||
$publishingServiceMock = Mockery::mock(ArticlePublishingService::class);
|
$publishingServiceMock = Mockery::mock(ArticlePublishingService::class);
|
||||||
$publishingServiceMock->shouldReceive('publishToRoutedChannels')
|
$publishingServiceMock->shouldReceive('publishToRoutedChannels')
|
||||||
->once();
|
->once()
|
||||||
|
->andReturn(new Collection(['publication']));
|
||||||
|
|
||||||
$job = new PublishNextArticleJob;
|
$job = new PublishNextArticleJob;
|
||||||
$job->handle($articleFetcherMock, $publishingServiceMock);
|
$job->handle($articleFetcherMock, $publishingServiceMock, $this->notificationService);
|
||||||
|
|
||||||
$this->assertTrue(true);
|
$this->assertTrue(true);
|
||||||
}
|
}
|
||||||
|
|
@ -401,10 +415,11 @@ public function test_handle_publishes_when_last_publication_exactly_at_interval(
|
||||||
|
|
||||||
$publishingServiceMock = Mockery::mock(ArticlePublishingService::class);
|
$publishingServiceMock = Mockery::mock(ArticlePublishingService::class);
|
||||||
$publishingServiceMock->shouldReceive('publishToRoutedChannels')
|
$publishingServiceMock->shouldReceive('publishToRoutedChannels')
|
||||||
->once();
|
->once()
|
||||||
|
->andReturn(new Collection(['publication']));
|
||||||
|
|
||||||
$job = new PublishNextArticleJob;
|
$job = new PublishNextArticleJob;
|
||||||
$job->handle($articleFetcherMock, $publishingServiceMock);
|
$job->handle($articleFetcherMock, $publishingServiceMock, $this->notificationService);
|
||||||
|
|
||||||
$this->assertTrue(true);
|
$this->assertTrue(true);
|
||||||
}
|
}
|
||||||
|
|
@ -428,14 +443,91 @@ public function test_handle_publishes_when_no_previous_publications_exist(): voi
|
||||||
|
|
||||||
$publishingServiceMock = Mockery::mock(ArticlePublishingService::class);
|
$publishingServiceMock = Mockery::mock(ArticlePublishingService::class);
|
||||||
$publishingServiceMock->shouldReceive('publishToRoutedChannels')
|
$publishingServiceMock->shouldReceive('publishToRoutedChannels')
|
||||||
->once();
|
->once()
|
||||||
|
->andReturn(new Collection(['publication']));
|
||||||
|
|
||||||
$job = new PublishNextArticleJob;
|
$job = new PublishNextArticleJob;
|
||||||
$job->handle($articleFetcherMock, $publishingServiceMock);
|
$job->handle($articleFetcherMock, $publishingServiceMock, $this->notificationService);
|
||||||
|
|
||||||
$this->assertTrue(true);
|
$this->assertTrue(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_handle_creates_warning_notification_when_no_publications_created(): void
|
||||||
|
{
|
||||||
|
$feed = Feed::factory()->create();
|
||||||
|
$article = Article::factory()->create([
|
||||||
|
'feed_id' => $feed->id,
|
||||||
|
'approval_status' => 'approved',
|
||||||
|
'title' => 'No Route Article',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extractedData = ['title' => 'No Route Article'];
|
||||||
|
|
||||||
|
$articleFetcherMock = Mockery::mock(ArticleFetcher::class);
|
||||||
|
$articleFetcherMock->shouldReceive('fetchArticleData')
|
||||||
|
->once()
|
||||||
|
->andReturn($extractedData);
|
||||||
|
|
||||||
|
$publishingServiceMock = Mockery::mock(ArticlePublishingService::class);
|
||||||
|
$publishingServiceMock->shouldReceive('publishToRoutedChannels')
|
||||||
|
->once()
|
||||||
|
->andReturn(new Collection);
|
||||||
|
|
||||||
|
$job = new PublishNextArticleJob;
|
||||||
|
$job->handle($articleFetcherMock, $publishingServiceMock, $this->notificationService);
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('notifications', [
|
||||||
|
'type' => NotificationTypeEnum::PUBLISH_FAILED->value,
|
||||||
|
'severity' => NotificationSeverityEnum::WARNING->value,
|
||||||
|
'notifiable_type' => $article->getMorphClass(),
|
||||||
|
'notifiable_id' => $article->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$notification = Notification::first();
|
||||||
|
$this->assertStringContainsString('No Route Article', $notification->title);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_handle_creates_notification_on_publish_exception(): void
|
||||||
|
{
|
||||||
|
$feed = Feed::factory()->create();
|
||||||
|
$article = Article::factory()->create([
|
||||||
|
'feed_id' => $feed->id,
|
||||||
|
'approval_status' => 'approved',
|
||||||
|
'title' => 'Failing Article',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extractedData = ['title' => 'Failing Article'];
|
||||||
|
$publishException = new PublishException($article, null);
|
||||||
|
|
||||||
|
$articleFetcherMock = Mockery::mock(ArticleFetcher::class);
|
||||||
|
$articleFetcherMock->shouldReceive('fetchArticleData')
|
||||||
|
->once()
|
||||||
|
->andReturn($extractedData);
|
||||||
|
|
||||||
|
$publishingServiceMock = Mockery::mock(ArticlePublishingService::class);
|
||||||
|
$publishingServiceMock->shouldReceive('publishToRoutedChannels')
|
||||||
|
->once()
|
||||||
|
->andThrow($publishException);
|
||||||
|
|
||||||
|
$job = new PublishNextArticleJob;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$job->handle($articleFetcherMock, $publishingServiceMock, $this->notificationService);
|
||||||
|
} catch (PublishException) {
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('notifications', [
|
||||||
|
'type' => NotificationTypeEnum::PUBLISH_FAILED->value,
|
||||||
|
'severity' => NotificationSeverityEnum::ERROR->value,
|
||||||
|
'notifiable_type' => $article->getMorphClass(),
|
||||||
|
'notifiable_id' => $article->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$notification = Notification::first();
|
||||||
|
$this->assertStringContainsString('Failing Article', $notification->title);
|
||||||
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
{
|
{
|
||||||
Mockery::close();
|
Mockery::close();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue