191 lines
6.6 KiB
PHP
191 lines
6.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Lvl0\FediDiscover\Tests\Feature;
|
|
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Lvl0\FediDiscover\Clients\MastodonClient;
|
|
use Lvl0\FediDiscover\Config\InstanceType;
|
|
use Lvl0\FediDiscover\Models\Instance;
|
|
use Lvl0\FediDiscover\ValueObjects\FediversePost;
|
|
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_maps_published_at_from_created_at(): void
|
|
{
|
|
Http::fake([
|
|
'*' => Http::response([
|
|
$this->mastodonStatus(id: '109876543210', url: 'https://mastodon.social/@alice/109876543210'),
|
|
], 200),
|
|
]);
|
|
|
|
$posts = (new MastodonClient)->fetchPostsSince($this->mastodonInstance(), null);
|
|
|
|
$this->assertSame('2026-04-25T10:00:00Z', $posts->first()->publishedAt);
|
|
}
|
|
|
|
public function test_it_sets_title_to_null_for_mastodon_statuses(): void
|
|
{
|
|
Http::fake([
|
|
'*' => Http::response([
|
|
$this->mastodonStatus(id: '109876543210', url: 'https://mastodon.social/@alice/109876543210'),
|
|
], 200),
|
|
]);
|
|
|
|
$posts = (new MastodonClient)->fetchPostsSince($this->mastodonInstance(), null);
|
|
|
|
$this->assertNull($posts->first()->title);
|
|
}
|
|
|
|
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'],
|
|
];
|
|
}
|
|
}
|