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_passes_the_stored_community_id(): void { [$channel, $account] = $this->makeSyncableChannel(42); $apiMock = Mockery::mock(LemmyApiService::class); $apiMock->shouldReceive('login') ->once() ->with($account->username, $account->password) ->andReturn('token'); $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_uses_channel_id_without_a_lookup(): void { [$channel, $account] = $this->makeSyncableChannel(42); $apiMock = Mockery::mock(LemmyApiService::class); $apiMock->shouldReceive('login') ->once() ->with($account->username, $account->password) ->andReturn('token'); $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(int $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(); } }