create(['is_active' => true]); $job = new ArticleDiscoveryJob(); $job->handle(); // Should dispatch individual feed jobs Queue::assertPushed(ArticleDiscoveryForFeedJob::class); } public function test_article_discovery_for_feed_job_processes_feed(): void { Event::fake(); $feed = Feed::factory()->create([ 'url' => 'https://example.com/feed', 'is_active' => true ]); $job = new ArticleDiscoveryForFeedJob($feed); // Mock the external dependency behavior $this->app->bind(\App\Services\Article\ArticleFetcher::class, function () { $mock = \Mockery::mock(\App\Services\Article\ArticleFetcher::class); $mock->shouldReceive('fetchArticles')->andReturn([ ['title' => 'Test Article', 'url' => 'https://example.com/article1'], ['title' => 'Another Article', 'url' => 'https://example.com/article2'] ]); return $mock; }); $job->handle(); // Should create articles and fire events $this->assertCount(2, Article::all()); Event::assertDispatched(NewArticleFetched::class, 2); } public function test_sync_channel_posts_job_processes_successfully(): void { $channel = PlatformChannel::factory()->create(); $job = new SyncChannelPostsJob($channel); // Mock the external dependency $this->app->bind(\App\Modules\Lemmy\Services\LemmyApiService::class, function () { $mock = \Mockery::mock(\App\Modules\Lemmy\Services\LemmyApiService::class); $mock->shouldReceive('getChannelPosts')->andReturn([]); return $mock; }); $result = $job->handle(); $this->assertTrue($result); } public function test_publish_to_lemmy_job_has_correct_configuration(): void { $article = Article::factory()->create(); $job = new PublishToLemmyJob($article); $this->assertEquals('lemmy-posts', $job->queue); $this->assertInstanceOf(PublishToLemmyJob::class, $job); } public function test_new_article_fetched_event_is_dispatched(): void { Event::fake(); $feed = Feed::factory()->create(); $article = Article::factory()->create(['feed_id' => $feed->id]); event(new NewArticleFetched($article)); Event::assertDispatched(NewArticleFetched::class, function (NewArticleFetched $event) use ($article) { return $event->article->id === $article->id; }); } public function test_article_approved_event_is_dispatched(): void { Event::fake(); $article = Article::factory()->create(); event(new ArticleApproved($article)); Event::assertDispatched(ArticleApproved::class, function (ArticleApproved $event) use ($article) { return $event->article->id === $article->id; }); } public function test_article_ready_to_publish_event_is_dispatched(): void { Event::fake(); $article = Article::factory()->create(); event(new ArticleReadyToPublish($article)); Event::assertDispatched(ArticleReadyToPublish::class, function (ArticleReadyToPublish $event) use ($article) { return $event->article->id === $article->id; }); } public function test_exception_occurred_event_is_dispatched(): void { Event::fake(); $exception = new \Exception('Test exception'); event(new ExceptionOccurred($exception, ['context' => 'test'])); Event::assertDispatched(ExceptionOccurred::class, function (ExceptionOccurred $event) { return $event->exception->getMessage() === 'Test exception'; }); } public function test_exception_logged_event_is_dispatched(): void { Event::fake(); $logData = [ 'level' => 'error', 'message' => 'Test error', 'context' => ['key' => 'value'] ]; event(new ExceptionLogged($logData)); Event::assertDispatched(ExceptionLogged::class, function (ExceptionLogged $event) use ($logData) { return $event->logData['message'] === 'Test error'; }); } public function test_validate_article_listener_processes_new_article(): void { Event::fake([ArticleReadyToPublish::class]); $feed = Feed::factory()->create(); $article = Article::factory()->create([ 'feed_id' => $feed->id, 'is_valid' => null, 'validated_at' => null ]); $listener = new ValidateArticleListener(); $event = new NewArticleFetched($article); $listener->handle($event); $article->refresh(); $this->assertNotNull($article->validated_at); $this->assertNotNull($article->is_valid); } public function test_publish_approved_article_listener_queues_job(): void { Queue::fake(); $article = Article::factory()->create([ 'approval_status' => 'approved', 'is_valid' => true, 'validated_at' => now() ]); $listener = new PublishApprovedArticle(); $event = new ArticleApproved($article); $listener->handle($event); Event::assertDispatched(ArticleReadyToPublish::class); } public function test_publish_article_listener_queues_publish_job(): void { Queue::fake(); $article = Article::factory()->create([ 'is_valid' => true, 'validated_at' => now() ]); $listener = new PublishArticle(); $event = new ArticleReadyToPublish($article); $listener->handle($event); Queue::assertPushed(PublishToLemmyJob::class, function (PublishToLemmyJob $job) use ($article) { return $job->article->id === $article->id; }); } public function test_log_exception_to_database_listener_creates_log(): void { $logData = [ 'level' => 'error', 'message' => 'Test exception message', 'context' => json_encode(['error' => 'details']) ]; $listener = new LogExceptionToDatabase(); $event = new ExceptionLogged($logData); $listener->handle($event); $this->assertDatabaseHas('logs', [ 'level' => 'error', 'message' => 'Test exception message' ]); $log = Log::where('message', 'Test exception message')->first(); $this->assertNotNull($log); $this->assertEquals('error', $log->level); } public function test_event_listener_registration_works(): void { // Test that events are properly bound to listeners $listeners = Event::getListeners(NewArticleFetched::class); $this->assertNotEmpty($listeners); $listeners = Event::getListeners(ArticleApproved::class); $this->assertNotEmpty($listeners); $listeners = Event::getListeners(ArticleReadyToPublish::class); $this->assertNotEmpty($listeners); $listeners = Event::getListeners(ExceptionLogged::class); $this->assertNotEmpty($listeners); } public function test_job_retry_configuration(): void { $article = Article::factory()->create(); $job = new PublishToLemmyJob($article); // Test that job has retry configuration $this->assertObjectHasProperty('tries', $job); $this->assertObjectHasProperty('backoff', $job); } public function test_job_queue_configuration(): void { $feed = Feed::factory()->create(); $channel = PlatformChannel::factory()->create(); $article = Article::factory()->create(); $discoveryJob = new ArticleDiscoveryJob(); $feedJob = new ArticleDiscoveryForFeedJob($feed); $publishJob = new PublishToLemmyJob($article); $syncJob = new SyncChannelPostsJob($channel); // Test queue assignments $this->assertEquals('default', $discoveryJob->queue ?? 'default'); $this->assertEquals('discovery', $feedJob->queue ?? 'discovery'); $this->assertEquals('lemmy-posts', $publishJob->queue); $this->assertEquals('sync', $syncJob->queue ?? 'sync'); } protected function tearDown(): void { \Mockery::close(); parent::tearDown(); } }