shouldReceive('upload')->andReturnUsing(fn (?string $url): ?string => $url); $uploader = $passthrough; } $reflection = new \ReflectionClass($publisher); $apiProperty = $reflection->getProperty('api'); $apiProperty->setAccessible(true); $apiProperty->setValue($publisher, $api); $uploaderProperty = $reflection->getProperty('thumbnailUploader'); $uploaderProperty->setAccessible(true); $uploaderProperty->setValue($publisher, $uploader); } public function test_constructor_initializes_api_service(): void { $account = PlatformAccount::factory()->make([ 'instance_url' => 'https://lemmy.world', ]); $publisher = new LemmyPublisher($account); $reflection = new \ReflectionClass($publisher); $apiProperty = $reflection->getProperty('api'); $apiProperty->setAccessible(true); $this->assertInstanceOf(LemmyApiService::class, $apiProperty->getValue($publisher)); $accountProperty = $reflection->getProperty('account'); $accountProperty->setAccessible(true); $this->assertSame($account, $accountProperty->getValue($publisher)); } public function test_publish_to_channel_with_all_data(): void { $account = PlatformAccount::factory()->make([ 'instance_url' => 'https://lemmy.world', ]); $article = Article::factory()->make([ 'url' => 'https://example.com/article', ]); $channel = PlatformChannel::factory()->make([ 'channel_id' => 42, ]); $extractedData = [ 'title' => 'Test Article', 'description' => 'Test Description', 'thumbnail' => 'https://example.com/thumb.jpg', 'language_id' => 5, ]; // Mock LemmyAuthService via service container $authMock = Mockery::mock(LemmyAuthService::class); $authMock->shouldReceive('getToken') ->once() ->with($account) ->andReturn('test-token'); $this->app->instance(LemmyAuthService::class, $authMock); // Mock LemmyApiService $apiMock = Mockery::mock(LemmyApiService::class); $apiMock->shouldReceive('createPost') ->once() ->with( 'test-token', 'Test Article', 'Test Description', 42, 'https://example.com/article', 'https://example.com/thumb.jpg', 5 ) ->andReturn(['post_view' => ['post' => ['id' => 999]]]); // Create publisher and inject mocked API using reflection $publisher = new LemmyPublisher($account); $this->injectMocks($publisher, $apiMock); $result = $publisher->publishToChannel($article, $extractedData, $channel); $this->assertEquals(['post_view' => ['post' => ['id' => 999]]], $result); } public function test_publish_to_channel_with_minimal_data(): void { $account = PlatformAccount::factory()->make([ 'instance_url' => 'https://lemmy.world', ]); $article = Article::factory()->make([ 'url' => 'https://example.com/article', ]); $channel = PlatformChannel::factory()->make([ 'channel_id' => 24, ]); $extractedData = []; // Mock LemmyAuthService $authMock = Mockery::mock(LemmyAuthService::class); $authMock->shouldReceive('getToken') ->once() ->with($account) ->andReturn('minimal-token'); $this->app->instance(LemmyAuthService::class, $authMock); // Mock LemmyApiService $apiMock = Mockery::mock(LemmyApiService::class); $apiMock->shouldReceive('createPost') ->once() ->with( 'minimal-token', 'Untitled', '', 24, 'https://example.com/article', null, null ) ->andReturn(['post_view' => ['post' => ['id' => 777]]]); // Create publisher and inject mocked API using reflection $publisher = new LemmyPublisher($account); $this->injectMocks($publisher, $apiMock); $result = $publisher->publishToChannel($article, $extractedData, $channel); $this->assertEquals(['post_view' => ['post' => ['id' => 777]]], $result); } public function test_publish_to_channel_without_thumbnail(): void { $account = PlatformAccount::factory()->make([ 'instance_url' => 'https://lemmy.world', ]); $article = Article::factory()->make([ 'url' => 'https://example.com/article', ]); $channel = PlatformChannel::factory()->make([ 'channel_id' => 33, ]); $extractedData = [ 'title' => 'No Thumbnail Article', 'description' => 'Article without thumbnail', 'language_id' => 2, ]; // Mock LemmyAuthService $authMock = Mockery::mock(LemmyAuthService::class); $this->app->instance(LemmyAuthService::class, $authMock); $authMock->shouldReceive('getToken') ->once() ->with($account) ->andReturn('no-thumb-token'); // Mock LemmyApiService $apiMock = Mockery::mock(LemmyApiService::class); $apiMock->shouldReceive('createPost') ->once() ->with( 'no-thumb-token', 'No Thumbnail Article', 'Article without thumbnail', 33, 'https://example.com/article', null, 2 ) ->andReturn(['post_view' => ['post' => ['id' => 555]]]); // Create publisher and inject mocked API using reflection $publisher = new LemmyPublisher($account); $this->injectMocks($publisher, $apiMock); $result = $publisher->publishToChannel($article, $extractedData, $channel); $this->assertEquals(['post_view' => ['post' => ['id' => 555]]], $result); } public function test_publish_to_channel_throws_platform_auth_exception(): void { $account = PlatformAccount::factory()->make([ 'instance_url' => 'https://lemmy.world', ]); $article = Article::factory()->make(); $channel = PlatformChannel::factory()->make(); $extractedData = []; // Mock LemmyAuthService to throw exception $authMock = Mockery::mock(LemmyAuthService::class); $this->app->instance(LemmyAuthService::class, $authMock); $authMock->shouldReceive('getToken') ->once() ->with($account) ->andThrow(new PlatformAuthException(PlatformEnum::LEMMY, 'Auth failed')); $publisher = new LemmyPublisher($account); $this->expectException(PlatformAuthException::class); $this->expectExceptionMessage('Auth failed'); $publisher->publishToChannel($article, $extractedData, $channel); } public function test_publish_to_channel_throws_api_exception(): void { $account = PlatformAccount::factory()->make([ 'instance_url' => 'https://lemmy.world', ]); $article = Article::factory()->make([ 'url' => 'https://example.com/article', ]); $channel = PlatformChannel::factory()->make([ 'channel_id' => 42, ]); $extractedData = [ 'title' => 'Test Article', ]; // Mock LemmyAuthService via service container $authMock = Mockery::mock(LemmyAuthService::class); $authMock->shouldReceive('getToken') ->once() ->with($account) ->andReturn('test-token'); $this->app->instance(LemmyAuthService::class, $authMock); // Mock LemmyApiService to throw exception $apiMock = Mockery::mock(LemmyApiService::class); $apiMock->shouldReceive('createPost') ->once() ->andThrow(new Exception('API Error')); // Create publisher and inject mocked API using reflection $publisher = new LemmyPublisher($account); $this->injectMocks($publisher, $apiMock); $this->expectException(Exception::class); $this->expectExceptionMessage('API Error'); $publisher->publishToChannel($article, $extractedData, $channel); } public function test_publish_to_channel_forwards_resolved_community_id_to_create_post(): void { $account = PlatformAccount::factory()->make([ 'instance_url' => 'https://lemmy.world', ]); $article = Article::factory()->make([ 'url' => 'https://example.com/article', ]); $channel = PlatformChannel::factory()->make([ 'channel_id' => 42, ]); $extractedData = [ 'title' => 'Test Title', ]; // Mock LemmyAuthService $authMock = Mockery::mock(LemmyAuthService::class); $this->app->instance(LemmyAuthService::class, $authMock); $authMock->shouldReceive('getToken') ->once() ->andReturn('token'); // Mock LemmyApiService - should resolve non-numeric channel_id to a community id $apiMock = Mockery::mock(LemmyApiService::class); $apiMock->shouldReceive('createPost') ->once() ->with( 'token', 'Test Title', '', 42, // resolved community ID 'https://example.com/article', null, null ) ->andReturn(['success' => true]); // Create publisher and inject mocked API using reflection $publisher = new LemmyPublisher($account); $this->injectMocks($publisher, $apiMock); $result = $publisher->publishToChannel($article, $extractedData, $channel); $this->assertEquals(['success' => true], $result); } public function test_it_passes_the_uploaded_thumbnail_url_to_create_post(): void { $account = PlatformAccount::factory()->make(['instance_url' => 'https://lemmy.world']); $article = Article::factory()->make(['url' => 'https://example.com/article']); $channel = PlatformChannel::factory()->make(['channel_id' => 7]); $authMock = Mockery::mock(LemmyAuthService::class); $authMock->shouldReceive('getToken')->andReturn('tok'); $this->app->instance(LemmyAuthService::class, $authMock); $apiMock = Mockery::mock(LemmyApiService::class); $apiMock->shouldReceive('createPost') ->once() ->with('tok', 'T', '', 7, 'https://example.com/article', 'https://lemmy.world/pictrs/image/x.jpg', null) ->andReturn(['ok' => true]); $uploader = Mockery::mock(ThumbnailUploader::class); $uploader->shouldReceive('upload') ->once() ->with('https://cdn.example.com/big.jpg', 'tok') ->andReturn('https://lemmy.world/pictrs/image/x.jpg'); $publisher = new LemmyPublisher($account); $this->injectMocks($publisher, $apiMock, $uploader); $this->assertSame( ['ok' => true], $publisher->publishToChannel($article, ['title' => 'T', 'thumbnail' => 'https://cdn.example.com/big.jpg'], $channel) ); } public function test_it_logs_a_warning_when_the_thumbnail_upload_fails(): void { $account = PlatformAccount::factory()->make(['instance_url' => 'https://lemmy.world']); $article = Article::factory()->create(); $channel = PlatformChannel::factory()->create(); $authMock = Mockery::mock(LemmyAuthService::class); $authMock->shouldReceive('getToken')->andReturn('tok'); $this->app->instance(LemmyAuthService::class, $authMock); $logMock = Mockery::mock(LogSaver::class); $logMock->shouldReceive('warning') ->once() ->withArgs(fn (string $message, ?PlatformChannel $c, array $context): bool => str_contains($message, 'Thumbnail upload failed') && $context['source'] === 'https://cdn.example.com/big.jpg'); $this->app->instance(LogSaver::class, $logMock); $apiMock = Mockery::mock(LemmyApiService::class); $apiMock->shouldReceive('createPost')->once()->andReturn(['ok' => true]); $uploader = Mockery::mock(ThumbnailUploader::class); $uploader->shouldReceive('upload')->once()->andReturn(null); $publisher = new LemmyPublisher($account); $this->injectMocks($publisher, $apiMock, $uploader); $this->assertSame( ['ok' => true], $publisher->publishToChannel($article, ['title' => 'T', 'thumbnail' => 'https://cdn.example.com/big.jpg'], $channel) ); } public function test_it_does_not_log_when_there_was_no_thumbnail_to_upload(): void { $account = PlatformAccount::factory()->make(['instance_url' => 'https://lemmy.world']); $article = Article::factory()->make(['url' => 'https://example.com/article']); $channel = PlatformChannel::factory()->make(['channel_id' => 7]); $authMock = Mockery::mock(LemmyAuthService::class); $authMock->shouldReceive('getToken')->andReturn('tok'); $this->app->instance(LemmyAuthService::class, $authMock); $logMock = Mockery::mock(LogSaver::class); $logMock->shouldNotReceive('warning'); $this->app->instance(LogSaver::class, $logMock); $apiMock = Mockery::mock(LemmyApiService::class); $apiMock->shouldReceive('createPost')->once()->andReturn(['ok' => true]); $uploader = Mockery::mock(ThumbnailUploader::class); $uploader->shouldReceive('upload')->once()->with(null, 'tok')->andReturn(null); $publisher = new LemmyPublisher($account); $this->injectMocks($publisher, $apiMock, $uploader); $this->assertSame(['ok' => true], $publisher->publishToChannel($article, ['title' => 'T'], $channel)); } }