fedi-feed-router/tests/Unit/Modules/Lemmy/Services/LemmyApiServiceTest.php
myrmidex 6784af2ff6
Some checks failed
CI / ci (push) Failing after 4m31s
25 - Fix all PHPStan errors and add mockery extension
2026-03-08 14:18:28 +01:00

408 lines
12 KiB
PHP

<?php
namespace Tests\Unit\Modules\Lemmy\Services;
use App\Enums\PlatformEnum;
use App\Modules\Lemmy\Services\LemmyApiService;
use Exception;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class LemmyApiServiceTest extends TestCase
{
use RefreshDatabase;
public function test_constructor_sets_instance(): void
{
$service = new LemmyApiService('lemmy.world');
$reflection = new \ReflectionClass($service);
$property = $reflection->getProperty('instance');
$property->setAccessible(true);
$this->assertEquals('lemmy.world', $property->getValue($service));
}
public function test_login_with_https_success(): void
{
Http::fake([
'https://lemmy.world/api/v3/user/login' => Http::response(['jwt' => 'test-token'], 200),
]);
$service = new LemmyApiService('lemmy.world');
$token = $service->login('user', 'pass');
$this->assertEquals('test-token', $token);
Http::assertSent(function ($request) {
return $request->url() === 'https://lemmy.world/api/v3/user/login'
&& $request['username_or_email'] === 'user'
&& $request['password'] === 'pass';
});
}
public function test_login_falls_back_to_http_on_https_failure(): void
{
Http::fake([
'https://lemmy.world/api/v3/user/login' => Http::response('', 500),
'http://lemmy.world/api/v3/user/login' => Http::response(['jwt' => 'http-token'], 200),
]);
$service = new LemmyApiService('lemmy.world');
$token = $service->login('user', 'pass');
$this->assertEquals('http-token', $token);
Http::assertSentCount(2);
}
public function test_login_with_explicit_http_scheme(): void
{
Http::fake([
'http://localhost/api/v3/user/login' => Http::response(['jwt' => 'local-token'], 200),
]);
$service = new LemmyApiService('http://localhost');
$token = $service->login('user', 'pass');
$this->assertEquals('local-token', $token);
Http::assertSent(function ($request) {
return $request->url() === 'http://localhost/api/v3/user/login';
});
}
public function test_login_with_explicit_https_scheme(): void
{
Http::fake([
'https://secure.lemmy/api/v3/user/login' => Http::response(['jwt' => 'secure-token'], 200),
]);
$service = new LemmyApiService('https://secure.lemmy');
$token = $service->login('user', 'pass');
$this->assertEquals('secure-token', $token);
Http::assertSent(function ($request) {
return $request->url() === 'https://secure.lemmy/api/v3/user/login';
});
}
public function test_login_returns_null_on_unsuccessful_response(): void
{
Http::fake([
'*' => Http::response(['error' => 'Invalid credentials'], 401),
]);
$service = new LemmyApiService('lemmy.world');
$token = $service->login('user', 'wrong');
$this->assertNull($token);
}
public function test_login_handles_rate_limit_error(): void
{
Http::fake([
'*' => Http::response('{"error":"rate_limit_error"}', 429),
]);
$service = new LemmyApiService('lemmy.world');
$this->expectException(Exception::class);
$this->expectExceptionMessage('Rate limited');
$service->login('user', 'pass');
}
public function test_login_returns_null_when_jwt_missing_from_response(): void
{
Http::fake([
'*' => Http::response(['success' => true], 200),
]);
$service = new LemmyApiService('lemmy.world');
$token = $service->login('user', 'pass');
$this->assertNull($token);
}
public function test_login_handles_exception_and_throws(): void
{
Http::fake(function () {
throw new Exception('Network error');
});
$service = new LemmyApiService('lemmy.world');
$this->expectException(Exception::class);
$this->expectExceptionMessage('Connection failed');
$service->login('user', 'pass');
}
public function test_get_community_id_success(): void
{
Http::fake([
'*' => Http::response([
'community_view' => [
'community' => ['id' => 123],
],
], 200),
]);
$service = new LemmyApiService('lemmy.world');
$id = $service->getCommunityId('test-community', 'token');
$this->assertEquals(123, $id);
Http::assertSent(function ($request) {
return str_contains($request->url(), '/api/v3/community')
&& str_contains($request->url(), 'name=test-community')
&& $request->header('Authorization')[0] === 'Bearer token';
});
}
public function test_get_community_id_throws_on_unsuccessful_response(): void
{
Http::fake([
'*' => Http::response('Not found', 404),
]);
$service = new LemmyApiService('lemmy.world');
$this->expectException(Exception::class);
$this->expectExceptionMessage('Failed to fetch community: 404');
$service->getCommunityId('missing', 'token');
}
public function test_get_community_id_throws_when_community_not_in_response(): void
{
Http::fake([
'*' => Http::response(['success' => true], 200),
]);
$service = new LemmyApiService('lemmy.world');
$this->expectException(Exception::class);
$this->expectExceptionMessage('Community not found');
$service->getCommunityId('test', 'token');
}
public function test_sync_channel_posts_success(): void
{
Http::fake([
'*' => Http::response([
'posts' => [
[
'post' => [
'id' => 1,
'url' => 'https://example.com/1',
'name' => 'Post 1',
'published' => '2024-01-01T00:00:00Z',
],
],
[
'post' => [
'id' => 2,
'url' => 'https://example.com/2',
'name' => 'Post 2',
'published' => '2024-01-02T00:00:00Z',
],
],
],
], 200),
]);
$service = new LemmyApiService('lemmy.world');
$service->syncChannelPosts('token', 42, 'test-community');
Http::assertSent(function ($request) {
return str_contains($request->url(), '/api/v3/post/list')
&& str_contains($request->url(), 'community_id=42')
&& str_contains($request->url(), 'limit=50')
&& str_contains($request->url(), 'sort=New');
});
// Verify posts were stored in the database
$this->assertDatabaseHas('platform_channel_posts', [
'platform' => PlatformEnum::LEMMY->value,
'channel_id' => '42',
'channel_name' => 'test-community',
'post_id' => '1',
'url' => 'https://example.com/1',
'title' => 'Post 1',
]);
$this->assertDatabaseHas('platform_channel_posts', [
'platform' => PlatformEnum::LEMMY->value,
'channel_id' => '42',
'channel_name' => 'test-community',
'post_id' => '2',
'url' => 'https://example.com/2',
'title' => 'Post 2',
]);
}
public function test_sync_channel_posts_handles_unsuccessful_response(): void
{
Http::fake([
'*' => Http::response('Error', 500),
]);
$service = new LemmyApiService('lemmy.world');
$service->syncChannelPosts('token', 42, 'test-community');
Http::assertSentCount(1);
$this->assertDatabaseCount('platform_channel_posts', 0);
}
public function test_sync_channel_posts_handles_exception(): void
{
Http::fake(function () {
throw new Exception('Network error');
});
$service = new LemmyApiService('lemmy.world');
$service->syncChannelPosts('token', 42, 'test-community');
// Assert that the method completes without throwing
$this->assertTrue(true);
}
public function test_create_post_with_all_parameters(): void
{
Http::fake([
'*' => Http::response(['post_view' => ['post' => ['id' => 999]]], 200),
]);
$service = new LemmyApiService('lemmy.world');
$result = $service->createPost(
'token',
'Test Title',
'Test Body',
42,
'https://example.com',
'https://example.com/thumb.jpg',
5
);
$this->assertEquals(['post_view' => ['post' => ['id' => 999]]], $result);
Http::assertSent(function ($request) {
$data = $request->data();
return $request->url() === 'https://lemmy.world/api/v3/post'
&& $data['name'] === 'Test Title'
&& $data['body'] === 'Test Body'
&& $data['community_id'] === 42
&& $data['url'] === 'https://example.com'
&& $data['custom_thumbnail'] === 'https://example.com/thumb.jpg'
&& $data['language_id'] === 5;
});
}
public function test_create_post_with_minimal_parameters(): void
{
Http::fake([
'*' => Http::response(['post_view' => ['post' => ['id' => 888]]], 200),
]);
$service = new LemmyApiService('lemmy.world');
$result = $service->createPost(
'token',
'Title Only',
'Body Only',
42
);
$this->assertEquals(['post_view' => ['post' => ['id' => 888]]], $result);
Http::assertSent(function ($request) {
$data = $request->data();
return $request->url() === 'https://lemmy.world/api/v3/post'
&& $data['name'] === 'Title Only'
&& $data['body'] === 'Body Only'
&& $data['community_id'] === 42
&& ! isset($data['url'])
&& ! isset($data['custom_thumbnail'])
&& ! isset($data['language_id']);
});
}
public function test_create_post_throws_on_unsuccessful_response(): void
{
Http::fake([
'*' => Http::response('Forbidden', 403),
]);
$service = new LemmyApiService('lemmy.world');
$this->expectException(Exception::class);
$this->expectExceptionMessage('Failed to create post: 403');
$service->createPost('token', 'Title', 'Body', 42);
}
public function test_get_languages_success(): void
{
Http::fake([
'*' => Http::response([
'all_languages' => [
['id' => 1, 'code' => 'en', 'name' => 'English'],
['id' => 2, 'code' => 'fr', 'name' => 'French'],
],
], 200),
]);
$service = new LemmyApiService('lemmy.world');
$languages = $service->getLanguages();
$this->assertCount(2, $languages);
$this->assertEquals('en', $languages[0]['code']);
$this->assertEquals('fr', $languages[1]['code']);
Http::assertSent(function ($request) {
return str_contains($request->url(), '/api/v3/site');
});
}
public function test_get_languages_returns_empty_array_on_failure(): void
{
Http::fake([
'*' => Http::response('Error', 500),
]);
$service = new LemmyApiService('lemmy.world');
$languages = $service->getLanguages();
$this->assertEquals([], $languages);
}
public function test_get_languages_handles_exception(): void
{
Http::fake(function () {
throw new Exception('Network error');
});
$service = new LemmyApiService('lemmy.world');
$languages = $service->getLanguages();
$this->assertEquals([], $languages);
}
public function test_get_languages_returns_empty_when_all_languages_missing(): void
{
Http::fake([
'*' => Http::response(['site_view' => []], 200),
]);
$service = new LemmyApiService('lemmy.world');
$languages = $service->getLanguages();
$this->assertEquals([], $languages);
}
}