2025-08-06 21:49:13 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace Tests\Unit\Services\Publishing;
|
|
|
|
|
|
|
|
|
|
use App\Enums\PlatformEnum;
|
|
|
|
|
use App\Models\Article;
|
|
|
|
|
use App\Models\Feed;
|
|
|
|
|
use App\Models\PlatformAccount;
|
|
|
|
|
use App\Models\PlatformChannel;
|
2026-02-25 23:22:05 +01:00
|
|
|
use App\Models\PlatformChannelPost;
|
2025-08-06 21:49:13 +02:00
|
|
|
use App\Models\PlatformInstance;
|
2025-08-10 16:18:09 +02:00
|
|
|
use App\Models\Route;
|
2025-08-06 21:49:13 +02:00
|
|
|
use App\Modules\Lemmy\Services\LemmyPublisher;
|
|
|
|
|
use App\Services\Log\LogSaver;
|
|
|
|
|
use App\Services\Publishing\ArticlePublishingService;
|
|
|
|
|
use Exception;
|
|
|
|
|
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
|
|
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
|
use Mockery;
|
|
|
|
|
use Tests\TestCase;
|
|
|
|
|
|
|
|
|
|
class ArticlePublishingServiceTest extends TestCase
|
|
|
|
|
{
|
|
|
|
|
use RefreshDatabase;
|
|
|
|
|
|
|
|
|
|
protected ArticlePublishingService $service;
|
2026-03-08 14:18:28 +01:00
|
|
|
|
2025-08-15 02:50:42 +02:00
|
|
|
protected LogSaver $logSaver;
|
2025-08-06 21:49:13 +02:00
|
|
|
|
|
|
|
|
protected function setUp(): void
|
|
|
|
|
{
|
|
|
|
|
parent::setUp();
|
2025-08-15 02:50:42 +02:00
|
|
|
$this->logSaver = Mockery::mock(LogSaver::class);
|
|
|
|
|
$this->logSaver->shouldReceive('info')->zeroOrMoreTimes();
|
|
|
|
|
$this->logSaver->shouldReceive('warning')->zeroOrMoreTimes();
|
|
|
|
|
$this->logSaver->shouldReceive('error')->zeroOrMoreTimes();
|
|
|
|
|
$this->logSaver->shouldReceive('debug')->zeroOrMoreTimes();
|
|
|
|
|
$this->service = new ArticlePublishingService($this->logSaver);
|
2025-08-06 21:49:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected function tearDown(): void
|
|
|
|
|
{
|
|
|
|
|
Mockery::close();
|
|
|
|
|
parent::tearDown();
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-10 16:18:09 +02:00
|
|
|
public function test_publish_to_routed_channels_returns_empty_collection_when_no_active_routes(): void
|
2025-08-06 21:49:13 +02:00
|
|
|
{
|
|
|
|
|
$feed = Feed::factory()->create();
|
|
|
|
|
$article = Article::factory()->create([
|
|
|
|
|
'feed_id' => $feed->id,
|
2026-03-18 16:23:46 +01:00
|
|
|
|
2026-03-08 14:18:28 +01:00
|
|
|
'validated_at' => now(),
|
2025-08-06 21:49:13 +02:00
|
|
|
]);
|
|
|
|
|
$extractedData = ['title' => 'Test Title'];
|
|
|
|
|
|
|
|
|
|
$result = $this->service->publishToRoutedChannels($article, $extractedData);
|
|
|
|
|
|
|
|
|
|
$this->assertInstanceOf(EloquentCollection::class, $result);
|
|
|
|
|
$this->assertTrue($result->isEmpty());
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-10 16:18:09 +02:00
|
|
|
public function test_publish_to_routed_channels_skips_routes_without_active_accounts(): void
|
2025-08-06 21:49:13 +02:00
|
|
|
{
|
2025-08-10 01:26:56 +02:00
|
|
|
// Arrange: valid article
|
|
|
|
|
$feed = Feed::factory()->create();
|
|
|
|
|
$article = Article::factory()->create([
|
|
|
|
|
'feed_id' => $feed->id,
|
2026-03-18 16:23:46 +01:00
|
|
|
|
2026-03-08 02:54:10 +01:00
|
|
|
'validated_at' => now(),
|
2025-08-10 01:26:56 +02:00
|
|
|
]);
|
|
|
|
|
|
2025-08-10 16:18:09 +02:00
|
|
|
// Create a route with a channel but no active accounts
|
2025-08-10 01:26:56 +02:00
|
|
|
$channel = PlatformChannel::factory()->create();
|
|
|
|
|
|
2025-08-10 16:18:09 +02:00
|
|
|
Route::create([
|
|
|
|
|
'feed_id' => $feed->id,
|
|
|
|
|
'platform_channel_id' => $channel->id,
|
|
|
|
|
'is_active' => true,
|
2026-03-08 14:18:28 +01:00
|
|
|
'priority' => 50,
|
2025-08-10 16:18:09 +02:00
|
|
|
]);
|
2025-08-10 01:26:56 +02:00
|
|
|
|
2025-08-10 16:18:09 +02:00
|
|
|
// Don't create any platform accounts for the channel
|
2025-08-10 01:26:56 +02:00
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
$result = $this->service->publishToRoutedChannels($article, ['title' => 'Test']);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
$this->assertTrue($result->isEmpty());
|
|
|
|
|
$this->assertDatabaseCount('article_publications', 0);
|
2025-08-06 21:49:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_publish_to_routed_channels_successfully_publishes_to_channel(): void
|
|
|
|
|
{
|
2025-08-10 01:26:56 +02:00
|
|
|
// Arrange
|
|
|
|
|
$feed = Feed::factory()->create();
|
2026-03-18 16:23:46 +01:00
|
|
|
$article = Article::factory()->create(['feed_id' => $feed->id, 'validated_at' => now()]);
|
2026-02-25 23:22:05 +01:00
|
|
|
|
2025-08-10 16:18:09 +02:00
|
|
|
$platformInstance = PlatformInstance::factory()->create();
|
|
|
|
|
$channel = PlatformChannel::factory()->create(['platform_instance_id' => $platformInstance->id]);
|
|
|
|
|
$account = PlatformAccount::factory()->create();
|
2025-08-10 01:26:56 +02:00
|
|
|
|
2025-08-10 16:18:09 +02:00
|
|
|
// Create route
|
|
|
|
|
Route::create([
|
|
|
|
|
'feed_id' => $feed->id,
|
|
|
|
|
'platform_channel_id' => $channel->id,
|
|
|
|
|
'is_active' => true,
|
2026-03-08 14:18:28 +01:00
|
|
|
'priority' => 50,
|
2025-08-10 16:18:09 +02:00
|
|
|
]);
|
2025-08-10 01:26:56 +02:00
|
|
|
|
2025-08-10 16:18:09 +02:00
|
|
|
// Attach account to channel as active
|
|
|
|
|
$channel->platformAccounts()->attach($account->id, [
|
|
|
|
|
'is_active' => true,
|
2026-03-08 14:18:28 +01:00
|
|
|
'priority' => 50,
|
2025-08-10 16:18:09 +02:00
|
|
|
]);
|
2025-08-10 01:26:56 +02:00
|
|
|
|
|
|
|
|
// Mock publisher via service seam
|
|
|
|
|
$publisherDouble = \Mockery::mock(LemmyPublisher::class);
|
|
|
|
|
$publisherDouble->shouldReceive('publishToChannel')
|
|
|
|
|
->once()
|
|
|
|
|
->andReturn(['post_view' => ['post' => ['id' => 123]]]);
|
2025-08-15 02:50:42 +02:00
|
|
|
$service = \Mockery::mock(ArticlePublishingService::class, [$this->logSaver])->makePartial();
|
2025-08-10 01:26:56 +02:00
|
|
|
$service->shouldAllowMockingProtectedMethods();
|
|
|
|
|
$service->shouldReceive('makePublisher')->andReturn($publisherDouble);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
$result = $service->publishToRoutedChannels($article, ['title' => 'Hello']);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
$this->assertCount(1, $result);
|
|
|
|
|
$this->assertDatabaseHas('article_publications', [
|
|
|
|
|
'article_id' => $article->id,
|
|
|
|
|
'platform_channel_id' => $channel->id,
|
|
|
|
|
'post_id' => 123,
|
|
|
|
|
'published_by' => $account->username,
|
|
|
|
|
]);
|
2025-08-06 21:49:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_publish_to_routed_channels_handles_publishing_failure_gracefully(): void
|
|
|
|
|
{
|
2025-08-10 01:26:56 +02:00
|
|
|
// Arrange
|
|
|
|
|
$feed = Feed::factory()->create();
|
2026-03-18 16:23:46 +01:00
|
|
|
$article = Article::factory()->create(['feed_id' => $feed->id, 'validated_at' => now()]);
|
2026-02-25 23:22:05 +01:00
|
|
|
|
2025-08-10 16:18:09 +02:00
|
|
|
$platformInstance = PlatformInstance::factory()->create();
|
|
|
|
|
$channel = PlatformChannel::factory()->create(['platform_instance_id' => $platformInstance->id]);
|
|
|
|
|
$account = PlatformAccount::factory()->create();
|
2025-08-10 01:26:56 +02:00
|
|
|
|
2025-08-10 16:18:09 +02:00
|
|
|
// Create route
|
|
|
|
|
Route::create([
|
|
|
|
|
'feed_id' => $feed->id,
|
|
|
|
|
'platform_channel_id' => $channel->id,
|
|
|
|
|
'is_active' => true,
|
2026-03-08 14:18:28 +01:00
|
|
|
'priority' => 50,
|
2025-08-10 16:18:09 +02:00
|
|
|
]);
|
2025-08-10 01:26:56 +02:00
|
|
|
|
2025-08-10 16:18:09 +02:00
|
|
|
// Attach account to channel as active
|
|
|
|
|
$channel->platformAccounts()->attach($account->id, [
|
|
|
|
|
'is_active' => true,
|
2026-03-08 14:18:28 +01:00
|
|
|
'priority' => 50,
|
2025-08-10 16:18:09 +02:00
|
|
|
]);
|
2025-08-10 01:26:56 +02:00
|
|
|
|
|
|
|
|
// Publisher throws an exception via service seam
|
|
|
|
|
$publisherDouble = \Mockery::mock(LemmyPublisher::class);
|
|
|
|
|
$publisherDouble->shouldReceive('publishToChannel')
|
|
|
|
|
->once()
|
|
|
|
|
->andThrow(new Exception('network error'));
|
2025-08-15 02:50:42 +02:00
|
|
|
$service = \Mockery::mock(ArticlePublishingService::class, [$this->logSaver])->makePartial();
|
2025-08-10 01:26:56 +02:00
|
|
|
$service->shouldAllowMockingProtectedMethods();
|
|
|
|
|
$service->shouldReceive('makePublisher')->andReturn($publisherDouble);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
$result = $service->publishToRoutedChannels($article, ['title' => 'Hello']);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
$this->assertTrue($result->isEmpty());
|
|
|
|
|
$this->assertDatabaseCount('article_publications', 0);
|
2025-08-06 21:49:13 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-10 16:18:09 +02:00
|
|
|
public function test_publish_to_routed_channels_publishes_to_multiple_routes(): void
|
2025-08-06 21:49:13 +02:00
|
|
|
{
|
2025-08-10 01:26:56 +02:00
|
|
|
// Arrange
|
|
|
|
|
$feed = Feed::factory()->create();
|
2026-03-18 16:23:46 +01:00
|
|
|
$article = Article::factory()->create(['feed_id' => $feed->id, 'validated_at' => now()]);
|
2025-08-10 01:26:56 +02:00
|
|
|
|
2025-08-10 16:18:09 +02:00
|
|
|
$platformInstance = PlatformInstance::factory()->create();
|
|
|
|
|
$channel1 = PlatformChannel::factory()->create(['platform_instance_id' => $platformInstance->id]);
|
|
|
|
|
$channel2 = PlatformChannel::factory()->create(['platform_instance_id' => $platformInstance->id]);
|
2025-08-10 01:26:56 +02:00
|
|
|
$account1 = PlatformAccount::factory()->create();
|
|
|
|
|
$account2 = PlatformAccount::factory()->create();
|
|
|
|
|
|
2025-08-10 16:18:09 +02:00
|
|
|
// Create routes
|
|
|
|
|
Route::create([
|
|
|
|
|
'feed_id' => $feed->id,
|
|
|
|
|
'platform_channel_id' => $channel1->id,
|
|
|
|
|
'is_active' => true,
|
2026-03-08 14:18:28 +01:00
|
|
|
'priority' => 100,
|
2025-08-10 16:18:09 +02:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
Route::create([
|
|
|
|
|
'feed_id' => $feed->id,
|
|
|
|
|
'platform_channel_id' => $channel2->id,
|
|
|
|
|
'is_active' => true,
|
2026-03-08 14:18:28 +01:00
|
|
|
'priority' => 50,
|
2025-08-10 16:18:09 +02:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Attach accounts to channels as active
|
|
|
|
|
$channel1->platformAccounts()->attach($account1->id, [
|
|
|
|
|
'is_active' => true,
|
2026-03-08 14:18:28 +01:00
|
|
|
'priority' => 50,
|
2025-08-10 16:18:09 +02:00
|
|
|
]);
|
|
|
|
|
$channel2->platformAccounts()->attach($account2->id, [
|
|
|
|
|
'is_active' => true,
|
2026-03-08 14:18:28 +01:00
|
|
|
'priority' => 50,
|
2025-08-10 16:18:09 +02:00
|
|
|
]);
|
2025-08-10 01:26:56 +02:00
|
|
|
|
|
|
|
|
$publisherDouble = \Mockery::mock(LemmyPublisher::class);
|
|
|
|
|
$publisherDouble->shouldReceive('publishToChannel')
|
|
|
|
|
->once()->andReturn(['post_view' => ['post' => ['id' => 100]]]);
|
|
|
|
|
$publisherDouble->shouldReceive('publishToChannel')
|
|
|
|
|
->once()->andReturn(['post_view' => ['post' => ['id' => 200]]]);
|
2025-08-15 02:50:42 +02:00
|
|
|
$service = \Mockery::mock(ArticlePublishingService::class, [$this->logSaver])->makePartial();
|
2025-08-10 01:26:56 +02:00
|
|
|
$service->shouldAllowMockingProtectedMethods();
|
|
|
|
|
$service->shouldReceive('makePublisher')->andReturn($publisherDouble);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
$result = $service->publishToRoutedChannels($article, ['title' => 'Hello']);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
$this->assertCount(2, $result);
|
|
|
|
|
$this->assertDatabaseHas('article_publications', ['post_id' => 100]);
|
|
|
|
|
$this->assertDatabaseHas('article_publications', ['post_id' => 200]);
|
2025-08-06 21:49:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_publish_to_routed_channels_filters_out_failed_publications(): void
|
|
|
|
|
{
|
2025-08-10 01:26:56 +02:00
|
|
|
// Arrange
|
|
|
|
|
$feed = Feed::factory()->create();
|
2026-03-18 16:23:46 +01:00
|
|
|
$article = Article::factory()->create(['feed_id' => $feed->id, 'validated_at' => now()]);
|
2025-08-10 01:26:56 +02:00
|
|
|
|
2025-08-10 16:18:09 +02:00
|
|
|
$platformInstance = PlatformInstance::factory()->create();
|
|
|
|
|
$channel1 = PlatformChannel::factory()->create(['platform_instance_id' => $platformInstance->id]);
|
|
|
|
|
$channel2 = PlatformChannel::factory()->create(['platform_instance_id' => $platformInstance->id]);
|
2025-08-10 01:26:56 +02:00
|
|
|
$account1 = PlatformAccount::factory()->create();
|
|
|
|
|
$account2 = PlatformAccount::factory()->create();
|
|
|
|
|
|
2025-08-10 16:18:09 +02:00
|
|
|
// Create routes
|
|
|
|
|
Route::create([
|
|
|
|
|
'feed_id' => $feed->id,
|
|
|
|
|
'platform_channel_id' => $channel1->id,
|
|
|
|
|
'is_active' => true,
|
2026-03-08 14:18:28 +01:00
|
|
|
'priority' => 100,
|
2025-08-10 16:18:09 +02:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
Route::create([
|
|
|
|
|
'feed_id' => $feed->id,
|
|
|
|
|
'platform_channel_id' => $channel2->id,
|
|
|
|
|
'is_active' => true,
|
2026-03-08 14:18:28 +01:00
|
|
|
'priority' => 50,
|
2025-08-10 16:18:09 +02:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Attach accounts to channels as active
|
|
|
|
|
$channel1->platformAccounts()->attach($account1->id, [
|
|
|
|
|
'is_active' => true,
|
2026-03-08 14:18:28 +01:00
|
|
|
'priority' => 50,
|
2025-08-10 16:18:09 +02:00
|
|
|
]);
|
|
|
|
|
$channel2->platformAccounts()->attach($account2->id, [
|
|
|
|
|
'is_active' => true,
|
2026-03-08 14:18:28 +01:00
|
|
|
'priority' => 50,
|
2025-08-10 16:18:09 +02:00
|
|
|
]);
|
2025-08-10 01:26:56 +02:00
|
|
|
|
|
|
|
|
$publisherDouble = \Mockery::mock(LemmyPublisher::class);
|
|
|
|
|
$publisherDouble->shouldReceive('publishToChannel')
|
|
|
|
|
->once()->andReturn(['post_view' => ['post' => ['id' => 300]]]);
|
|
|
|
|
$publisherDouble->shouldReceive('publishToChannel')
|
|
|
|
|
->once()->andThrow(new Exception('failed'));
|
2025-08-15 02:50:42 +02:00
|
|
|
$service = \Mockery::mock(ArticlePublishingService::class, [$this->logSaver])->makePartial();
|
2025-08-10 01:26:56 +02:00
|
|
|
$service->shouldAllowMockingProtectedMethods();
|
|
|
|
|
$service->shouldReceive('makePublisher')->andReturn($publisherDouble);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
$result = $service->publishToRoutedChannels($article, ['title' => 'Hello']);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
$this->assertCount(1, $result);
|
|
|
|
|
$this->assertDatabaseHas('article_publications', ['post_id' => 300]);
|
|
|
|
|
$this->assertDatabaseCount('article_publications', 1);
|
2025-08-06 21:49:13 +02:00
|
|
|
}
|
2026-02-25 23:22:05 +01:00
|
|
|
|
|
|
|
|
public function test_publish_skips_duplicate_when_url_already_posted_to_channel(): void
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
$feed = Feed::factory()->create();
|
|
|
|
|
$article = Article::factory()->create([
|
|
|
|
|
'feed_id' => $feed->id,
|
2026-03-18 16:23:46 +01:00
|
|
|
|
2026-03-08 02:54:10 +01:00
|
|
|
'validated_at' => now(),
|
2026-02-25 23:22:05 +01:00
|
|
|
'url' => 'https://example.com/article-1',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$platformInstance = PlatformInstance::factory()->create(['platform' => 'lemmy']);
|
|
|
|
|
$channel = PlatformChannel::factory()->create(['platform_instance_id' => $platformInstance->id]);
|
|
|
|
|
$account = PlatformAccount::factory()->create();
|
|
|
|
|
|
|
|
|
|
Route::create([
|
|
|
|
|
'feed_id' => $feed->id,
|
|
|
|
|
'platform_channel_id' => $channel->id,
|
|
|
|
|
'is_active' => true,
|
|
|
|
|
'priority' => 50,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$channel->platformAccounts()->attach($account->id, [
|
|
|
|
|
'is_active' => true,
|
|
|
|
|
'priority' => 50,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Simulate the URL already being posted to this channel (synced from Lemmy)
|
|
|
|
|
PlatformChannelPost::storePost(
|
|
|
|
|
PlatformEnum::LEMMY,
|
|
|
|
|
(string) $channel->channel_id,
|
|
|
|
|
$channel->name,
|
|
|
|
|
'999',
|
|
|
|
|
'https://example.com/article-1',
|
|
|
|
|
'Different Title',
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Publisher should never be called
|
|
|
|
|
$publisherDouble = \Mockery::mock(LemmyPublisher::class);
|
|
|
|
|
$publisherDouble->shouldNotReceive('publishToChannel');
|
|
|
|
|
$service = \Mockery::mock(ArticlePublishingService::class, [$this->logSaver])->makePartial();
|
|
|
|
|
$service->shouldAllowMockingProtectedMethods();
|
|
|
|
|
$service->shouldReceive('makePublisher')->andReturn($publisherDouble);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
$result = $service->publishToRoutedChannels($article, ['title' => 'Some Title']);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
$this->assertTrue($result->isEmpty());
|
|
|
|
|
$this->assertDatabaseCount('article_publications', 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_publish_skips_duplicate_when_title_already_posted_to_channel(): void
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
$feed = Feed::factory()->create();
|
|
|
|
|
$article = Article::factory()->create([
|
|
|
|
|
'feed_id' => $feed->id,
|
2026-03-18 16:23:46 +01:00
|
|
|
|
2026-03-08 02:54:10 +01:00
|
|
|
'validated_at' => now(),
|
2026-02-25 23:22:05 +01:00
|
|
|
'url' => 'https://example.com/article-new-url',
|
|
|
|
|
'title' => 'Breaking News: Something Happened',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$platformInstance = PlatformInstance::factory()->create(['platform' => 'lemmy']);
|
|
|
|
|
$channel = PlatformChannel::factory()->create(['platform_instance_id' => $platformInstance->id]);
|
|
|
|
|
$account = PlatformAccount::factory()->create();
|
|
|
|
|
|
|
|
|
|
Route::create([
|
|
|
|
|
'feed_id' => $feed->id,
|
|
|
|
|
'platform_channel_id' => $channel->id,
|
|
|
|
|
'is_active' => true,
|
|
|
|
|
'priority' => 50,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$channel->platformAccounts()->attach($account->id, [
|
|
|
|
|
'is_active' => true,
|
|
|
|
|
'priority' => 50,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Simulate the same title already posted with a different URL
|
|
|
|
|
PlatformChannelPost::storePost(
|
|
|
|
|
PlatformEnum::LEMMY,
|
|
|
|
|
(string) $channel->channel_id,
|
|
|
|
|
$channel->name,
|
|
|
|
|
'888',
|
|
|
|
|
'https://example.com/different-url',
|
|
|
|
|
'Breaking News: Something Happened',
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Publisher should never be called
|
|
|
|
|
$publisherDouble = \Mockery::mock(LemmyPublisher::class);
|
|
|
|
|
$publisherDouble->shouldNotReceive('publishToChannel');
|
|
|
|
|
$service = \Mockery::mock(ArticlePublishingService::class, [$this->logSaver])->makePartial();
|
|
|
|
|
$service->shouldAllowMockingProtectedMethods();
|
|
|
|
|
$service->shouldReceive('makePublisher')->andReturn($publisherDouble);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
$result = $service->publishToRoutedChannels($article, ['title' => 'Breaking News: Something Happened']);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
$this->assertTrue($result->isEmpty());
|
|
|
|
|
$this->assertDatabaseCount('article_publications', 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_publish_proceeds_when_no_duplicate_exists(): void
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
$feed = Feed::factory()->create();
|
|
|
|
|
$article = Article::factory()->create([
|
|
|
|
|
'feed_id' => $feed->id,
|
2026-03-18 16:23:46 +01:00
|
|
|
|
2026-03-08 02:54:10 +01:00
|
|
|
'validated_at' => now(),
|
2026-02-25 23:22:05 +01:00
|
|
|
'url' => 'https://example.com/unique-article',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$platformInstance = PlatformInstance::factory()->create(['platform' => 'lemmy']);
|
|
|
|
|
$channel = PlatformChannel::factory()->create(['platform_instance_id' => $platformInstance->id]);
|
|
|
|
|
$account = PlatformAccount::factory()->create();
|
|
|
|
|
|
|
|
|
|
Route::create([
|
|
|
|
|
'feed_id' => $feed->id,
|
|
|
|
|
'platform_channel_id' => $channel->id,
|
|
|
|
|
'is_active' => true,
|
|
|
|
|
'priority' => 50,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$channel->platformAccounts()->attach($account->id, [
|
|
|
|
|
'is_active' => true,
|
|
|
|
|
'priority' => 50,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Existing post in the channel has a completely different URL and title
|
|
|
|
|
PlatformChannelPost::storePost(
|
|
|
|
|
PlatformEnum::LEMMY,
|
|
|
|
|
(string) $channel->channel_id,
|
|
|
|
|
$channel->name,
|
|
|
|
|
'777',
|
|
|
|
|
'https://example.com/other-article',
|
|
|
|
|
'Totally Different Title',
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$publisherDouble = \Mockery::mock(LemmyPublisher::class);
|
|
|
|
|
$publisherDouble->shouldReceive('publishToChannel')
|
|
|
|
|
->once()
|
|
|
|
|
->andReturn(['post_view' => ['post' => ['id' => 456]]]);
|
|
|
|
|
$service = \Mockery::mock(ArticlePublishingService::class, [$this->logSaver])->makePartial();
|
|
|
|
|
$service->shouldAllowMockingProtectedMethods();
|
|
|
|
|
$service->shouldReceive('makePublisher')->andReturn($publisherDouble);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
$result = $service->publishToRoutedChannels($article, ['title' => 'Unique Title']);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
$this->assertCount(1, $result);
|
|
|
|
|
$this->assertDatabaseHas('article_publications', [
|
|
|
|
|
'article_id' => $article->id,
|
|
|
|
|
'post_id' => 456,
|
|
|
|
|
]);
|
|
|
|
|
}
|
2025-08-10 01:26:56 +02:00
|
|
|
}
|