Fix tests

+ Move LogLevelEnum
This commit is contained in:
myrmidex 2025-08-05 21:15:17 +02:00
parent 43ab722fdc
commit d2416a3ae2
13 changed files with 171 additions and 434 deletions

View file

@ -1,6 +1,6 @@
<?php <?php
namespace App; namespace App\Enums;
enum LogLevelEnum: string enum LogLevelEnum: string
{ {

View file

@ -2,7 +2,7 @@
namespace App\Events; namespace App\Events;
use App\LogLevelEnum; use App\Enums\LogLevelEnum;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Throwable; use Throwable;

View file

@ -2,7 +2,7 @@
namespace App\Models; namespace App\Models;
use App\LogLevelEnum; use App\Enums\LogLevelEnum;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;

View file

@ -2,16 +2,24 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
/**
* @method static updateOrCreate(string[] $array, array $array1)
* @method static create(string[] $array)
* @method static where(string $string, string $key)
*/
class Setting extends Model class Setting extends Model
{ {
use HasFactory;
protected $fillable = ['key', 'value']; protected $fillable = ['key', 'value'];
public static function get(string $key, mixed $default = null): mixed public static function get(string $key, mixed $default = null): mixed
{ {
$setting = static::where('key', $key)->first(); $setting = static::where('key', $key)->first();
return $setting ? $setting->value : $default; 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 public static function getBool(string $key, bool $default = false): bool
{ {
$value = static::get($key, $default); $value = static::get($key, $default);
return filter_var($value, FILTER_VALIDATE_BOOLEAN); return filter_var($value, FILTER_VALIDATE_BOOLEAN);
} }

View file

@ -2,9 +2,9 @@
namespace App\Providers; namespace App\Providers;
use App\Enums\LogLevelEnum;
use App\Events\ExceptionOccurred; use App\Events\ExceptionOccurred;
use App\Listeners\LogExceptionToDatabase; use App\Listeners\LogExceptionToDatabase;
use App\LogLevelEnum;
use Error; use Error;
use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Contracts\Debug\ExceptionHandler;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Event;

View file

@ -2,7 +2,7 @@
namespace App\Services\Log; namespace App\Services\Log;
use App\LogLevelEnum; use App\Enums\LogLevelEnum;
use App\Models\Log; use App\Models\Log;
use App\Models\PlatformChannel; use App\Models\PlatformChannel;
@ -62,4 +62,4 @@ private static function log(LogLevelEnum $level, string $message, ?PlatformChann
'context' => $logContext, 'context' => $logContext,
]); ]);
} }
} }

View file

@ -26,12 +26,12 @@
->withExceptions(function (Exceptions $exceptions) { ->withExceptions(function (Exceptions $exceptions) {
$exceptions->reportable(function (Throwable $e) { $exceptions->reportable(function (Throwable $e) {
$level = match (true) { $level = match (true) {
$e instanceof Error => App\LogLevelEnum::CRITICAL, $e instanceof Error => \App\Enums\LogLevelEnum::CRITICAL,
$e instanceof RuntimeException => App\LogLevelEnum::ERROR, $e instanceof RuntimeException => \App\Enums\LogLevelEnum::ERROR,
$e instanceof InvalidArgumentException => App\LogLevelEnum::WARNING, $e instanceof InvalidArgumentException => \App\Enums\LogLevelEnum::WARNING,
default => App\LogLevelEnum::ERROR, default => \App\Enums\LogLevelEnum::ERROR,
}; };
App\Events\ExceptionOccurred::dispatch( App\Events\ExceptionOccurred::dispatch(
$e, $e,
$level, $level,

View file

@ -2,7 +2,7 @@
namespace Database\Factories; namespace Database\Factories;
use App\LogLevelEnum; use App\Enums\LogLevelEnum;
use App\Models\Log; use App\Models\Log;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
@ -31,4 +31,4 @@ public function definition(): array
]), ]),
]; ];
} }
} }

View file

@ -1,6 +1,6 @@
<?php <?php
use App\LogLevelEnum; use App\Enums\LogLevelEnum;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;

View file

