fedi-feed-router/tests/Unit/Jobs/SyncChannelPostsJobTest.php

280 lines
9.2 KiB
PHP
Raw Normal View History

2025-08-10 15:46:20 +02:00
<?php
namespace Tests\Unit\Jobs;
use App\Enums\PlatformEnum;
use App\Jobs\SyncChannelPostsJob;
2025-08-15 02:50:42 +02:00
use App\Models\PlatformAccount;
2025-08-10 15:46:20 +02:00
use App\Models\PlatformChannel;
2025-08-15 02:50:42 +02:00
use App\Models\PlatformInstance;
use App\Modules\Lemmy\Services\LemmyApiService;
2025-08-15 02:50:42 +02:00
use App\Services\Log\LogSaver;
use Exception;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
2025-08-10 15:46:20 +02:00
use Illuminate\Foundation\Testing\RefreshDatabase;
2025-08-15 02:50:42 +02:00
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Queue;
use Mockery;
2025-08-10 15:46:20 +02:00
use Tests\TestCase;
class SyncChannelPostsJobTest extends TestCase
{
use RefreshDatabase;
2025-08-15 02:50:42 +02:00
protected function setUp(): void
2025-08-10 15:46:20 +02:00
{
2025-08-15 02:50:42 +02:00
parent::setUp();
Queue::fake();
Cache::flush();
}
2025-08-10 15:46:20 +02:00
2025-08-15 02:50:42 +02:00
public function test_constructor_sets_correct_queue(): void
{
$channel = PlatformChannel::factory()->make();
2025-08-10 15:46:20 +02:00
$job = new SyncChannelPostsJob($channel);
2025-08-10 15:46:20 +02:00
$this->assertEquals('sync', $job->queue);
}
2025-08-15 02:50:42 +02:00
public function test_job_implements_should_queue(): void
2025-08-10 15:46:20 +02:00
{
2025-08-15 02:50:42 +02:00
$channel = PlatformChannel::factory()->make();
2025-08-10 15:46:20 +02:00
$job = new SyncChannelPostsJob($channel);
$this->assertInstanceOf(ShouldQueue::class, $job);
2025-08-15 02:50:42 +02:00
}
public function test_job_implements_should_be_unique(): void
{
$channel = PlatformChannel::factory()->make();
$job = new SyncChannelPostsJob($channel);
$this->assertInstanceOf(ShouldBeUnique::class, $job);
2025-08-10 15:46:20 +02:00
}
public function test_job_uses_queueable_trait(): void
2025-08-15 02:50:42 +02:00
{
$channel = PlatformChannel::factory()->make();
$job = new SyncChannelPostsJob($channel);
2025-08-15 02:50:42 +02:00
$this->assertContains(
Queueable::class,
2025-08-15 02:50:42 +02:00
class_uses($job)
);
}
public function test_dispatch_for_all_active_channels_dispatches_jobs(): void
2025-08-10 15:46:20 +02:00
{
// Arrange
2025-08-15 02:50:42 +02:00
$platformInstance = PlatformInstance::factory()->create([
'platform' => PlatformEnum::LEMMY,
2025-08-15 02:50:42 +02:00
]);
2025-08-15 02:50:42 +02:00
$account = PlatformAccount::factory()->create([
'instance_url' => $platformInstance->url,
'is_active' => true,
2025-08-15 02:50:42 +02:00
]);
2025-08-15 02:50:42 +02:00
$channel = PlatformChannel::factory()->create([
'platform_instance_id' => $platformInstance->id,
'is_active' => true,
2025-08-15 02:50:42 +02:00
]);
2025-08-15 02:50:42 +02:00
// Attach account to channel with active status
$channel->platformAccounts()->attach($account->id, [
'is_active' => true,
'created_at' => now(),
'updated_at' => now(),
2025-08-15 02:50:42 +02:00
]);
// Mock LogSaver to avoid strict expectations
$logSaverMock = Mockery::mock(LogSaver::class);
$logSaverMock->shouldReceive('info')->zeroOrMoreTimes();
2025-08-15 02:50:42 +02:00
$this->app->instance(LogSaver::class, $logSaverMock);
// Act
SyncChannelPostsJob::dispatchForAllActiveChannels();
// Assert - At least one job should be dispatched
Queue::assertPushed(SyncChannelPostsJob::class);
}
public function test_handle_logs_start_message(): void
{
// Arrange
$platformInstance = PlatformInstance::factory()->create([
'platform' => PlatformEnum::LEMMY,
'url' => 'https://lemmy.example.com',
2025-08-15 02:50:42 +02:00
]);
2025-08-15 02:50:42 +02:00
$channel = PlatformChannel::factory()->create([
'platform_instance_id' => $platformInstance->id,
'name' => 'testcommunity',
2025-08-15 02:50:42 +02:00
]);
// Mock LogSaver - only test that logging methods are called
$logSaverMock = Mockery::mock(LogSaver::class);
$logSaverMock->shouldReceive('info')->atLeast()->once();
$logSaverMock->shouldReceive('error')->zeroOrMoreTimes();
2025-08-10 15:46:20 +02:00
$job = new SyncChannelPostsJob($channel);
2025-08-15 02:50:42 +02:00
// Act - This will fail due to no active account, but we test the logging
try {
$job->handle($logSaverMock);
} catch (Exception $e) {
// Expected to fail, we're testing that logging is called
}
// Assert - Test completes if no exceptions during setup
$this->assertTrue(true);
}
public function test_sync_resolves_non_numeric_channel_id_via_get_community_id(): void
{
[$channel, $account] = $this->makeSyncableChannel('tech_news');
$apiMock = Mockery::mock(LemmyApiService::class);
$apiMock->shouldReceive('login')
->once()
->with($account->username, $account->password)
->andReturn('token');
$apiMock->shouldReceive('resolveCommunityId')
->once()
->with('tech_news', 'token')
->andReturn(42);
$apiMock->shouldReceive('syncChannelPosts')
->once()
->with('token', Mockery::on(fn ($arg) => $arg->is($channel)), 42);
$logSaverMock = Mockery::mock(LogSaver::class);
$logSaverMock->shouldReceive('info')->zeroOrMoreTimes();
$logSaverMock->shouldReceive('error')->zeroOrMoreTimes();
$this->makeJobWithApi($channel, $apiMock)->handle($logSaverMock);
// The behaviour under test is the mocked call sequence above; assert explicitly
// so PHPUnit does not flag the test as risky for performing no assertions.
$this->addToAssertionCount(1);
}
public function test_sync_passes_resolved_community_id_to_sync_channel_posts(): void
{
[$channel, $account] = $this->makeSyncableChannel('42');
$apiMock = Mockery::mock(LemmyApiService::class);
$apiMock->shouldReceive('login')
->once()
->with($account->username, $account->password)
->andReturn('token');
// The slug-vs-numeric branch itself now lives in LemmyApiService::resolveCommunityId
// and is covered by LemmyApiServiceTest; here we only assert the job forwards
// whatever that resolution returns.
$apiMock->shouldReceive('resolveCommunityId')
->once()
->with('42', 'token')
->andReturn(42);
$apiMock->shouldReceive('syncChannelPosts')
->once()
->with('token', Mockery::on(fn ($arg) => $arg->is($channel)), 42);
$logSaverMock = Mockery::mock(LogSaver::class);
$logSaverMock->shouldReceive('info')->zeroOrMoreTimes();
$logSaverMock->shouldReceive('error')->zeroOrMoreTimes();
$this->makeJobWithApi($channel, $apiMock)->handle($logSaverMock);
// The mocked call sequence above is the assertion; assert explicitly so PHPUnit
// does not flag the test as risky.
$this->addToAssertionCount(1);
}
/**
* @return array{0: PlatformChannel, 1: PlatformAccount}
*/
private function makeSyncableChannel(string $channelId): array
{
$platformInstance = PlatformInstance::factory()->create([
'platform' => PlatformEnum::LEMMY,
'url' => 'https://lemmy.example.com',
]);
$account = PlatformAccount::factory()->create([
'instance_url' => $platformInstance->url,
'is_active' => true,
]);
$channel = PlatformChannel::factory()->create([
'platform_instance_id' => $platformInstance->id,
'name' => 'tech_news',
'channel_id' => $channelId,
'is_active' => true,
]);
$channel->platformAccounts()->attach($account->id, [
'is_active' => true,
'priority' => 1,
'created_at' => now(),
'updated_at' => now(),
]);
return [$channel->fresh(['platformInstance']), $account];
}
private function makeJobWithApi(PlatformChannel $channel, LemmyApiService $api): SyncChannelPostsJob
{
return new class($channel, $api) extends SyncChannelPostsJob
{
public function __construct(PlatformChannel $channel, private readonly LemmyApiService $api)
{
parent::__construct($channel);
}
protected function makeApiService(string $instanceUrl): LemmyApiService
{
return $this->api;
}
};
}
2025-08-15 02:50:42 +02:00
public function test_job_can_be_serialized(): void
{
$platformInstance = PlatformInstance::factory()->create();
$channel = PlatformChannel::factory()->create([
'platform_instance_id' => $platformInstance->id,
'name' => 'Test Channel',
2025-08-15 02:50:42 +02:00
]);
$job = new SyncChannelPostsJob($channel);
2025-08-15 02:50:42 +02:00
$serialized = serialize($job);
$unserialized = unserialize($serialized);
2025-08-15 02:50:42 +02:00
$this->assertInstanceOf(SyncChannelPostsJob::class, $unserialized);
$this->assertEquals($job->queue, $unserialized->queue);
// Note: Cannot test channel property directly as it's private
// but serialization/unserialization working proves the job structure is intact
2025-08-10 15:46:20 +02:00
}
public function test_dispatch_for_all_active_channels_method_exists(): void
{
$this->assertTrue(method_exists(SyncChannelPostsJob::class, 'dispatchForAllActiveChannels'));
}
2025-08-15 02:50:42 +02:00
public function test_job_has_handle_method(): void
2025-08-10 15:46:20 +02:00
{
2025-08-15 02:50:42 +02:00
$channel = PlatformChannel::factory()->make();
2025-08-10 15:46:20 +02:00
$job = new SyncChannelPostsJob($channel);
2025-08-10 15:46:20 +02:00
$this->assertTrue(method_exists($job, 'handle'));
2025-08-15 02:50:42 +02:00
}
protected function tearDown(): void
{
Mockery::close();
parent::tearDown();
2025-08-10 15:46:20 +02:00
}
}