113 lines
3.1 KiB
PHP
113 lines
3.1 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace Tests\Unit\Actions;
|
||
|
|
|
||
|
|
use App\Actions\FetchRssArticlesAction;
|
||
|
|
use App\Actions\SaveArticleAction;
|
||
|
|
use App\Models\Article;
|
||
|
|
use App\Models\Feed;
|
||
|
|
use App\Services\Log\LogSaver;
|
||
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||
|
|
use Illuminate\Support\Facades\Http;
|
||
|
|
use Tests\TestCase;
|
||
|
|
|
||
|
|
class FetchRssArticlesActionTest extends TestCase
|
||
|
|
{
|
||
|
|
use RefreshDatabase;
|
||
|
|
|
||
|
|
private function action(): FetchRssArticlesAction
|
||
|
|
{
|
||
|
|
$logSaver = app(LogSaver::class);
|
||
|
|
|
||
|
|
return new FetchRssArticlesAction($logSaver, new SaveArticleAction($logSaver));
|
||
|
|
}
|
||
|
|
|
||
|
|
private function rss(string ...$links): string
|
||
|
|
{
|
||
|
|
$items = '';
|
||
|
|
|
||
|
|
foreach ($links as $link) {
|
||
|
|
$items .= "<item><link>{$link}</link></item>";
|
||
|
|
}
|
||
|
|
|
||
|
|
return "<?xml version=\"1.0\"?><rss><channel>{$items}</channel></rss>";
|
||
|
|
}
|
||
|
|
|
||
|
|
private function feed(): Feed
|
||
|
|
{
|
||
|
|
return Feed::factory()->create([
|
||
|
|
'type' => 'rss',
|
||
|
|
'url' => 'https://example.com/feed.rss',
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_it_saves_an_article_per_rss_item(): void
|
||
|
|
{
|
||
|
|
$feed = $this->feed();
|
||
|
|
|
||
|
|
Http::fake([$feed->url => Http::response(
|
||
|
|
$this->rss('https://example.com/one', 'https://example.com/two'), 200
|
||
|
|
)]);
|
||
|
|
|
||
|
|
$result = $this->action()->execute($feed);
|
||
|
|
|
||
|
|
$this->assertCount(2, $result);
|
||
|
|
$this->assertDatabaseHas('articles', ['url' => 'https://example.com/one', 'feed_id' => $feed->id]);
|
||
|
|
$this->assertDatabaseHas('articles', ['url' => 'https://example.com/two', 'feed_id' => $feed->id]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_it_skips_items_with_an_empty_link(): void
|
||
|
|
{
|
||
|
|
$feed = $this->feed();
|
||
|
|
|
||
|
|
Http::fake([$feed->url => Http::response(
|
||
|
|
$this->rss('https://example.com/one', ''), 200
|
||
|
|
)]);
|
||
|
|
|
||
|
|
$result = $this->action()->execute($feed);
|
||
|
|
|
||
|
|
$this->assertCount(1, $result);
|
||
|
|
$this->assertSame(1, Article::count());
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_it_returns_empty_for_malformed_xml(): void
|
||
|
|
{
|
||
|
|
$feed = $this->feed();
|
||
|
|
|
||
|
|
Http::fake([$feed->url => Http::response('<not-xml', 200)]);
|
||
|
|
|
||
|
|
$this->assertEmpty($this->action()->execute($feed));
|
||
|
|
$this->assertSame(0, Article::count());
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_it_returns_empty_when_the_feed_has_no_items(): void
|
||
|
|
{
|
||
|
|
$feed = $this->feed();
|
||
|
|
|
||
|
|
Http::fake([$feed->url => Http::response('<?xml version="1.0"?><rss><channel></channel></rss>', 200)]);
|
||
|
|
|
||
|
|
$this->assertEmpty($this->action()->execute($feed));
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_it_returns_empty_when_the_fetch_throws(): void
|
||
|
|
{
|
||
|
|
$feed = $this->feed();
|
||
|
|
|
||
|
|
Http::fake(fn () => throw new \RuntimeException('connection refused'));
|
||
|
|
|
||
|
|
$this->assertEmpty($this->action()->execute($feed));
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_it_does_not_duplicate_articles_across_runs(): void
|
||
|
|
{
|
||
|
|
$feed = $this->feed();
|
||
|
|
|
||
|
|
Http::fake([$feed->url => Http::response($this->rss('https://example.com/one'), 200)]);
|
||
|
|
|
||
|
|
$this->action()->execute($feed);
|
||
|
|
$this->action()->execute($feed);
|
||
|
|
|
||
|
|
$this->assertSame(1, Article::count());
|
||
|
|
}
|
||
|
|
}
|