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) ]); Log::shouldReceive('error')->twice(); // Once for HTTPS, once for HTTP fallback $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) ]); // Expecting 4 error logs: // 1. 'Lemmy login failed' for HTTPS attempt // 2. 'Lemmy login exception' for catching the rate limit exception on HTTPS // 3. 'Lemmy login failed' for HTTP attempt // 4. 'Lemmy login exception' for catching the rate limit exception on HTTP Log::shouldReceive('error')->times(4); $service = new LemmyApiService('lemmy.world'); $result = $service->login('user', 'pass'); // Since the exception is caught and HTTP is tried, then that also fails, // the method returns null instead of throwing $this->assertNull($result); } 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_returns_null(): void { Http::fake(function () { throw new Exception('Network error'); }); Log::shouldReceive('error')->twice(); $service = new LemmyApiService('lemmy.world'); $token = $service->login('user', 'pass'); $this->assertNull($token); } 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) ]); Log::shouldReceive('error')->once(); $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) ]); Log::shouldReceive('error')->once(); $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) ]); Log::shouldReceive('info')->once()->with('Synced channel posts', Mockery::any()); $mockPost = Mockery::mock('alias:' . PlatformChannelPost::class); $mockPost->shouldReceive('storePost') ->twice() ->with( PlatformEnum::LEMMY, Mockery::any(), 'test-community', Mockery::any(), Mockery::any(), Mockery::any(), Mockery::any() ); $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'); }); } public function test_sync_channel_posts_handles_unsuccessful_response(): void { Http::fake([ '*' => Http::response('Error', 500) ]); Log::shouldReceive('warning')->once()->with('Failed to sync channel posts', Mockery::any()); $service = new LemmyApiService('lemmy.world'); $service->syncChannelPosts('token', 42, 'test-community'); Http::assertSentCount(1); } public function test_sync_channel_posts_handles_exception(): void { Http::fake(function () { throw new Exception('Network error'); }); Log::shouldReceive('error')->once()->with('Exception while syncing channel posts', Mockery::any()); $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) ]); Log::shouldReceive('error')->once(); $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) ]); Log::shouldReceive('warning')->once(); $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'); }); Log::shouldReceive('error')->once(); $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); } public function test_list_communities_success_with_default_parameters(): void { Http::fake([ '*' => Http::response([ 'communities' => [ [ 'community' => [ 'id' => 1, 'name' => 'technology', 'title' => 'Technology Community', 'description' => 'All about tech', 'nsfw' => false ] ], [ 'community' => [ 'id' => 2, 'name' => 'news', 'title' => 'News Community', 'description' => 'Latest news', 'nsfw' => false ] ] ] ], 200) ]); $service = new LemmyApiService('lemmy.world'); $result = $service->listCommunities(); $this->assertArrayHasKey('communities', $result); $this->assertCount(2, $result['communities']); $this->assertEquals('technology', $result['communities'][0]['community']['name']); $this->assertEquals('news', $result['communities'][1]['community']['name']); Http::assertSent(function ($request) { return str_contains($request->url(), '/api/v3/community/list') && str_contains($request->url(), 'type_=Local') && str_contains($request->url(), 'sort=Active') && str_contains($request->url(), 'limit=50') && str_contains($request->url(), 'page=1') && str_contains($request->url(), 'show_nsfw='); }); } public function test_list_communities_success_with_custom_parameters(): void { Http::fake([ '*' => Http::response([ 'communities' => [ [ 'community' => [ 'id' => 3, 'name' => 'gaming', 'title' => 'Gaming Community', 'description' => 'Gaming discussions', 'nsfw' => false ] ] ] ], 200) ]); $service = new LemmyApiService('lemmy.world'); $result = $service->listCommunities( 'test-token', 'All', 'TopMonth', 25, 2, true ); $this->assertArrayHasKey('communities', $result); $this->assertCount(1, $result['communities']); $this->assertEquals('gaming', $result['communities'][0]['community']['name']); Http::assertSent(function ($request) { return str_contains($request->url(), '/api/v3/community/list') && str_contains($request->url(), 'type_=All') && str_contains($request->url(), 'sort=TopMonth') && str_contains($request->url(), 'limit=25') && str_contains($request->url(), 'page=2') && str_contains($request->url(), 'show_nsfw=1') && $request->header('Authorization')[0] === 'Bearer test-token'; }); } public function test_list_communities_success_without_token(): void { Http::fake([ '*' => Http::response([ 'communities' => [] ], 200) ]); $service = new LemmyApiService('lemmy.world'); $result = $service->listCommunities(); $this->assertArrayHasKey('communities', $result); $this->assertEmpty($result['communities']); Http::assertSent(function ($request) { return !$request->hasHeader('Authorization'); }); } public function test_list_communities_throws_on_unsuccessful_response(): void { Http::fake([ '*' => Http::response('Server Error', 500) ]); Log::shouldReceive('warning')->once()->with('Failed to fetch communities list', Mockery::any()); Log::shouldReceive('error')->once()->with('Exception while fetching communities list', Mockery::any()); $service = new LemmyApiService('lemmy.world'); $this->expectException(Exception::class); $this->expectExceptionMessage('Failed to fetch communities list: 500 - Server Error'); $service->listCommunities('token'); } public function test_list_communities_throws_on_404_response(): void { Http::fake([ '*' => Http::response('Not Found', 404) ]); Log::shouldReceive('warning')->once(); Log::shouldReceive('error')->once(); $service = new LemmyApiService('lemmy.world'); $this->expectException(Exception::class); $this->expectExceptionMessage('Failed to fetch communities list: 404'); $service->listCommunities('token'); } public function test_list_communities_returns_empty_communities_on_invalid_response_format(): void { Http::fake([ '*' => Http::response([ 'invalid_format' => 'data' ], 200) ]); Log::shouldReceive('warning')->once()->with('Invalid communities list response format', Mockery::any()); $service = new LemmyApiService('lemmy.world'); $result = $service->listCommunities(); $this->assertArrayHasKey('communities', $result); $this->assertEmpty($result['communities']); } public function test_list_communities_handles_exception(): void { Http::fake(function () { throw new Exception('Network error'); }); Log::shouldReceive('error')->once()->with('Exception while fetching communities list', Mockery::any()); $service = new LemmyApiService('lemmy.world'); $this->expectException(Exception::class); $this->expectExceptionMessage('Network error'); $service->listCommunities('token'); } public function test_list_communities_with_all_sort_options(): void { $sortOptions = ['Hot', 'Active', 'New', 'TopDay', 'TopWeek', 'TopMonth', 'TopYear', 'TopAll']; foreach ($sortOptions as $sort) { Http::fake([ '*' => Http::response(['communities' => []], 200) ]); $service = new LemmyApiService('lemmy.world'); $result = $service->listCommunities(null, 'Local', $sort); $this->assertArrayHasKey('communities', $result); Http::assertSent(function ($request) use ($sort) { return str_contains($request->url(), "sort={$sort}"); }); } } public function test_list_communities_with_all_type_options(): void { $typeOptions = ['Local', 'All', 'Subscribed']; foreach ($typeOptions as $type) { Http::fake([ '*' => Http::response(['communities' => []], 200) ]); $service = new LemmyApiService('lemmy.world'); $result = $service->listCommunities(null, $type); $this->assertArrayHasKey('communities', $result); Http::assertSent(function ($request) use ($type) { return str_contains($request->url(), "type_={$type}"); }); } } public function test_list_communities_pagination_parameters(): void { Http::fake([ '*' => Http::response(['communities' => []], 200) ]); $service = new LemmyApiService('lemmy.world'); $result = $service->listCommunities(null, 'Local', 'Active', 100, 3); $this->assertArrayHasKey('communities', $result); Http::assertSent(function ($request) { return str_contains($request->url(), 'limit=100') && str_contains($request->url(), 'page=3'); }); } public function test_list_communities_nsfw_parameter(): void { Http::fake([ '*' => Http::response(['communities' => []], 200) ]); $service = new LemmyApiService('lemmy.world'); // Test with NSFW enabled $result = $service->listCommunities(null, 'Local', 'Active', 50, 1, true); $this->assertArrayHasKey('communities', $result); Http::assertSent(function ($request) { return str_contains($request->url(), 'show_nsfw=1'); }); // Test with NSFW disabled $result = $service->listCommunities(null, 'Local', 'Active', 50, 1, false); $this->assertArrayHasKey('communities', $result); Http::assertSent(function ($request) { return str_contains($request->url(), 'show_nsfw='); }); } }