trove/packages/Lvl0/FediDiscover/tests/Feature/MastodonClientTest.php

166 lines
5.7 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
namespace Lvl0\FediDiscover\Tests\Feature;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;
use Lvl0\FediDiscover\Clients\FediversePost;
use Lvl0\FediDiscover\Clients\MastodonClient;
use Lvl0\FediDiscover\Config\InstanceType;
use Lvl0\FediDiscover\Models\Instance;
use Tests\TestCase;
class MastodonClientTest extends TestCase
{
public function test_it_hits_the_public_timeline_endpoint_of_the_instance(): void
{
Http::fake([
'mastodon.social/api/v1/timelines/public*' => Http::response([], 200),
]);
(new MastodonClient)->fetchPostsSince($this->mastodonInstance(), null);
Http::assertSent(fn ($request) => str_starts_with($request->url(), 'https://mastodon.social/api/v1/timelines/public')
&& $request->method() === 'GET'
);
}
public function test_it_omits_min_id_on_first_poll(): void
{
Http::fake(['*' => Http::response([], 200)]);
(new MastodonClient)->fetchPostsSince($this->mastodonInstance(), null);
Http::assertSent(fn ($request) => ! str_contains($request->url(), 'min_id'));
}
public function test_it_passes_min_id_on_subsequent_polls(): void
{
Http::fake(['*' => Http::response([], 200)]);
(new MastodonClient)->fetchPostsSince($this->mastodonInstance(), '109876543210');
Http::assertSent(fn ($request) => str_contains($request->url(), 'min_id=109876543210'));
}
public function test_it_returns_an_empty_collection_when_the_api_returns_no_posts(): void
{
Http::fake(['*' => Http::response([], 200)]);
$posts = (new MastodonClient)->fetchPostsSince($this->mastodonInstance(), null);
$this->assertInstanceOf(Collection::class, $posts);
$this->assertTrue($posts->isEmpty());
}
public function test_it_maps_each_status_to_a_fediverse_post(): void
{
Http::fake([
'*' => Http::response([
$this->mastodonStatus(id: '109876543210', url: 'https://mastodon.social/@alice/109876543210', content: '<p>Hello</p>'),
$this->mastodonStatus(id: '109876543211', url: 'https://mastodon.social/@bob/109876543211', content: '<p>World</p>'),
], 200),
]);
$posts = (new MastodonClient)->fetchPostsSince($this->mastodonInstance(), null);
$this->assertCount(2, $posts);
$this->assertInstanceOf(FediversePost::class, $posts->first());
$this->assertSame('109876543210', $posts->first()->cursorId);
$this->assertSame('https://mastodon.social/@alice/109876543210', $posts->first()->selfUrl);
$this->assertSame('<p>Hello</p>', $posts->first()->body);
}
public function test_it_falls_back_to_uri_when_url_is_null(): void
{
Http::fake([
'*' => Http::response([
$this->mastodonStatus(
id: '109876543210',
url: null,
uri: 'https://hachyderm.io/users/bob/statuses/5678',
content: '<p>federated post</p>'
),
], 200),
]);
$posts = (new MastodonClient)->fetchPostsSince($this->mastodonInstance(), null);
$this->assertSame('https://hachyderm.io/users/bob/statuses/5678', $posts->first()->selfUrl);
}
public function test_it_preserves_newest_first_ordering_from_the_api(): void
{
Http::fake([
'*' => Http::response([
$this->mastodonStatus(id: '300', url: 'https://mastodon.social/@a/300'),
$this->mastodonStatus(id: '200', url: 'https://mastodon.social/@b/200'),
$this->mastodonStatus(id: '100', url: 'https://mastodon.social/@c/100'),
], 200),
]);
$posts = (new MastodonClient)->fetchPostsSince($this->mastodonInstance(), null);
$this->assertSame(['300', '200', '100'], $posts->pluck('cursorId')->all());
}
public function test_it_returns_an_empty_collection_on_a_non_2xx_response(): void
{
Http::fake(['*' => Http::response('Too many requests', 429)]);
$posts = (new MastodonClient)->fetchPostsSince($this->mastodonInstance(), null);
$this->assertInstanceOf(Collection::class, $posts);
$this->assertTrue($posts->isEmpty());
}
public function test_it_returns_an_empty_collection_when_the_response_body_is_not_json(): void
{
Http::fake(['*' => Http::response('<html>error</html>', 200)]);
$posts = (new MastodonClient)->fetchPostsSince($this->mastodonInstance(), null);
$this->assertInstanceOf(Collection::class, $posts);
$this->assertTrue($posts->isEmpty());
}
public function test_it_sends_the_configured_user_agent(): void
{
Http::fake(['*' => Http::response([], 200)]);
(new MastodonClient)->fetchPostsSince($this->mastodonInstance(), null);
$expected = config('fedi-discover.http.user_agent');
Http::assertSent(fn ($request) => $request->header('User-Agent')[0] === $expected);
}
private function mastodonInstance(): Instance
{
return new Instance([
'type' => InstanceType::Mastodon,
'url' => 'https://mastodon.social',
]);
}
/**
* @return array<string, mixed>
*/
private function mastodonStatus(
string $id,
?string $url = null,
?string $uri = null,
string $content = '<p>example</p>',
): array {
return [
'id' => $id,
'url' => $url,
'uri' => $uri ?? "https://mastodon.social/users/x/statuses/{$id}",
'content' => $content,
'created_at' => '2026-04-25T10:00:00Z',
'account' => ['acct' => 'alice@mastodon.social'],
];
}
}