From d2416a3ae2df346197605f77debdc3f66be10fe2 Mon Sep 17 00:00:00 2001 From: myrmidex Date: Tue, 5 Aug 2025 21:15:17 +0200 Subject: [PATCH] Fix tests + Move LogLevelEnum --- backend/app/{ => Enums}/LogLevelEnum.php | 2 +- backend/app/Events/ExceptionOccurred.php | 2 +- backend/app/Models/Log.php | 2 +- backend/app/Models/Setting.php | 12 +- backend/app/Providers/AppServiceProvider.php | 2 +- backend/app/Services/Log/LogSaver.php | 4 +- backend/bootstrap/app.php | 10 +- backend/database/factories/LogFactory.php | 4 +- .../2025_06_29_154705_create_logs_table.php | 2 +- backend/tests/Feature/ApiAccessTest.php | 55 ++++ .../AuthenticationAndAuthorizationTest.php | 300 ------------------ .../tests/Feature/DatabaseIntegrationTest.php | 75 ++--- backend/tests/Feature/JobsAndEventsTest.php | 135 ++++---- 13 files changed, 171 insertions(+), 434 deletions(-) rename backend/app/{ => Enums}/LogLevelEnum.php (95%) create mode 100644 backend/tests/Feature/ApiAccessTest.php delete mode 100644 backend/tests/Feature/AuthenticationAndAuthorizationTest.php diff --git a/backend/app/LogLevelEnum.php b/backend/app/Enums/LogLevelEnum.php similarity index 95% rename from backend/app/LogLevelEnum.php rename to backend/app/Enums/LogLevelEnum.php index 620df68..78d60fe 100644 --- a/backend/app/LogLevelEnum.php +++ b/backend/app/Enums/LogLevelEnum.php @@ -1,6 +1,6 @@ first(); - + return $setting ? $setting->value : $default; } @@ -23,7 +31,7 @@ public static function set(string $key, mixed $value): void public static function getBool(string $key, bool $default = false): bool { $value = static::get($key, $default); - + return filter_var($value, FILTER_VALIDATE_BOOLEAN); } diff --git a/backend/app/Providers/AppServiceProvider.php b/backend/app/Providers/AppServiceProvider.php index fb469a6..544ba68 100644 --- a/backend/app/Providers/AppServiceProvider.php +++ b/backend/app/Providers/AppServiceProvider.php @@ -2,9 +2,9 @@ namespace App\Providers; +use App\Enums\LogLevelEnum; use App\Events\ExceptionOccurred; use App\Listeners\LogExceptionToDatabase; -use App\LogLevelEnum; use Error; use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Support\Facades\Event; diff --git a/backend/app/Services/Log/LogSaver.php b/backend/app/Services/Log/LogSaver.php index a94d178..2592f82 100644 --- a/backend/app/Services/Log/LogSaver.php +++ b/backend/app/Services/Log/LogSaver.php @@ -2,7 +2,7 @@ namespace App\Services\Log; -use App\LogLevelEnum; +use App\Enums\LogLevelEnum; use App\Models\Log; use App\Models\PlatformChannel; @@ -62,4 +62,4 @@ private static function log(LogLevelEnum $level, string $message, ?PlatformChann 'context' => $logContext, ]); } -} \ No newline at end of file +} diff --git a/backend/bootstrap/app.php b/backend/bootstrap/app.php index 34f2dca..b08c378 100644 --- a/backend/bootstrap/app.php +++ b/backend/bootstrap/app.php @@ -26,12 +26,12 @@ ->withExceptions(function (Exceptions $exceptions) { $exceptions->reportable(function (Throwable $e) { $level = match (true) { - $e instanceof Error => App\LogLevelEnum::CRITICAL, - $e instanceof RuntimeException => App\LogLevelEnum::ERROR, - $e instanceof InvalidArgumentException => App\LogLevelEnum::WARNING, - default => App\LogLevelEnum::ERROR, + $e instanceof Error => \App\Enums\LogLevelEnum::CRITICAL, + $e instanceof RuntimeException => \App\Enums\LogLevelEnum::ERROR, + $e instanceof InvalidArgumentException => \App\Enums\LogLevelEnum::WARNING, + default => \App\Enums\LogLevelEnum::ERROR, }; - + App\Events\ExceptionOccurred::dispatch( $e, $level, diff --git a/backend/database/factories/LogFactory.php b/backend/database/factories/LogFactory.php index 8c0ef32..f9617de 100644 --- a/backend/database/factories/LogFactory.php +++ b/backend/database/factories/LogFactory.php @@ -2,7 +2,7 @@ namespace Database\Factories; -use App\LogLevelEnum; +use App\Enums\LogLevelEnum; use App\Models\Log; use Illuminate\Database\Eloquent\Factories\Factory; @@ -31,4 +31,4 @@ public function definition(): array ]), ]; } -} \ No newline at end of file +} diff --git a/backend/database/migrations/2025_06_29_154705_create_logs_table.php b/backend/database/migrations/2025_06_29_154705_create_logs_table.php index 9f4c0ae..985ddd0 100644 --- a/backend/database/migrations/2025_06_29_154705_create_logs_table.php +++ b/backend/database/migrations/2025_06_29_154705_create_logs_table.php @@ -1,6 +1,6 @@ get('/health'); + $response->assertSuccessful(); + } + + public function test_api_routes_are_publicly_accessible(): void + { + // Test that main API routes are accessible without authentication + $routes = [ + '/api/v1/articles', + '/api/v1/dashboard/stats', + '/api/v1/platform-accounts', + '/api/v1/platform-channels', + '/api/v1/feeds', + '/api/v1/routing', + '/api/v1/settings', + '/api/v1/logs' + ]; + + foreach ($routes as $route) { + $response = $this->get($route); + $this->assertTrue( + $response->isSuccessful(), + "API route {$route} should be publicly accessible" + ); + } + } + + public function test_fallback_route_returns_api_message(): void + { + $response = $this->get('/nonexistent-route'); + $response->assertStatus(404); + $response->assertJson([ + 'message' => 'This is the FFR API backend. Use /api/v1/* endpoints or check the React frontend.', + 'api_base' => '/api/v1' + ]); + } +} \ No newline at end of file diff --git a/backend/tests/Feature/AuthenticationAndAuthorizationTest.php b/backend/tests/Feature/AuthenticationAndAuthorizationTest.php deleted file mode 100644 index 6a4ecd4..0000000 --- a/backend/tests/Feature/AuthenticationAndAuthorizationTest.php +++ /dev/null @@ -1,300 +0,0 @@ -get($route); - $this->assertTrue( - $response->isSuccessful(), - "Public route {$route} should be accessible to guests" - ); - } - } - - public function test_application_routes_require_no_authentication_by_default(): void - { - // Test that main application routes are accessible - // This assumes the application doesn't have authentication middleware by default - - $routes = [ - '/articles', - '/logs', - '/settings', - '/platforms', - '/channels', - '/feeds', - '/routing' - ]; - - foreach ($routes as $route) { - $response = $this->get($route); - $this->assertTrue( - $response->isSuccessful(), - "Route {$route} should be accessible" - ); - } - } - - public function test_article_approval_permissions(): void - { - $feed = Feed::factory()->create(); - $article = Article::factory()->create([ - 'feed_id' => $feed->id, - 'approval_status' => 'pending' - ]); - - // Test approval endpoint - $response = $this->post("/articles/{$article->id}/approve"); - $response->assertRedirect(); // Should redirect after successful approval - - $article->refresh(); - $this->assertEquals('approved', $article->approval_status); - } - - public function test_article_rejection_permissions(): void - { - $feed = Feed::factory()->create(); - $article = Article::factory()->create([ - 'feed_id' => $feed->id, - 'approval_status' => 'pending' - ]); - - // Test rejection endpoint - $response = $this->post("/articles/{$article->id}/reject"); - $response->assertRedirect(); // Should redirect after successful rejection - - $article->refresh(); - $this->assertEquals('rejected', $article->approval_status); - } - - public function test_platform_management_permissions(): void - { - $platform = PlatformAccount::factory()->create(['is_active' => false]); - - // Test platform activation - $response = $this->post("/platforms/{$platform->id}/set-active"); - $response->assertRedirect(); - - $platform->refresh(); - $this->assertTrue($platform->is_active); - } - - public function test_channel_management_permissions(): void - { - $channel = PlatformChannel::factory()->create(['is_active' => false]); - - // Test channel toggle - $response = $this->post("/channels/{$channel->id}/toggle"); - $response->assertRedirect(); - - $channel->refresh(); - $this->assertTrue($channel->is_active); - } - - public function test_feed_management_permissions(): void - { - $feed = Feed::factory()->create(['is_active' => false]); - - // Test feed toggle - $response = $this->post("/feeds/{$feed->id}/toggle"); - $response->assertRedirect(); - - $feed->refresh(); - $this->assertTrue($feed->is_active); - } - - public function test_settings_update_permissions(): void - { - Setting::factory()->create([ - 'key' => 'test_setting', - 'value' => 'old_value' - ]); - - // Test settings update - $response = $this->put('/settings', [ - 'test_setting' => 'new_value' - ]); - - $response->assertRedirect(); - $this->assertEquals('new_value', Setting::where('key', 'test_setting')->first()->value); - } - - public function test_routing_management_permissions(): void - { - $feed = Feed::factory()->create(); - $channel = PlatformChannel::factory()->create(); - - // Test routing creation - $response = $this->post('/routing', [ - 'feed_id' => $feed->id, - 'platform_channel_id' => $channel->id, - 'is_active' => true - ]); - - // Should either create successfully or have validation errors - $this->assertTrue( - $response->isRedirect() || $response->status() === 422, - 'Routing creation should either succeed or fail with validation' - ); - } - - public function test_crud_operations_on_resources(): void - { - // Test platform CRUD - $platform = PlatformAccount::factory()->create(); - - $response = $this->get("/platforms/{$platform->id}"); - $response->assertSuccessful(); - - $response = $this->get("/platforms/{$platform->id}/edit"); - $response->assertSuccessful(); - - // Test channel CRUD - $channel = PlatformChannel::factory()->create(); - - $response = $this->get("/channels/{$channel->id}"); - $response->assertSuccessful(); - - $response = $this->get("/channels/{$channel->id}/edit"); - $response->assertSuccessful(); - - // Test feed CRUD - $feed = Feed::factory()->create(); - - $response = $this->get("/feeds/{$feed->id}"); - $response->assertSuccessful(); - - $response = $this->get("/feeds/{$feed->id}/edit"); - $response->assertSuccessful(); - } - - public function test_resource_creation_pages_are_accessible(): void - { - $createRoutes = [ - '/platforms/create', - '/channels/create', - '/feeds/create', - '/routing/create' - ]; - - foreach ($createRoutes as $route) { - $response = $this->get($route); - $this->assertTrue( - $response->isSuccessful(), - "Create route {$route} should be accessible" - ); - } - } - - public function test_nonexistent_resource_returns_404(): void - { - $response = $this->get('/platforms/99999'); - $response->assertNotFound(); - - $response = $this->get('/channels/99999'); - $response->assertNotFound(); - - $response = $this->get('/feeds/99999'); - $response->assertNotFound(); - } - - public function test_invalid_article_operations_handle_gracefully(): void - { - // Test operations on nonexistent articles - $response = $this->post('/articles/99999/approve'); - $response->assertNotFound(); - - $response = $this->post('/articles/99999/reject'); - $response->assertNotFound(); - } - - public function test_invalid_platform_operations_handle_gracefully(): void - { - // Test operations on nonexistent platforms - $response = $this->post('/platforms/99999/set-active'); - $response->assertNotFound(); - } - - public function test_invalid_channel_operations_handle_gracefully(): void - { - // Test operations on nonexistent channels - $response = $this->post('/channels/99999/toggle'); - $response->assertNotFound(); - } - - public function test_invalid_feed_operations_handle_gracefully(): void - { - // Test operations on nonexistent feeds - $response = $this->post('/feeds/99999/toggle'); - $response->assertNotFound(); - } - - public function test_csrf_protection_on_post_requests(): void - { - $feed = Feed::factory()->create(); - $article = Article::factory()->create(['feed_id' => $feed->id]); - - // Test that POST requests without CSRF token are rejected - $response = $this->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class) - ->post("/articles/{$article->id}/approve"); - - // Should work when CSRF middleware is disabled for testing - $response->assertRedirect(); - } - - public function test_method_spoofing_works_for_put_delete(): void - { - // Test that method spoofing works for PUT/DELETE requests - Setting::factory()->create([ - 'key' => 'test_setting', - 'value' => 'old_value' - ]); - - $response = $this->put('/settings', [ - 'test_setting' => 'new_value' - ]); - - $response->assertRedirect(); - } - - public function test_route_model_binding_works_correctly(): void - { - // Test that route model binding resolves correctly - $platform = PlatformAccount::factory()->create(); - $channel = PlatformChannel::factory()->create(); - $feed = Feed::factory()->create(); - - // These should all resolve the models correctly - $response = $this->get("/platforms/{$platform->id}"); - $response->assertSuccessful(); - - $response = $this->get("/channels/{$channel->id}"); - $response->assertSuccessful(); - - $response = $this->get("/feeds/{$feed->id}"); - $response->assertSuccessful(); - } -} \ No newline at end of file diff --git a/backend/tests/Feature/DatabaseIntegrationTest.php b/backend/tests/Feature/DatabaseIntegrationTest.php index 9af72e9..fb0b86c 100644 --- a/backend/tests/Feature/DatabaseIntegrationTest.php +++ b/backend/tests/Feature/DatabaseIntegrationTest.php @@ -42,12 +42,12 @@ public function test_language_model_creates_successfully(): void { $language = Language::factory()->create([ 'name' => 'English', - 'code' => 'en' + 'short_code' => 'en' ]); $this->assertDatabaseHas('languages', [ 'name' => 'English', - 'code' => 'en' + 'short_code' => 'en' ]); } @@ -66,43 +66,39 @@ public function test_platform_instance_model_creates_successfully(): void public function test_platform_account_model_creates_successfully(): void { - $instance = PlatformInstance::factory()->create(); - $account = PlatformAccount::factory()->create([ - 'platform_instance_id' => $instance->id, 'username' => 'testuser', 'is_active' => true ]); $this->assertDatabaseHas('platform_accounts', [ - 'platform_instance_id' => $instance->id, 'username' => 'testuser', 'is_active' => true ]); - $this->assertEquals($instance->id, $account->platformInstance->id); + $this->assertEquals('testuser', $account->username); } public function test_platform_channel_model_creates_successfully(): void { $language = Language::factory()->create(); - $account = PlatformAccount::factory()->create(); + $instance = PlatformInstance::factory()->create(); $channel = PlatformChannel::factory()->create([ - 'platform_account_id' => $account->id, + 'platform_instance_id' => $instance->id, 'language_id' => $language->id, 'name' => 'Test Channel', 'is_active' => true ]); $this->assertDatabaseHas('platform_channels', [ - 'platform_account_id' => $account->id, + 'platform_instance_id' => $instance->id, 'language_id' => $language->id, 'name' => 'Test Channel', 'is_active' => true ]); - $this->assertEquals($account->id, $channel->platformAccount->id); + $this->assertEquals($instance->id, $channel->platformInstance->id); $this->assertEquals($language->id, $channel->language->id); } @@ -169,7 +165,7 @@ public function test_article_publication_model_creates_successfully(): void ]); $this->assertEquals($article->id, $publication->article->id); - $this->assertEquals($channel->id, $publication->platformChannel->id); + $this->assertEquals($channel->id, $publication->platform_channel_id); } public function test_route_model_creates_successfully(): void @@ -195,25 +191,23 @@ public function test_route_model_creates_successfully(): void public function test_platform_channel_post_model_creates_successfully(): void { - $channel = PlatformChannel::factory()->create(); - $post = PlatformChannelPost::create([ - 'platform_channel_id' => $channel->id, + 'platform' => 'lemmy', + 'channel_id' => 'technology', 'post_id' => 'external-post-123', 'title' => 'Test Post', - 'content' => 'Test content', 'url' => 'https://example.com/post', - 'created_at' => now(), - 'updated_at' => now() + 'posted_at' => now() ]); $this->assertDatabaseHas('platform_channel_posts', [ - 'platform_channel_id' => $channel->id, + 'platform' => 'lemmy', + 'channel_id' => 'technology', 'post_id' => 'external-post-123', 'title' => 'Test Post' ]); - $this->assertEquals($channel->id, $post->platformChannel->id); + $this->assertEquals('external-post-123', $post->post_id); } public function test_keyword_model_creates_successfully(): void @@ -254,7 +248,7 @@ public function test_log_model_creates_successfully(): void public function test_setting_model_creates_successfully(): void { - $setting = Setting::factory()->create([ + $setting = Setting::create([ 'key' => 'test_setting', 'value' => 'test_value' ]); @@ -277,40 +271,19 @@ public function test_feed_articles_relationship(): void } } - public function test_article_publications_relationship(): void - { - $article = Article::factory()->create(); - $publications = ArticlePublication::factory()->count(2)->create(['article_id' => $article->id]); - - $this->assertCount(2, $article->publications); - - foreach ($publications as $publication) { - $this->assertTrue($article->publications->contains($publication)); - } - } - - public function test_platform_account_channels_relationship(): void + public function test_platform_account_channels_many_to_many_relationship(): void { $account = PlatformAccount::factory()->create(); - $channels = PlatformChannel::factory()->count(2)->create(['platform_account_id' => $account->id]); - - $this->assertCount(2, $account->channels); - - foreach ($channels as $channel) { - $this->assertTrue($account->channels->contains($channel)); - } - } - - public function test_platform_channel_routes_relationship(): void - { $channel = PlatformChannel::factory()->create(); - $routes = Route::factory()->count(2)->create(['platform_channel_id' => $channel->id]); - - $this->assertCount(2, $channel->routes); - foreach ($routes as $route) { - $this->assertTrue($channel->routes->contains($route)); - } + // Test the pivot table relationship + $account->channels()->attach($channel->id, ['is_active' => true, 'priority' => 1]); + + $this->assertDatabaseHas('platform_account_channels', [ + 'platform_account_id' => $account->id, + 'platform_channel_id' => $channel->id, + 'is_active' => true + ]); } public function test_language_platform_instances_relationship(): void diff --git a/backend/tests/Feature/JobsAndEventsTest.php b/backend/tests/Feature/JobsAndEventsTest.php index 8254fee..a534c70 100644 --- a/backend/tests/Feature/JobsAndEventsTest.php +++ b/backend/tests/Feature/JobsAndEventsTest.php @@ -7,8 +7,8 @@ use App\Events\ExceptionLogged; use App\Events\ExceptionOccurred; use App\Events\NewArticleFetched; -use App\Jobs\ArticleDiscoveryJob; use App\Jobs\ArticleDiscoveryForFeedJob; +use App\Jobs\ArticleDiscoveryJob; use App\Jobs\PublishToLemmyJob; use App\Jobs\SyncChannelPostsJob; use App\Listeners\LogExceptionToDatabase; @@ -17,8 +17,8 @@ use App\Listeners\ValidateArticleListener; use App\Models\Article; use App\Models\Feed; -use App\Models\PlatformChannel; use App\Models\Log; +use App\Models\PlatformChannel; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Queue; @@ -31,12 +31,12 @@ class JobsAndEventsTest extends TestCase public function test_article_discovery_job_processes_successfully(): void { Queue::fake(); - + $feed = Feed::factory()->create(['is_active' => true]); - + $job = new ArticleDiscoveryJob(); $job->handle(); - + // Should dispatch individual feed jobs Queue::assertPushed(ArticleDiscoveryForFeedJob::class); } @@ -44,25 +44,26 @@ public function test_article_discovery_job_processes_successfully(): void 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 ArticleFetcher to return created articles - $mockFetcher = \Mockery::mock('overload:' . \App\Services\Article\ArticleFetcher::class); + + // Mock the ArticleFetcher service in the container + $mockFetcher = \Mockery::mock(\App\Services\Article\ArticleFetcher::class); $article1 = Article::factory()->create(['url' => 'https://example.com/article1', 'feed_id' => $feed->id]); $article2 = Article::factory()->create(['url' => 'https://example.com/article2', 'feed_id' => $feed->id]); $mockFetcher->shouldReceive('getArticlesFromFeed') ->with($feed) ->andReturn(collect([$article1, $article2])); - + + $this->app->instance(\App\Services\Article\ArticleFetcher::class, $mockFetcher); + + $job = new ArticleDiscoveryForFeedJob($feed); $job->handle(); - - // Should have articles in database + + // Should have articles in database (existing articles created by factory) $this->assertCount(2, Article::all()); // Note: Events are not fired by ArticleDiscoveryForFeedJob directly // They would be fired by the Article model when created @@ -74,15 +75,15 @@ public function test_sync_channel_posts_job_processes_successfully(): void $key = config('app.key'); $cipher = config('app.cipher'); $this->assertNotNull($key, 'APP_KEY should be set'); - + // The supported method expects the raw key, not the base64: prefixed version $rawKey = base64_decode(substr($key, 7)); // Remove 'base64:' prefix and decode $this->assertTrue(app('encrypter')->supported($rawKey, $cipher), 'Encryption should be supported'); - + $channel = PlatformChannel::factory()->create([ 'channel_id' => '' // Empty string to trigger getCommunityId call ]); - + // Create platform account with proper factory $account = \App\Models\PlatformAccount::factory()->create([ 'is_active' => true, @@ -90,15 +91,15 @@ public function test_sync_channel_posts_job_processes_successfully(): void 'platform' => 'lemmy', 'instance_url' => 'https://lemmy.example.com' ]); - + // Attach the account to the channel with active status $channel->platformAccounts()->attach($account->id, [ 'is_active' => true, 'priority' => 1 ]); - + $job = new SyncChannelPostsJob($channel); - + // Mock the LemmyApiService class $mockApi = \Mockery::mock('overload:' . \App\Modules\Lemmy\Services\LemmyApiService::class); $mockApi->shouldReceive('login') @@ -111,9 +112,9 @@ public function test_sync_channel_posts_job_processes_successfully(): void $mockApi->shouldReceive('syncChannelPosts') ->once() ->andReturn(true); - + $job->handle(); - + $this->assertTrue(true); // If we get here without exception, test passes } @@ -121,9 +122,9 @@ public function test_sync_channel_posts_job_processes_successfully(): void 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); } @@ -131,12 +132,12 @@ public function test_publish_to_lemmy_job_has_correct_configuration(): void 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; }); @@ -145,11 +146,11 @@ public function test_new_article_fetched_event_is_dispatched(): void 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; }); @@ -158,11 +159,11 @@ public function test_article_approved_event_is_dispatched(): void 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; }); @@ -171,11 +172,11 @@ public function test_article_ready_to_publish_event_is_dispatched(): void public function test_exception_occurred_event_is_dispatched(): void { Event::fake(); - + $exception = new \Exception('Test exception'); - - event(new ExceptionOccurred($exception, \App\LogLevelEnum::ERROR, 'Test exception', ['context' => 'test'])); - + + event(new ExceptionOccurred($exception, \App\Enums\LogLevelEnum::ERROR, 'Test exception', ['context' => 'test'])); + Event::assertDispatched(ExceptionOccurred::class, function (ExceptionOccurred $event) { return $event->exception->getMessage() === 'Test exception'; }); @@ -184,15 +185,15 @@ public function test_exception_occurred_event_is_dispatched(): void public function test_exception_logged_event_is_dispatched(): void { Event::fake(); - + $log = Log::factory()->create([ 'level' => 'error', 'message' => 'Test error', 'context' => json_encode(['key' => 'value']) ]); - + event(new ExceptionLogged($log)); - + Event::assertDispatched(ExceptionLogged::class, function (ExceptionLogged $event) use ($log) { return $event->log->message === 'Test error'; }); @@ -201,14 +202,14 @@ public function test_exception_logged_event_is_dispatched(): void 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 ]); - + // Mock ArticleFetcher to return valid article data $mockFetcher = \Mockery::mock('alias:ArticleFetcher2'); $this->app->instance(\App\Services\Article\ArticleFetcher::class, $mockFetcher); @@ -217,12 +218,12 @@ public function test_validate_article_listener_processes_new_article(): void ->andReturn([ 'full_article' => 'Test article content' ]); - + $listener = new ValidateArticleListener(); $event = new NewArticleFetched($article); - + $listener->handle($event); - + $article->refresh(); $this->assertNotNull($article->validated_at); $this->assertNotNull($article->is_valid); @@ -231,35 +232,35 @@ public function test_validate_article_listener_processes_new_article(): void public function test_publish_approved_article_listener_queues_job(): void { Event::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); } @@ -270,21 +271,21 @@ public function test_log_exception_to_database_listener_creates_log(): void 'message' => 'Test exception message', 'context' => json_encode(['error' => 'details']) ]); - + $listener = new LogExceptionToDatabase(); $exception = new \Exception('Test exception message'); - $event = new ExceptionOccurred($exception, \App\LogLevelEnum::ERROR, 'Test exception message'); - + $event = new ExceptionOccurred($exception, \App\Enums\LogLevelEnum::ERROR, 'Test exception message'); + $listener->handle($event); - + $this->assertDatabaseHas('logs', [ 'level' => 'error', 'message' => 'Test exception message' ]); - + $savedLog = Log::where('message', 'Test exception message')->first(); $this->assertNotNull($savedLog); - $this->assertEquals(\App\LogLevelEnum::ERROR, $savedLog->level); + $this->assertEquals(\App\Enums\LogLevelEnum::ERROR, $savedLog->level); } public function test_event_listener_registration_works(): void @@ -292,13 +293,13 @@ 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(ExceptionOccurred::class); $this->assertNotEmpty($listeners); } @@ -306,9 +307,9 @@ public function test_event_listener_registration_works(): void 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); @@ -319,12 +320,12 @@ 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('feed-discovery', $discoveryJob->queue ?? 'default'); $this->assertEquals('feed-discovery', $feedJob->queue ?? 'discovery'); @@ -337,4 +338,4 @@ protected function tearDown(): void \Mockery::close(); parent::tearDown(); } -} \ No newline at end of file +}