fedi-feed-router/tests/Feature/Jobs/ArticleDiscoveryForFeedJobEmptyFetchTest.php

172 lines
5.4 KiB
PHP

<?php
namespace Tests\Feature\Jobs;
use App\Actions\FetchFeedArticlesAction;
use App\Enums\ActivityTypeEnum;
use App\Enums\NotificationSeverityEnum;
use App\Enums\NotificationTypeEnum;
use App\Jobs\ArticleDiscoveryForFeedJob;
use App\Models\ActivityLog;
use App\Models\Article;
use App\Models\Feed;
use App\Models\Notification;
use App\Services\Log\LogSaver;
use App\Services\Notification\NotificationService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Collection;
use Mockery;
use Tests\TestCase;
class ArticleDiscoveryForFeedJobEmptyFetchTest extends TestCase
{
use RefreshDatabase;
/**
* @param Collection<int, Article>|null $articles
*/
private function runJobForFeed(Feed $feed, ?Collection $articles = null): void
{
$fetcher = Mockery::mock(FetchFeedArticlesAction::class);
$fetcher->shouldReceive('execute')
->andReturn($articles ?? collect());
(new ArticleDiscoveryForFeedJob($feed))->handle(
app(LogSaver::class),
$fetcher,
app(NotificationService::class)
);
}
public function test_empty_fetch_creates_notification(): void
{
$feed = Feed::factory()->create(['is_active' => true]);
$this->runJobForFeed($feed);
$this->assertDatabaseHas('notifications', [
'type' => NotificationTypeEnum::FEED_EMPTY->value,
'severity' => NotificationSeverityEnum::WARNING->value,
'notifiable_type' => $feed->getMorphClass(),
'notifiable_id' => $feed->id,
]);
}
public function test_fetch_with_articles_does_not_create_notification(): void
{
$feed = Feed::factory()->create(['is_active' => true]);
$articles = Article::factory()->count(2)->create(['feed_id' => $feed->id]);
$this->runJobForFeed($feed, $articles);
$this->assertDatabaseCount('notifications', 0);
}
public function test_notification_message_names_the_feed_url(): void
{
$feed = Feed::factory()->create([
'is_active' => true,
'name' => 'Belga',
'url' => 'https://example.test/retired-feed.xml',
]);
$this->runJobForFeed($feed);
$notification = Notification::first();
$this->assertStringContainsString('Belga', $notification->title);
$this->assertStringContainsString('https://example.test/retired-feed.xml', $notification->message);
}
public function test_empty_fetch_still_updates_last_fetched_at(): void
{
$feed = Feed::factory()->create(['is_active' => true, 'last_fetched_at' => null]);
$this->runJobForFeed($feed);
$this->assertNotNull($feed->fresh()->last_fetched_at);
}
public function test_does_not_create_duplicate_notification_when_unread_exists(): void
{
$feed = Feed::factory()->create(['is_active' => true]);
Notification::factory()
->type(NotificationTypeEnum::FEED_EMPTY)
->unread()
->create([
'notifiable_type' => $feed->getMorphClass(),
'notifiable_id' => $feed->id,
]);
$this->runJobForFeed($feed);
$this->assertDatabaseCount('notifications', 1);
}
public function test_creates_new_notification_when_previous_is_read(): void
{
$feed = Feed::factory()->create(['is_active' => true]);
Notification::factory()
->type(NotificationTypeEnum::FEED_EMPTY)
->read()
->create([
'notifiable_type' => $feed->getMorphClass(),
'notifiable_id' => $feed->id,
]);
$this->runJobForFeed($feed);
$this->assertDatabaseCount('notifications', 2);
}
public function test_empty_fetch_records_error_activity_not_fetch(): void
{
$feed = Feed::factory()->create(['is_active' => true, 'name' => 'Belga']);
$this->runJobForFeed($feed);
$entry = ActivityLog::first();
$this->assertSame(ActivityTypeEnum::ERROR, $entry->type);
$this->assertStringContainsString('Belga', $entry->message);
$this->assertSame(0, ActivityLog::ofType(ActivityTypeEnum::FETCH)->count());
}
public function test_fetch_with_articles_records_fetch_activity(): void
{
$feed = Feed::factory()->create(['is_active' => true, 'name' => 'VRT']);
$articles = Article::factory()->count(2)->create(['feed_id' => $feed->id]);
$this->runJobForFeed($feed, $articles);
$entry = ActivityLog::first();
$this->assertSame(ActivityTypeEnum::FETCH, $entry->type);
$this->assertStringContainsString('2 articles', $entry->message);
$this->assertSame($feed->getMorphClass(), $entry->subject_type);
}
public function test_notification_for_one_feed_does_not_suppress_another(): void
{
$notified = Feed::factory()->create(['is_active' => true]);
$other = Feed::factory()->create(['is_active' => true]);
Notification::factory()
->type(NotificationTypeEnum::FEED_EMPTY)
->unread()
->create([
'notifiable_type' => $notified->getMorphClass(),
'notifiable_id' => $notified->id,
]);
$this->runJobForFeed($other);
$this->assertDatabaseHas('notifications', [
'type' => NotificationTypeEnum::FEED_EMPTY->value,
'notifiable_id' => $other->id,
]);
$this->assertDatabaseCount('notifications', 2);
}
}