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); } }