@ -0,0 +1,55 @@
<?php
namespace Tests\Feature;
use App\Models\Article;
use App\Models\Feed;
use App\Models\PlatformAccount;
use App\Models\PlatformChannel;
use App\Models\Setting;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ApiAccessTest extends TestCase
{
use RefreshDatabase;
public function test_health_endpoint_is_accessible(): void
{
$response = $this->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'
]);
}
}

View file

@ -1,300 +0,0 @@
<?php
namespace Tests\Feature;
use App\Models\User;
use App\Models\Article;
use App\Models\Feed;
use App\Models\PlatformAccount;
use App\Models\PlatformChannel;
use App\Models\Setting;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class AuthenticationAndAuthorizationTest extends TestCase
{
use RefreshDatabase;
public function test_guest_can_access_public_routes(): void
{
$publicRoutes = [
'/',
'/onboarding/platform',
'/onboarding/feed',
'/onboarding/channel',
'/onboarding/complete'
];
foreach ($publicRoutes as $route) {
$response = $this->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();
}
}

View file

@ -42,12 +42,12 @@ public function test_language_model_creates_successfully(): void
{ {
$language = Language::factory()->create([ $language = Language::factory()->create([
'name' => 'English', 'name' => 'English',
'code' => 'en' 'short_code' => 'en'
]); ]);
$this->assertDatabaseHas('languages', [ $this->assertDatabaseHas('languages', [
'name' => 'English', '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 public function test_platform_account_model_creates_successfully(): void
{ {
$instance = PlatformInstance::factory()->create();
$account = PlatformAccount::factory()->create([ $account = PlatformAccount::factory()->create([
'platform_instance_id' => $instance->id,
'username' => 'testuser', 'username' => 'testuser',
'is_active' => true 'is_active' => true
]); ]);
$this->assertDatabaseHas('platform_accounts', [ $this->assertDatabaseHas('platform_accounts', [
'platform_instance_id' => $instance->id,
'username' => 'testuser', 'username' => 'testuser',
'is_active' => true 'is_active' => true
]); ]);
$this->assertEquals($instance->id, $account->platformInstance->id); $this->assertEquals('testuser', $account->username);
} }
public function test_platform_channel_model_creates_successfully(): void public function test_platform_channel_model_creates_successfully(): void
{ {
$language = Language::factory()->create(); $language = Language::factory()->create();
$account = PlatformAccount::factory()->create(); $instance = PlatformInstance::factory()->create();
$channel = PlatformChannel::factory()->create([ $channel = PlatformChannel::factory()->create([
'platform_account_id' => $account->id, 'platform_instance_id' => $instance->id,
'language_id' => $language->id, 'language_id' => $language->id,
'name' => 'Test Channel', 'name' => 'Test Channel',
'is_active' => true 'is_active' => true
]); ]);
$this->assertDatabaseHas('platform_channels', [ $this->assertDatabaseHas('platform_channels', [
'platform_account_id' => $account->id, 'platform_instance_id' => $instance->id,
'language_id' => $language->id, 'language_id' => $language->id,
'name' => 'Test Channel', 'name' => 'Test Channel',
'is_active' => true 'is_active' => true
]); ]);
$this->assertEquals($account->id, $channel->platformAccount->id); $this->assertEquals($instance->id, $channel->platformInstance->id);
$this->assertEquals($language->id, $channel->language->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($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 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 public function test_platform_channel_post_model_creates_successfully(): void
{ {
$channel = PlatformChannel::factory()->create();
$post = PlatformChannelPost::create([ $post = PlatformChannelPost::create([
'platform_channel_id' => $channel->id, 'platform' => 'lemmy',
'channel_id' => 'technology',
'post_id' => 'external-post-123', 'post_id' => 'external-post-123',
'title' => 'Test Post', 'title' => 'Test Post',
'content' => 'Test content',
'url' => 'https://example.com/post', 'url' => 'https://example.com/post',
'created_at' => now(), 'posted_at' => now()
'updated_at' => now()
]); ]);
$this->assertDatabaseHas('platform_channel_posts', [ $this->assertDatabaseHas('platform_channel_posts', [
'platform_channel_id' => $channel->id, 'platform' => 'lemmy',
'channel_id' => 'technology',
'post_id' => 'external-post-123', 'post_id' => 'external-post-123',
'title' => 'Test Post' '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 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 public function test_setting_model_creates_successfully(): void
{ {
$setting = Setting::factory()->create([ $setting = Setting::create([
'key' => 'test_setting', 'key' => 'test_setting',
'value' => 'test_value' 'value' => 'test_value'
]); ]);
@ -277,40 +271,19 @@ public function test_feed_articles_relationship(): void
} }
} }
public function test_article_publications_relationship(): void public function test_platform_account_channels_many_to_many_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
{ {
$account = PlatformAccount::factory()->create(); $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(); $channel = PlatformChannel::factory()->create();
$routes = Route::factory()->count(2)->create(['platform_channel_id' => $channel->id]);
$this->assertCount(2, $channel->routes);
foreach ($routes as $route) { // Test the pivot table relationship
$this->assertTrue($channel->routes->contains($route)); $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 public function test_language_platform_instances_relationship(): void

View file

@ -7,8 +7,8 @@
use App\Events\ExceptionLogged; use App\Events\ExceptionLogged;
use App\Events\ExceptionOccurred; use App\Events\ExceptionOccurred;
use App\Events\NewArticleFetched; use App\Events\NewArticleFetched;
use App\Jobs\ArticleDiscoveryJob;
use App\Jobs\ArticleDiscoveryForFeedJob; use App\Jobs\ArticleDiscoveryForFeedJob;
use App\Jobs\ArticleDiscoveryJob;
use App\Jobs\PublishToLemmyJob; use App\Jobs\PublishToLemmyJob;
use App\Jobs\SyncChannelPostsJob; use App\Jobs\SyncChannelPostsJob;
use App\Listeners\LogExceptionToDatabase; use App\Listeners\LogExceptionToDatabase;
@ -17,8 +17,8 @@
use App\Listeners\ValidateArticleListener; use App\Listeners\ValidateArticleListener;
use App\Models\Article; use App\Models\Article;
use App\Models\Feed; use App\Models\Feed;
use App\Models\PlatformChannel;
use App\Models\Log; use App\Models\Log;
use App\Models\PlatformChannel;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Queue; use Illuminate\Support\Facades\Queue;
@ -31,12 +31,12 @@ class JobsAndEventsTest extends TestCase
public function test_article_discovery_job_processes_successfully(): void public function test_article_discovery_job_processes_successfully(): void
{ {
Queue::fake(); Queue::fake();
$feed = Feed::factory()->create(['is_active' => true]); $feed = Feed::factory()->create(['is_active' => true]);
$job = new ArticleDiscoveryJob(); $job = new ArticleDiscoveryJob();
$job->handle(); $job->handle();
// Should dispatch individual feed jobs // Should dispatch individual feed jobs
Queue::assertPushed(ArticleDiscoveryForFeedJob::class); 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 public function test_article_discovery_for_feed_job_processes_feed(): void
{ {
Event::fake(); Event::fake();
$feed = Feed::factory()->create([ $feed = Feed::factory()->create([
'url' => 'https://example.com/feed', 'url' => 'https://example.com/feed',
'is_active' => true 'is_active' => true
]); ]);
$job = new ArticleDiscoveryForFeedJob($feed); // Mock the ArticleFetcher service in the container
$mockFetcher = \Mockery::mock(\App\Services\Article\ArticleFetcher::class);
// Mock the ArticleFetcher to return created articles
$mockFetcher = \Mockery::mock('overload:' . \App\Services\Article\ArticleFetcher::class);
$article1 = Article::factory()->create(['url' => 'https://example.com/article1', 'feed_id' => $feed->id]); $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]); $article2 = Article::factory()->create(['url' => 'https://example.com/article2', 'feed_id' => $feed->id]);
$mockFetcher->shouldReceive('getArticlesFromFeed') $mockFetcher->shouldReceive('getArticlesFromFeed')
->with($feed) ->with($feed)
->andReturn(collect([$article1, $article2])); ->andReturn(collect([$article1, $article2]));
$this->app->instance(\App\Services\Article\ArticleFetcher::class, $mockFetcher);
$job = new ArticleDiscoveryForFeedJob($feed);
$job->handle(); $job->handle();
// Should have articles in database // Should have articles in database (existing articles created by factory)
$this->assertCount(2, Article::all()); $this->assertCount(2, Article::all());
// Note: Events are not fired by ArticleDiscoveryForFeedJob directly // Note: Events are not fired by ArticleDiscoveryForFeedJob directly
// They would be fired by the Article model when created // 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'); $key = config('app.key');
$cipher = config('app.cipher'); $cipher = config('app.cipher');
$this->assertNotNull($key, 'APP_KEY should be set'); $this->assertNotNull($key, 'APP_KEY should be set');
// The supported method expects the raw key, not the base64: prefixed version // The supported method expects the raw key, not the base64: prefixed version
$rawKey = base64_decode(substr($key, 7)); // Remove 'base64:' prefix and decode $rawKey = base64_decode(substr($key, 7)); // Remove 'base64:' prefix and decode
$this->assertTrue(app('encrypter')->supported($rawKey, $cipher), 'Encryption should be supported'); $this->assertTrue(app('encrypter')->supported($rawKey, $cipher), 'Encryption should be supported');
$channel = PlatformChannel::factory()->create([ $channel = PlatformChannel::factory()->create([
'channel_id' => '' // Empty string to trigger getCommunityId call 'channel_id' => '' // Empty string to trigger getCommunityId call
]); ]);
// Create platform account with proper factory // Create platform account with proper factory
$account = \App\Models\PlatformAccount::factory()->create([ $account = \App\Models\PlatformAccount::factory()->create([
'is_active' => true, 'is_active' => true,
@ -90,15 +91,15 @@ public function test_sync_channel_posts_job_processes_successfully(): void
'platform' => 'lemmy', 'platform' => 'lemmy',
'instance_url' => 'https://lemmy.example.com' 'instance_url' => 'https://lemmy.example.com'
]); ]);
// Attach the account to the channel with active status // Attach the account to the channel with active status
$channel->platformAccounts()->attach($account->id, [ $channel->platformAccounts()->attach($account->id, [
'is_active' => true, 'is_active' => true,
'priority' => 1 'priority' => 1
]); ]);
$job = new SyncChannelPostsJob($channel); $job = new SyncChannelPostsJob($channel);
// Mock the LemmyApiService class // Mock the LemmyApiService class
$mockApi = \Mockery::mock('overload:' . \App\Modules\Lemmy\Services\LemmyApiService::class); $mockApi = \Mockery::mock('overload:' . \App\Modules\Lemmy\Services\LemmyApiService::class);
$mockApi->shouldReceive('login') $mockApi->shouldReceive('login')
@ -111,9 +112,9 @@ public function test_sync_channel_posts_job_processes_successfully(): void
$mockApi->shouldReceive('syncChannelPosts') $mockApi->shouldReceive('syncChannelPosts')
->once() ->once()
->andReturn(true); ->andReturn(true);
$job->handle(); $job->handle();
$this->assertTrue(true); // If we get here without exception, test passes $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 public function test_publish_to_lemmy_job_has_correct_configuration(): void
{ {
$article = Article::factory()->create(); $article = Article::factory()->create();
$job = new PublishToLemmyJob($article); $job = new PublishToLemmyJob($article);
$this->assertEquals('lemmy-posts', $job->queue); $this->assertEquals('lemmy-posts', $job->queue);
$this->assertInstanceOf(PublishToLemmyJob::class, $job); $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 public function test_new_article_fetched_event_is_dispatched(): void
{ {
Event::fake(); Event::fake();
$feed = Feed::factory()->create(); $feed = Feed::factory()->create();
$article = Article::factory()->create(['feed_id' => $feed->id]); $article = Article::factory()->create(['feed_id' => $feed->id]);
event(new NewArticleFetched($article)); event(new NewArticleFetched($article));
Event::assertDispatched(NewArticleFetched::class, function (NewArticleFetched $event) use ($article) { Event::assertDispatched(NewArticleFetched::class, function (NewArticleFetched $event) use ($article) {
return $event->article->id === $article->id; 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 public function test_article_approved_event_is_dispatched(): void
{ {
Event::fake(); Event::fake();
$article = Article::factory()->create(); $article = Article::factory()->create();
event(new ArticleApproved($article)); event(new ArticleApproved($article));
Event::assertDispatched(ArticleApproved::class, function (ArticleApproved $event) use ($article) { Event::assertDispatched(ArticleApproved::class, function (ArticleApproved $event) use ($article) {
return $event->article->id === $article->id; 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 public function test_article_ready_to_publish_event_is_dispatched(): void
{ {
Event::fake(); Event::fake();
$article = Article::factory()->create(); $article = Article::factory()->create();
event(new ArticleReadyToPublish($article)); event(new ArticleReadyToPublish($article));
Event::assertDispatched(ArticleReadyToPublish::class, function (ArticleReadyToPublish $event) use ($article) { Event::assertDispatched(ArticleReadyToPublish::class, function (ArticleReadyToPublish $event) use ($article) {
return $event->article->id === $article->id; 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 public function test_exception_occurred_event_is_dispatched(): void
{ {
Event::fake(); Event::fake();
$exception = new \Exception('Test exception'); $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) { Event::assertDispatched(ExceptionOccurred::class, function (ExceptionOccurred $event) {
return $event->exception->getMessage() === 'Test exception'; 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 public function test_exception_logged_event_is_dispatched(): void
{ {
Event::fake(); Event::fake();
$log = Log::factory()->create([ $log = Log::factory()->create([
'level' => 'error', 'level' => 'error',
'message' => 'Test error', 'message' => 'Test error',
'context' => json_encode(['key' => 'value']) 'context' => json_encode(['key' => 'value'])
]); ]);
event(new ExceptionLogged($log)); event(new ExceptionLogged($log));
Event::assertDispatched(ExceptionLogged::class, function (ExceptionLogged $event) use ($log) { Event::assertDispatched(ExceptionLogged::class, function (ExceptionLogged $event) use ($log) {
return $event->log->message === 'Test error'; 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 public function test_validate_article_listener_processes_new_article(): void
{ {
Event::fake([ArticleReadyToPublish::class]); Event::fake([ArticleReadyToPublish::class]);
$feed = Feed::factory()->create(); $feed = Feed::factory()->create();
$article = Article::factory()->create([ $article = Article::factory()->create([
'feed_id' => $feed->id, 'feed_id' => $feed->id,
'is_valid' => null, 'is_valid' => null,
'validated_at' => null 'validated_at' => null
]); ]);
// Mock ArticleFetcher to return valid article data // Mock ArticleFetcher to return valid article data
$mockFetcher = \Mockery::mock('alias:ArticleFetcher2'); $mockFetcher = \Mockery::mock('alias:ArticleFetcher2');
$this->app->instance(\App\Services\Article\ArticleFetcher::class, $mockFetcher); $this->app->instance(\App\Services\Article\ArticleFetcher::class, $mockFetcher);
@ -217,12 +218,12 @@ public function test_validate_article_listener_processes_new_article(): void
->andReturn([ ->andReturn([
'full_article' => 'Test article content' 'full_article' => 'Test article content'
]); ]);
$listener = new ValidateArticleListener(); $listener = new ValidateArticleListener();
$event = new NewArticleFetched($article); $event = new NewArticleFetched($article);
$listener->handle($event); $listener->handle($event);
$article->refresh(); $article->refresh();
$this->assertNotNull($article->validated_at); $this->assertNotNull($article->validated_at);
$this->assertNotNull($article->is_valid); $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 public function test_publish_approved_article_listener_queues_job(): void
{ {
Event::fake(); Event::fake();
$article = Article::factory()->create([ $article = Article::factory()->create([
'approval_status' => 'approved', 'approval_status' => 'approved',
'is_valid' => true, 'is_valid' => true,
'validated_at' => now() 'validated_at' => now()
]); ]);
$listener = new PublishApprovedArticle(); $listener = new PublishApprovedArticle();
$event = new ArticleApproved($article); $event = new ArticleApproved($article);
$listener->handle($event); $listener->handle($event);
Event::assertDispatched(ArticleReadyToPublish::class); Event::assertDispatched(ArticleReadyToPublish::class);
} }
public function test_publish_article_listener_queues_publish_job(): void public function test_publish_article_listener_queues_publish_job(): void
{ {
Queue::fake(); Queue::fake();
$article = Article::factory()->create([ $article = Article::factory()->create([
'is_valid' => true, 'is_valid' => true,
'validated_at' => now() 'validated_at' => now()
]); ]);
$listener = new PublishArticle(); $listener = new PublishArticle();
$event = new ArticleReadyToPublish($article); $event = new ArticleReadyToPublish($article);
$listener->handle($event); $listener->handle($event);
Queue::assertPushed(PublishToLemmyJob::class); Queue::assertPushed(PublishToLemmyJob::class);
} }
@ -270,21 +271,21 @@ public function test_log_exception_to_database_listener_creates_log(): void
'message' => 'Test exception message', 'message' => 'Test exception message',
'context' => json_encode(['error' => 'details']) 'context' => json_encode(['error' => 'details'])
]); ]);
$listener = new LogExceptionToDatabase(); $listener = new LogExceptionToDatabase();
$exception = new \Exception('Test exception message'); $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); $listener->handle($event);
$this->assertDatabaseHas('logs', [ $this->assertDatabaseHas('logs', [
'level' => 'error', 'level' => 'error',
'message' => 'Test exception message' 'message' => 'Test exception message'
]); ]);
$savedLog = Log::where('message', 'Test exception message')->first(); $savedLog = Log::where('message', 'Test exception message')->first();
$this->assertNotNull($savedLog); $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 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 // Test that events are properly bound to listeners
$listeners = Event::getListeners(NewArticleFetched::class); $listeners = Event::getListeners(NewArticleFetched::class);
$this->assertNotEmpty($listeners); $this->assertNotEmpty($listeners);
$listeners = Event::getListeners(ArticleApproved::class); $listeners = Event::getListeners(ArticleApproved::class);
$this->assertNotEmpty($listeners); $this->assertNotEmpty($listeners);
$listeners = Event::getListeners(ArticleReadyToPublish::class); $listeners = Event::getListeners(ArticleReadyToPublish::class);
$this->assertNotEmpty($listeners); $this->assertNotEmpty($listeners);
$listeners = Event::getListeners(ExceptionOccurred::class); $listeners = Event::getListeners(ExceptionOccurred::class);
$this->assertNotEmpty($listeners); $this->assertNotEmpty($listeners);
} }
@ -306,9 +307,9 @@ public function test_event_listener_registration_works(): void
public function test_job_retry_configuration(): void public function test_job_retry_configuration(): void
{ {
$article = Article::factory()->create(); $article = Article::factory()->create();
$job = new PublishToLemmyJob($article); $job = new PublishToLemmyJob($article);
// Test that job has retry configuration // Test that job has retry configuration
$this->assertObjectHasProperty('tries', $job); $this->assertObjectHasProperty('tries', $job);
$this->assertObjectHasProperty('backoff', $job); $this->assertObjectHasProperty('backoff', $job);
@ -319,12 +320,12 @@ public function test_job_queue_configuration(): void
$feed = Feed::factory()->create(); $feed = Feed::factory()->create();
$channel = PlatformChannel::factory()->create(); $channel = PlatformChannel::factory()->create();
$article = Article::factory()->create(); $article = Article::factory()->create();
$discoveryJob = new ArticleDiscoveryJob(); $discoveryJob = new ArticleDiscoveryJob();
$feedJob = new ArticleDiscoveryForFeedJob($feed); $feedJob = new ArticleDiscoveryForFeedJob($feed);
$publishJob = new PublishToLemmyJob($article); $publishJob = new PublishToLemmyJob($article);
$syncJob = new SyncChannelPostsJob($channel); $syncJob = new SyncChannelPostsJob($channel);
// Test queue assignments // Test queue assignments
$this->assertEquals('feed-discovery', $discoveryJob->queue ?? 'default'); $this->assertEquals('feed-discovery', $discoveryJob->queue ?? 'default');
$this->assertEquals('feed-discovery', $feedJob->queue ?? 'discovery'); $this->assertEquals('feed-discovery', $feedJob->queue ?? 'discovery');
@ -337,4 +338,4 @@ protected function tearDown(): void
\Mockery::close(); \Mockery::close();
parent::tearDown(); parent::tearDown();
} }
} }