diff --git a/app/Actions/CreateChannelAction.php b/app/Actions/CreateChannelAction.php new file mode 100644 index 0000000..4f99eeb --- /dev/null +++ b/app/Actions/CreateChannelAction.php @@ -0,0 +1,42 @@ +url) + ->where('is_active', true) + ->get(); + + if ($activeAccounts->isEmpty()) { + throw new \RuntimeException('No active platform accounts found for this instance. Please create a platform account first.'); + } + + $channel = PlatformChannel::create([ + 'platform_instance_id' => $platformInstanceId, + 'channel_id' => $name, + 'name' => $name, + 'display_name' => ucfirst($name), + 'description' => $description, + 'language_id' => $languageId, + 'is_active' => true, + ]); + + $channel->platformAccounts()->attach($activeAccounts->first()->id, [ + 'is_active' => true, + 'priority' => 1, + 'created_at' => now(), + 'updated_at' => now(), + ]); + + return $channel->load('platformAccounts'); + } +} diff --git a/app/Actions/CreateFeedAction.php b/app/Actions/CreateFeedAction.php new file mode 100644 index 0000000..457b75f --- /dev/null +++ b/app/Actions/CreateFeedAction.php @@ -0,0 +1,35 @@ +short_code; + + $url = config("feed.providers.{$provider}.languages.{$langCode}.url"); + + if (!$url) { + throw new \InvalidArgumentException("Invalid provider and language combination: {$provider}/{$langCode}"); + } + + $providerConfig = config("feed.providers.{$provider}"); + + return Feed::firstOrCreate( + ['url' => $url], + [ + 'name' => $name, + 'type' => $providerConfig['type'] ?? 'website', + 'provider' => $provider, + 'language_id' => $languageId, + 'description' => $description, + 'is_active' => true, + ] + ); + } +} diff --git a/app/Actions/CreatePlatformAccountAction.php b/app/Actions/CreatePlatformAccountAction.php new file mode 100644 index 0000000..f40b3c3 --- /dev/null +++ b/app/Actions/CreatePlatformAccountAction.php @@ -0,0 +1,50 @@ +lemmyAuthService->authenticate($fullInstanceUrl, $username, $password); + + $platformInstance = PlatformInstance::firstOrCreate([ + 'url' => $fullInstanceUrl, + 'platform' => $platform, + ], [ + 'name' => ucfirst($instanceDomain), + 'is_active' => true, + ]); + + return PlatformAccount::create([ + 'platform' => $platform, + 'instance_url' => $fullInstanceUrl, + 'username' => $username, + 'password' => $password, + 'settings' => [ + 'display_name' => $authResponse['person_view']['person']['display_name'] ?? null, + 'description' => $authResponse['person_view']['person']['bio'] ?? null, + 'person_id' => $authResponse['person_view']['person']['id'] ?? null, + 'platform_instance_id' => $platformInstance->id, + 'api_token' => $authResponse['jwt'] ?? null, + ], + 'is_active' => true, + 'status' => 'active', + ]); + } +} diff --git a/app/Actions/CreateRouteAction.php b/app/Actions/CreateRouteAction.php new file mode 100644 index 0000000..56711e9 --- /dev/null +++ b/app/Actions/CreateRouteAction.php @@ -0,0 +1,18 @@ + $feedId, + 'platform_channel_id' => $platformChannelId, + 'priority' => $priority, + 'is_active' => $isActive, + ]); + } +} diff --git a/tests/Unit/Actions/CreateChannelActionTest.php b/tests/Unit/Actions/CreateChannelActionTest.php new file mode 100644 index 0000000..f100a95 --- /dev/null +++ b/tests/Unit/Actions/CreateChannelActionTest.php @@ -0,0 +1,86 @@ +action = new CreateChannelAction(); + } + + public function test_creates_channel_and_attaches_account(): void + { + $instance = PlatformInstance::factory()->create(['url' => 'https://lemmy.world']); + $account = PlatformAccount::factory()->create([ + 'instance_url' => 'https://lemmy.world', + 'is_active' => true, + ]); + $language = Language::factory()->create(); + + $channel = $this->action->execute('test_community', $instance->id, $language->id, 'A description'); + + $this->assertInstanceOf(PlatformChannel::class, $channel); + $this->assertEquals('test_community', $channel->name); + $this->assertEquals('test_community', $channel->channel_id); + $this->assertEquals('Test_community', $channel->display_name); + $this->assertEquals($instance->id, $channel->platform_instance_id); + $this->assertEquals($language->id, $channel->language_id); + $this->assertEquals('A description', $channel->description); + $this->assertTrue($channel->is_active); + + // Verify account is attached + $this->assertTrue($channel->platformAccounts->contains($account)); + $this->assertEquals(1, $channel->platformAccounts->first()->pivot->priority); + } + + public function test_creates_channel_without_language(): void + { + $instance = PlatformInstance::factory()->create(['url' => 'https://lemmy.world']); + PlatformAccount::factory()->create([ + 'instance_url' => 'https://lemmy.world', + 'is_active' => true, + ]); + + $channel = $this->action->execute('test_community', $instance->id); + + $this->assertNull($channel->language_id); + } + + public function test_fails_when_no_active_accounts(): void + { + $instance = PlatformInstance::factory()->create(['url' => 'https://lemmy.world']); + // Create inactive account + PlatformAccount::factory()->create([ + 'instance_url' => 'https://lemmy.world', + 'is_active' => false, + ]); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('No active platform accounts found for this instance'); + + $this->action->execute('test_community', $instance->id); + } + + public function test_fails_when_no_accounts_at_all(): void + { + $instance = PlatformInstance::factory()->create(); + + $this->expectException(\RuntimeException::class); + + $this->action->execute('test_community', $instance->id); + } +} diff --git a/tests/Unit/Actions/CreateFeedActionTest.php b/tests/Unit/Actions/CreateFeedActionTest.php new file mode 100644 index 0000000..072bdab --- /dev/null +++ b/tests/Unit/Actions/CreateFeedActionTest.php @@ -0,0 +1,79 @@ +action = new CreateFeedAction(); + } + + public function test_creates_vrt_feed_with_correct_url(): void + { + $language = Language::factory()->create(['short_code' => 'en', 'is_active' => true]); + + $feed = $this->action->execute('VRT News', 'vrt', $language->id, 'Test description'); + + $this->assertInstanceOf(Feed::class, $feed); + $this->assertEquals('VRT News', $feed->name); + $this->assertEquals('https://www.vrt.be/vrtnws/en/', $feed->url); + $this->assertEquals('website', $feed->type); + $this->assertEquals('vrt', $feed->provider); + $this->assertEquals($language->id, $feed->language_id); + $this->assertEquals('Test description', $feed->description); + $this->assertTrue($feed->is_active); + } + + public function test_creates_belga_feed_with_correct_url(): void + { + $language = Language::factory()->create(['short_code' => 'en', 'is_active' => true]); + + $feed = $this->action->execute('Belga News', 'belga', $language->id); + + $this->assertEquals('https://www.belganewsagency.eu/', $feed->url); + $this->assertEquals('rss', $feed->type); + $this->assertEquals('belga', $feed->provider); + $this->assertNull($feed->description); + } + + public function test_creates_vrt_feed_with_dutch_language(): void + { + $language = Language::factory()->create(['short_code' => 'nl', 'is_active' => true]); + + $feed = $this->action->execute('VRT Nieuws', 'vrt', $language->id); + + $this->assertEquals('https://www.vrt.be/vrtnws/nl/', $feed->url); + } + + public function test_returns_existing_feed_for_duplicate_url(): void + { + $language = Language::factory()->create(['short_code' => 'en', 'is_active' => true]); + + $first = $this->action->execute('VRT News', 'vrt', $language->id); + $second = $this->action->execute('VRT News Duplicate', 'vrt', $language->id); + + $this->assertEquals($first->id, $second->id); + $this->assertEquals(1, Feed::count()); + } + + public function test_throws_exception_for_invalid_provider_language_combination(): void + { + $language = Language::factory()->create(['short_code' => 'fr', 'is_active' => true]); + + $this->expectException(\InvalidArgumentException::class); + + $this->action->execute('Feed', 'belga', $language->id); + } +} diff --git a/tests/Unit/Actions/CreatePlatformAccountActionTest.php b/tests/Unit/Actions/CreatePlatformAccountActionTest.php new file mode 100644 index 0000000..765e540 --- /dev/null +++ b/tests/Unit/Actions/CreatePlatformAccountActionTest.php @@ -0,0 +1,103 @@ +lemmyAuthService = Mockery::mock(LemmyAuthService::class); + $this->action = new CreatePlatformAccountAction($this->lemmyAuthService); + } + + public function test_creates_platform_account_with_new_instance(): void + { + $this->lemmyAuthService + ->shouldReceive('authenticate') + ->once() + ->with('https://lemmy.world', 'testuser', 'testpass') + ->andReturn([ + 'jwt' => 'test-jwt-token', + 'person_view' => [ + 'person' => [ + 'id' => 42, + 'display_name' => 'Test User', + 'bio' => 'A test bio', + ], + ], + ]); + + $account = $this->action->execute('lemmy.world', 'testuser', 'testpass'); + + $this->assertInstanceOf(PlatformAccount::class, $account); + $this->assertEquals('testuser', $account->username); + $this->assertEquals('https://lemmy.world', $account->instance_url); + $this->assertEquals('lemmy', $account->platform->value); + $this->assertTrue($account->is_active); + $this->assertEquals('active', $account->status); + $this->assertEquals(42, $account->settings['person_id']); + $this->assertEquals('Test User', $account->settings['display_name']); + $this->assertEquals('A test bio', $account->settings['description']); + $this->assertEquals('test-jwt-token', $account->settings['api_token']); + + $this->assertDatabaseHas('platform_instances', [ + 'url' => 'https://lemmy.world', + 'platform' => 'lemmy', + ]); + } + + public function test_reuses_existing_platform_instance(): void + { + $existingInstance = PlatformInstance::factory()->create([ + 'url' => 'https://lemmy.world', + 'platform' => 'lemmy', + 'name' => 'Existing Name', + ]); + + $this->lemmyAuthService + ->shouldReceive('authenticate') + ->once() + ->andReturn([ + 'jwt' => 'token', + 'person_view' => ['person' => ['id' => 1, 'display_name' => null, 'bio' => null]], + ]); + + $account = $this->action->execute('lemmy.world', 'user', 'pass'); + + $this->assertEquals($existingInstance->id, $account->settings['platform_instance_id']); + $this->assertEquals(1, PlatformInstance::where('url', 'https://lemmy.world')->count()); + } + + public function test_propagates_auth_exception(): void + { + $this->lemmyAuthService + ->shouldReceive('authenticate') + ->once() + ->andThrow(new PlatformAuthException(\App\Enums\PlatformEnum::LEMMY, 'Invalid credentials')); + + try { + $this->action->execute('lemmy.world', 'baduser', 'badpass'); + $this->fail('Expected PlatformAuthException was not thrown'); + } catch (PlatformAuthException) { + // No instance or account should be created on auth failure + $this->assertDatabaseCount('platform_instances', 0); + $this->assertDatabaseCount('platform_accounts', 0); + } + } +} diff --git a/tests/Unit/Actions/CreateRouteActionTest.php b/tests/Unit/Actions/CreateRouteActionTest.php new file mode 100644 index 0000000..bdc0dc9 --- /dev/null +++ b/tests/Unit/Actions/CreateRouteActionTest.php @@ -0,0 +1,62 @@ +action = new CreateRouteAction(); + } + + public function test_creates_route_with_defaults(): void + { + $language = Language::factory()->create(); + $feed = Feed::factory()->language($language)->create(); + $channel = PlatformChannel::factory()->create(); + + $route = $this->action->execute($feed->id, $channel->id); + + $this->assertInstanceOf(Route::class, $route); + $this->assertEquals($feed->id, $route->feed_id); + $this->assertEquals($channel->id, $route->platform_channel_id); + $this->assertEquals(0, $route->priority); + $this->assertTrue($route->is_active); + } + + public function test_creates_route_with_custom_priority(): void + { + $language = Language::factory()->create(); + $feed = Feed::factory()->language($language)->create(); + $channel = PlatformChannel::factory()->create(); + + $route = $this->action->execute($feed->id, $channel->id, 75); + + $this->assertEquals(75, $route->priority); + $this->assertTrue($route->is_active); + } + + public function test_creates_inactive_route(): void + { + $language = Language::factory()->create(); + $feed = Feed::factory()->language($language)->create(); + $channel = PlatformChannel::factory()->create(); + + $route = $this->action->execute($feed->id, $channel->id, 0, false); + + $this->assertFalse($route->is_active); + } +}