create(); $instance = PlatformInstance::factory()->create(['url' => 'https://lemmy.test']); $channel = PlatformChannel::factory()->create([ 'platform_instance_id' => $instance->id, 'channel_id' => 'news', 'name' => 'news', ]); $account = PlatformAccount::factory()->create(['instance_url' => 'https://lemmy.test']); /** @var Route $route */ $route = Route::factory()->active()->create([ 'feed_id' => $feed->id, 'platform_channel_id' => $channel->id, ]); $channel->platformAccounts()->attach($account->id, ['is_active' => true, 'priority' => 50]); $article = Article::factory()->create([ 'feed_id' => $feed->id, 'url' => 'https://news.test/already-posted', 'title' => 'Already Posted', ]); /** @var RouteArticle $routeArticle */ $routeArticle = RouteArticle::factory()->forRoute($route)->approved()->create([ 'article_id' => $article->id, ]); $this->fixture = [$routeArticle, $channel, $article]; } protected function tearDown(): void { Mockery::close(); parent::tearDown(); } private function service(?LemmyPublisher $publisher = null): ArticlePublishingService { $service = Mockery::mock(ArticlePublishingService::class, [app(LogSaver::class)])->makePartial(); $service->shouldAllowMockingProtectedMethods(); $service->shouldReceive('makePublisher')->andReturn( $publisher ?? Mockery::mock(LemmyPublisher::class) ); return $service; } public function test_sync_writes_the_key_the_duplicate_check_reads(): void { [$routeArticle, $channel, $article] = $this->fixture; // Populate the mirror through the real sync path rather than seeding a // row by hand, so write and read cannot silently disagree. Http::fake([ '*/api/v3/post/list*' => Http::response([ 'posts' => [[ 'post' => [ 'id' => 555, 'url' => $article->url, 'name' => 'Already Posted', 'published' => '2026-08-02T10:00:00Z', ], ]], ]), ]); (new LemmyApiService('https://lemmy.test')) ->syncChannelPosts('token', $channel, 8); $this->assertDatabaseCount('platform_channel_posts', 1); $publisher = Mockery::mock(LemmyPublisher::class); $publisher->shouldNotReceive('publishToChannel'); $result = $this->service($publisher)->publishRouteArticle($routeArticle, ['title' => 'Already Posted']); $this->assertTrue($result->wasSkipped()); $this->assertDatabaseCount('article_publications', 0); } public function test_a_skipped_duplicate_is_not_reported_as_a_publish_failure(): void { [$routeArticle, $channel, $article] = $this->fixture; PlatformChannelPost::storePost($channel, '555', $article->url, 'Already Posted'); $fetcher = Mockery::mock(FetchArticleDataAction::class); $fetcher->shouldNotReceive('execute'); $publisher = Mockery::mock(LemmyPublisher::class); $publisher->shouldNotReceive('publishToChannel'); (new PublishApprovedArticleListener(new PublishRouteArticleAction($fetcher, $this->service($publisher), new NotificationService))) ->handle(new RouteArticleApproved($routeArticle)); $this->assertSame(PublishStatusEnum::SKIPPED, $routeArticle->fresh()->publish_status); $this->assertDatabaseCount('notifications', 0); } public function test_a_genuine_failure_is_still_reported(): void { [$routeArticle] = $this->fixture; $fetcher = Mockery::mock(FetchArticleDataAction::class); $fetcher->shouldNotReceive('execute'); $publisher = Mockery::mock(LemmyPublisher::class); $publisher->shouldReceive('publishToChannel')->andThrow(new \RuntimeException('Lemmy rejected the post')); (new PublishApprovedArticleListener(new PublishRouteArticleAction($fetcher, $this->service($publisher), new NotificationService))) ->handle(new RouteArticleApproved($routeArticle)); $this->assertSame(PublishStatusEnum::ERROR, $routeArticle->fresh()->publish_status); $this->assertDatabaseHas('notifications', [ 'type' => NotificationTypeEnum::PUBLISH_FAILED->value, ]); } public function test_mirror_is_keyed_by_the_local_channel(): void { [, $channel, $article] = $this->fixture; PlatformChannelPost::storePost( $channel, '555', $article->url, 'Already Posted', ); $this->assertDatabaseHas('platform_channel_posts', [ 'platform_channel_id' => $channel->id, 'post_id' => '555', ]); } public function test_a_second_instance_with_the_same_community_name_does_not_collide(): void { [, $channel, $article] = $this->fixture; $otherInstance = PlatformInstance::factory()->create(['url' => 'https://other.test']); $otherChannel = PlatformChannel::factory()->create([ 'platform_instance_id' => $otherInstance->id, 'channel_id' => 'news', 'name' => 'news', ]); PlatformChannelPost::storePost($channel, '1', $article->url, 'Same Title'); // Same community name on a different instance is a different community. $this->assertFalse( PlatformChannelPost::duplicateExists($otherChannel, $article->url, 'Same Title') ); $this->assertTrue( PlatformChannelPost::duplicateExists($channel, $article->url, 'Same Title') ); } }