2026-08-10 01:02:16 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace Tests\Feature\Jobs;
|
|
|
|
|
|
2026-08-10 20:46:07 +02:00
|
|
|
use App\Enums\ActivityTypeEnum;
|
2026-08-10 01:02:16 +02:00
|
|
|
use App\Enums\NotificationSeverityEnum;
|
|
|
|
|
use App\Enums\NotificationTypeEnum;
|
|
|
|
|
use App\Jobs\ArticleDiscoveryForFeedJob;
|
2026-08-10 20:46:07 +02:00
|
|
|
use App\Models\ActivityLog;
|
2026-08-10 01:02:16 +02:00
|
|
|
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;
|
|
|
|
|
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(ArticleFetcher::class);
|
|
|
|
|
$fetcher->shouldReceive('getArticlesFromFeed')
|
|
|
|
|
->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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-10 20:46:07 +02:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-10 01:02:16 +02:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|