279 lines
9.1 KiB
PHP
279 lines
9.1 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\Jobs;
|
|
|
|
use App\Enums\PlatformEnum;
|
|
use App\Jobs\SyncChannelPostsJob;
|
|
use App\Models\PlatformAccount;
|
|
use App\Models\PlatformChannel;
|
|
use App\Models\PlatformInstance;
|
|
use App\Modules\Lemmy\Services\LemmyApiService;
|
|
use App\Services\Log\LogSaver;
|
|
use Exception;
|
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Foundation\Queue\Queueable;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Mockery;
|
|
use Tests\TestCase;
|
|
|
|
class SyncChannelPostsJobTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
Queue::fake();
|
|
Cache::flush();
|
|
}
|
|
|
|
public function test_constructor_sets_correct_queue(): void
|
|
{
|
|
$channel = PlatformChannel::factory()->make();
|
|
$job = new SyncChannelPostsJob($channel);
|
|
|
|
$this->assertEquals('sync', $job->queue);
|
|
}
|
|
|
|
public function test_job_implements_should_queue(): void
|
|
{
|
|
$channel = PlatformChannel::factory()->make();
|
|
$job = new SyncChannelPostsJob($channel);
|
|
|
|
$this->assertInstanceOf(ShouldQueue::class, $job);
|
|
}
|
|
|
|
public function test_job_implements_should_be_unique(): void
|
|
{
|
|
$channel = PlatformChannel::factory()->make();
|
|
$job = new SyncChannelPostsJob($channel);
|
|
|
|
$this->assertInstanceOf(ShouldBeUnique::class, $job);
|
|
}
|
|
|
|
public function test_job_uses_queueable_trait(): void
|
|
{
|
|
$channel = PlatformChannel::factory()->make();
|
|
$job = new SyncChannelPostsJob($channel);
|
|
|
|
$this->assertContains(
|
|
Queueable::class,
|
|
class_uses($job)
|
|
);
|
|
}
|
|
|
|
public function test_dispatch_for_all_active_channels_dispatches_jobs(): void
|
|
{
|
|
// Arrange
|
|
$platformInstance = PlatformInstance::factory()->create([
|
|
'platform' => PlatformEnum::LEMMY,
|
|
]);
|
|
|
|
$account = PlatformAccount::factory()->create([
|
|
'instance_url' => $platformInstance->url,
|
|
'is_active' => true,
|
|
]);
|
|
|
|
$channel = PlatformChannel::factory()->create([
|
|
'platform_instance_id' => $platformInstance->id,
|
|
'is_active' => true,
|
|
]);
|
|
|
|
// Attach account to channel with active status
|
|
$channel->platformAccounts()->attach($account->id, [
|
|
'is_active' => true,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
// Mock LogSaver to avoid strict expectations
|
|
$logSaverMock = Mockery::mock(LogSaver::class);
|
|
$logSaverMock->shouldReceive('info')->zeroOrMoreTimes();
|
|
|
|
$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',
|
|
]);
|
|
|
|
$channel = PlatformChannel::factory()->create([
|
|
'platform_instance_id' => $platformInstance->id,
|
|
'name' => 'testcommunity',
|
|
]);
|
|
|
|
// Mock LogSaver - only test that logging methods are called
|
|
$logSaverMock = Mockery::mock(LogSaver::class);
|
|
$logSaverMock->shouldReceive('info')->atLeast()->once();
|
|
$logSaverMock->shouldReceive('error')->zeroOrMoreTimes();
|
|
|
|
$job = new SyncChannelPostsJob($channel);
|
|
|
|
// 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', 42, $channel->name);
|
|
|
|
$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', 42, $channel->name);
|
|
|
|
$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;
|
|
}
|
|
};
|
|
}
|
|
|
|
public function test_job_can_be_serialized(): void
|
|
{
|
|
$platformInstance = PlatformInstance::factory()->create();
|
|
$channel = PlatformChannel::factory()->create([
|
|
'platform_instance_id' => $platformInstance->id,
|
|
'name' => 'Test Channel',
|
|
]);
|
|
$job = new SyncChannelPostsJob($channel);
|
|
|
|
$serialized = serialize($job);
|
|
$unserialized = unserialize($serialized);
|
|
|
|
$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
|
|
}
|
|
|
|
public function test_dispatch_for_all_active_channels_method_exists(): void
|
|
{
|
|
$this->assertTrue(method_exists(SyncChannelPostsJob::class, 'dispatchForAllActiveChannels'));
|
|
}
|
|
|
|
public function test_job_has_handle_method(): void
|
|
{
|
|
$channel = PlatformChannel::factory()->make();
|
|
$job = new SyncChannelPostsJob($channel);
|
|
|
|
$this->assertTrue(method_exists($job, 'handle'));
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
Mockery::close();
|
|
parent::tearDown();
|
|
}
|
|
}
|