Fix tests

+ remove old routes
This commit is contained in:
myrmidex 2025-08-04 22:10:30 +02:00
parent ae07aa80e0
commit bac0ae12d9
14 changed files with 61 additions and 1083 deletions

View file

@ -1,37 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\Article;
use App\Models\Setting;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class ArticlesController extends Controller
{
public function __invoke(Request $request): View
{
$articles = Article::with('articlePublication')
->orderBy('created_at', 'desc')
->paginate(15);
$publishingApprovalsEnabled = Setting::isPublishingApprovalsEnabled();
return view('pages.articles.index', compact('articles', 'publishingApprovalsEnabled'));
}
public function approve(Article $article): RedirectResponse
{
$article->approve('manual');
return redirect()->back()->with('success', 'Article approved and queued for publishing.');
}
public function reject(Article $article): RedirectResponse
{
$article->reject('manual');
return redirect()->back()->with('success', 'Article rejected.');
}
}

View file

@ -1,81 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\Feed;
use App\Http\Requests\StoreFeedRequest;
use App\Http\Requests\UpdateFeedRequest;
use App\Services\OnboardingRedirectService;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
class FeedsController extends Controller
{
public function index(): View
{
$feeds = Feed::orderBy('is_active', 'desc')
->orderBy('name')
->get();
return view('pages.feeds.index', compact('feeds'));
}
public function create(): View
{
return view('pages.feeds.create');
}
public function store(StoreFeedRequest $request): RedirectResponse
{
$validated = $request->validated();
$validated['is_active'] = $validated['is_active'] ?? true;
Feed::create($validated);
return OnboardingRedirectService::handleRedirect(
$request,
'feeds.index',
'Feed created successfully!'
);
}
public function show(Feed $feed): View
{
return view('pages.feeds.show', compact('feed'));
}
public function edit(Feed $feed): View
{
return view('pages.feeds.edit', compact('feed'));
}
public function update(UpdateFeedRequest $request, Feed $feed): RedirectResponse
{
$validated = $request->validated();
$validated['is_active'] = $validated['is_active'] ?? $feed->is_active;
$feed->update($validated);
return redirect()->route('feeds.index')
->with('success', 'Feed updated successfully!');
}
public function destroy(Feed $feed): RedirectResponse
{
$feed->delete();
return redirect()->route('feeds.index')
->with('success', 'Feed deleted successfully!');
}
public function toggle(Feed $feed): RedirectResponse
{
$newStatus = !$feed->is_active;
$feed->update(['is_active' => $newStatus]);
$status = $newStatus ? 'activated' : 'deactivated';
return redirect()->route('feeds.index')
->with('success', "Feed {$status} successfully!");
}
}

View file

@ -1,17 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\Log;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
class LogsController extends Controller
{
public function __invoke(Request $request): View
{
$logs = Log::orderBy('created_at', 'desc')->paginate(50);
return view('pages.logs.index', compact('logs'));
}
}

View file

@ -1,114 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\Feed;
use App\Models\PlatformAccount;
use App\Models\PlatformChannel;
use App\Models\PlatformInstance;
use App\Services\SystemStatusService;
use App\Services\DashboardStatsService;
use Illuminate\Http\Request;
use Illuminate\View\View;
use Illuminate\Http\RedirectResponse;
class OnboardingController extends Controller
{
public function index(Request $request): View|RedirectResponse
{
// Check if user needs onboarding
if (!$this->needsOnboarding()) {
$systemStatus = resolve(SystemStatusService::class)->getSystemStatus();
$statsService = resolve(DashboardStatsService::class);
$period = $request->get('period', 'today');
$stats = $statsService->getStats($period);
$systemStats = $statsService->getSystemStats();
$availablePeriods = $statsService->getAvailablePeriods();
return view('pages.dashboard', compact(
'systemStatus',
'stats',
'systemStats',
'availablePeriods',
'period'
));
}
return view('onboarding.welcome');
}
public function platform(): View|RedirectResponse
{
if (!$this->needsOnboarding()) {
return redirect()->route('feeds.index');
}
return view('onboarding.platform');
}
public function feed(): View|RedirectResponse
{
if (!$this->needsOnboarding()) {
return redirect()->route('feeds.index');
}
if (!$this->hasPlatformAccount()) {
return redirect()->route('onboarding.platform');
}
return view('onboarding.feed');
}
public function channel(): View|RedirectResponse
{
if (!$this->needsOnboarding()) {
return redirect()->route('feeds.index');
}
if (!$this->hasPlatformAccount()) {
return redirect()->route('onboarding.platform');
}
if (!$this->hasFeed()) {
return redirect()->route('onboarding.feed');
}
return view('onboarding.channel');
}
public function complete(): View|RedirectResponse
{
if (!$this->needsOnboarding()) {
return redirect()->route('feeds.index');
}
if (!$this->hasPlatformAccount() || !$this->hasFeed() || !$this->hasChannel()) {
return redirect()->route('onboarding.index');
}
$systemStatus = resolve(SystemStatusService::class)->getSystemStatus();
return view('onboarding.complete', compact('systemStatus'));
}
private function needsOnboarding(): bool
{
return !$this->hasPlatformAccount() || !$this->hasFeed() || !$this->hasChannel();
}
private function hasPlatformAccount(): bool
{
return PlatformAccount::where('is_active', true)->exists();
}
private function hasFeed(): bool
{
return Feed::where('is_active', true)->exists();
}
private function hasChannel(): bool
{
return PlatformChannel::where('is_active', true)->exists();
}
}

View file

@ -1,106 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\PlatformAccount;
use App\Models\PlatformInstance;
use App\Enums\PlatformEnum;
use Illuminate\Http\Request;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\View as ViewFacade;
class PlatformAccountsController extends Controller
{
public function index(): View
{
$accounts = PlatformAccount::orderBy('platform')->orderBy('created_at', 'desc')->get();
return view('pages.platforms.index', compact('accounts'));
}
public function create(): View
{
return ViewFacade::make('pages.platforms.create');
}
public function store(Request $request): RedirectResponse
{
$validated = $request->validate([
'platform' => 'required|in:lemmy,mastodon,reddit',
'instance_url' => 'required|url',
'username' => 'required|string|max:255',
'password' => 'required|string',
'settings' => 'nullable|array',
]);
// Create or find platform instance
$platformEnum = PlatformEnum::from($validated['platform']);
$instance = PlatformInstance::firstOrCreate([
'platform' => $platformEnum,
'url' => $validated['instance_url'],
], [
'name' => parse_url($validated['instance_url'], PHP_URL_HOST),
'description' => ucfirst($validated['platform']) . ' instance',
'is_active' => true,
]);
$account = PlatformAccount::create($validated);
// If this is the first account for this platform, make it active
if (! PlatformAccount::where('platform', $validated['platform'])->where('is_active', true)->exists()) {
$account->setAsActive();
}
// Check if there's a redirect_to parameter for onboarding flow
$redirectTo = $request->input('redirect_to');
if ($redirectTo) {
return redirect($redirectTo)
->with('success', 'Platform account created successfully!');
}
return redirect()->route('platforms.index')
->with('success', 'Platform account created successfully!');
}
public function edit(PlatformAccount $platformAccount): View
{
return ViewFacade::make('pages.platforms.edit', compact('platformAccount'));
}
public function update(Request $request, PlatformAccount $platformAccount): RedirectResponse
{
$validated = $request->validate([
'instance_url' => 'required|url',
'username' => 'required|string|max:255',
'password' => 'nullable|string',
'settings' => 'nullable|array',
]);
// Don't update password if not provided
if (empty($validated['password'])) {
unset($validated['password']);
}
$platformAccount->update($validated);
return redirect()->route('platforms.index')
->with('success', 'Platform account updated successfully!');
}
public function destroy(PlatformAccount $platformAccount): RedirectResponse
{
$platformAccount->delete();
return redirect()->route('platforms.index')
->with('success', 'Platform account deleted successfully!');
}
public function setActive(PlatformAccount $platformAccount): RedirectResponse
{
$platformAccount->setAsActive();
return redirect()->route('platforms.index')
->with('success', "Set $platformAccount->username@$platformAccount->instance_url as active for {$platformAccount->platform->value}!");
}
}

View file

@ -1,116 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\PlatformChannel;
use App\Models\PlatformInstance;
use Illuminate\Http\Request;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\View as ViewFacade;
class PlatformChannelsController extends Controller
{
public function index(): View
{
$channels = PlatformChannel::with('platformInstance')
->orderBy('platform_instance_id')
->orderBy('name')
->get();
return ViewFacade::make('pages.channels.index', compact('channels'));
}
public function create(): View
{
$instances = PlatformInstance::where('is_active', true)
->orderBy('name')
->get();
return ViewFacade::make('pages.channels.create', compact('instances'));
}
public function store(Request $request): RedirectResponse
{
$validated = $request->validate([
'platform_instance_id' => 'required|exists:platform_instances,id',
'name' => 'required|string|max:255',
'display_name' => 'nullable|string|max:255',
'channel_id' => 'nullable|string|max:255',
'description' => 'nullable|string',
'language_id' => 'required|exists:languages,id',
'is_active' => 'boolean',
]);
// Default is_active to true if not provided
$validated['is_active'] = $validated['is_active'] ?? true;
// Set display_name to name if not provided
$validated['display_name'] = $validated['display_name'] ?? $validated['name'];
PlatformChannel::create($validated);
// Check if there's a redirect_to parameter for onboarding flow
$redirectTo = $request->input('redirect_to');
if ($redirectTo) {
return redirect($redirectTo)
->with('success', 'Channel created successfully!');
}
return redirect()->route('channels.index')
->with('success', 'Channel created successfully!');
}
public function show(PlatformChannel $channel): View
{
$channel->load(['platformInstance', 'feeds']);
return ViewFacade::make('pages.channels.show', compact('channel'));
}
public function edit(PlatformChannel $channel): View
{
$instances = PlatformInstance::where('is_active', true)
->orderBy('name')
->get();
return ViewFacade::make('pages.channels.edit', compact('channel', 'instances'));
}
public function update(Request $request, PlatformChannel $channel): RedirectResponse
{
$validated = $request->validate([
'platform_instance_id' => 'required|exists:platform_instances,id',
'name' => 'required|string|max:255',
'display_name' => 'nullable|string|max:255',
'channel_id' => 'nullable|string|max:255',
'description' => 'nullable|string',
'language_id' => 'required|exists:languages,id',
'is_active' => 'boolean',
]);
$channel->update($validated);
return redirect()->route('channels.index')
->with('success', 'Channel updated successfully!');
}
public function destroy(PlatformChannel $channel): RedirectResponse
{
$channel->delete();
return redirect()->route('channels.index')
->with('success', 'Channel deleted successfully!');
}
public function toggle(PlatformChannel $channel): RedirectResponse
{
$newStatus = !$channel->is_active;
$channel->update(['is_active' => $newStatus]);
$status = $newStatus ? 'activated' : 'deactivated';
return redirect()->route('channels.index')
->with('success', "Channel {$status} successfully!");
}
}

View file

@ -1,173 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\Feed;
use App\Models\Route;
use App\Models\PlatformChannel;
use App\Services\RoutingValidationService;
use App\Exceptions\RoutingMismatchException;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
class RoutingController extends Controller
{
public function index(): View
{
$feeds = Feed::with(['channels.platformInstance'])
->where('is_active', true)
->orderBy('name')
->get();
$channels = PlatformChannel::with(['platformInstance', 'feeds'])
->where('is_active', true)
->orderBy('name')
->get();
return view('pages.routing.index', compact('feeds', 'channels'));
}
public function create(): View
{
$feeds = Feed::where('is_active', true)
->orderBy('name')
->get();
$channels = PlatformChannel::with('platformInstance')
->where('is_active', true)
->orderBy('name')
->get();
return view('pages.routing.create', compact('feeds', 'channels'));
}
public function store(Request $request): RedirectResponse
{
$validated = $request->validate([
'feed_id' => 'required|exists:feeds,id',
'channel_ids' => 'required|array|min:1',
'channel_ids.*' => 'exists:platform_channels,id',
'priority' => 'integer|min:0|max:100',
'filters' => 'nullable|string'
]);
/** @var Feed $feed */
$feed = Feed::findOrFail($validated['feed_id']);
/** @var Collection<int, PlatformChannel> $channels */
$channels = PlatformChannel::findMany($validated['channel_ids']);
$priority = $validated['priority'] ?? 0;
try {
app(RoutingValidationService::class)->validateLanguageCompatibility($feed, $channels);
} catch (RoutingMismatchException $e) {
return redirect()->back()
->withInput()
->withErrors(['language' => $e->getMessage()]);
}
$filters = $this->parseJsonFilters($validated['filters'] ?? null);
// Attach channels to feed
$syncData = [];
foreach ($validated['channel_ids'] as $channelId) {
$syncData[$channelId] = [
'is_active' => true,
'priority' => $priority,
'filters' => $filters,
'created_at' => now(),
'updated_at' => now()
];
}
$feed->channels()->syncWithoutDetaching($syncData);
return redirect()->route('routing.index')
->with('success', 'Feed routing created successfully!');
}
public function edit(Feed $feed, PlatformChannel $channel): View
{
$routing = $feed->channels()
->wherePivot('platform_channel_id', $channel->id)
->first();
if (! $routing) {
abort(404, 'Routing not found');
}
return view('pages.routing.edit', compact('feed', 'channel', 'routing'));
}
public function update(Request $request, Feed $feed, PlatformChannel $channel): RedirectResponse
{
$validated = $request->validate([
'is_active' => 'boolean',
'priority' => 'integer|min:0|max:100',
'filters' => 'nullable|string'
]);
$filters = $this->parseJsonFilters($validated['filters'] ?? null);
$feed->channels()->updateExistingPivot($channel->id, [
'is_active' => $validated['is_active'] ?? true,
'priority' => $validated['priority'] ?? 0,
'filters' => $filters,
'updated_at' => now()
]);
return redirect()->route('routing.index')
->with('success', 'Routing updated successfully!');
}
public function destroy(Feed $feed, PlatformChannel $channel): RedirectResponse
{
$feed->channels()->detach($channel->id);
return redirect()->route('routing.index')
->with('success', 'Routing deleted successfully!');
}
public function toggle(Request $request, Feed $feed, PlatformChannel $channel): RedirectResponse
{
$routing = Route::where('feed_id', $feed->id)
->where('platform_channel_id', $channel->id)
->first();
if (! $routing) {
abort(404, 'Routing not found');
}
$newStatus = ! $routing->is_active;
$feed->channels()->updateExistingPivot($channel->id, [
'is_active' => $newStatus,
'updated_at' => now()
]);
$status = $newStatus ? 'activated' : 'deactivated';
return redirect()->route('routing.index')
->with('success', "Routing {$status} successfully!");
}
/**
* @return array<string, mixed>|null
*/
private function parseJsonFilters(?string $json): ?array
{
if (empty($json)) {
return null;
}
$decoded = json_decode($json, true);
if (json_last_error() === JSON_ERROR_NONE) {
return $decoded;
}
return null;
}
}

View file

@ -1,39 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\Setting;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class SettingsController extends Controller
{
public function index(): View
{
$articleProcessingEnabled = Setting::isArticleProcessingEnabled();
$publishingApprovalsEnabled = Setting::isPublishingApprovalsEnabled();
return view('pages.settings.index', compact('articleProcessingEnabled', 'publishingApprovalsEnabled'));
}
public function update(Request $request): RedirectResponse
{
$request->validate([
'article_processing_enabled' => 'boolean',
'enable_publishing_approvals' => 'boolean',
]);
Setting::setArticleProcessingEnabled($request->boolean('article_processing_enabled'));
Setting::setPublishingApprovalsEnabled($request->boolean('enable_publishing_approvals'));
// If redirected from onboarding, go to dashboard
if ($request->get('from') === 'onboarding') {
return redirect()->route('onboarding.index')
->with('success', 'System activated successfully! Welcome to Lemmy Poster.');
}
return redirect()->route('settings.index')
->with('success', 'Settings updated successfully.');
}
}

View file

@ -2,6 +2,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Carbon;
@ -19,6 +20,8 @@
*/
class Keyword extends Model
{
use HasFactory;
protected $fillable = [
'feed_id',
'platform_channel_id',

View file

@ -12,24 +12,40 @@ class KeywordFactory extends Factory
public function definition(): array
{
return [
'keyword' => $this->faker->word(),
'is_blocked' => $this->faker->boolean(30), // 30% chance of being blocked
'feed_id' => null,
'platform_channel_id' => null,
'keyword' => 'test keyword',
'is_active' => $this->faker->boolean(70), // 70% chance of being active
'created_at' => $this->faker->dateTimeBetween('-1 year', 'now'),
'updated_at' => now(),
];
}
public function blocked(): static
public function forFeed(\App\Models\Feed $feed): static
{
return $this->state(fn (array $attributes) => [
'is_blocked' => true,
'feed_id' => $feed->id,
]);
}
public function allowed(): static
public function forChannel(\App\Models\PlatformChannel $channel): static
{
return $this->state(fn (array $attributes) => [
'is_blocked' => false,
'platform_channel_id' => $channel->id,
]);
}
public function active(): static
{
return $this->state(fn (array $attributes) => [
'is_active' => true,
]);
}
public function inactive(): static
{
return $this->state(fn (array $attributes) => [
'is_active' => false,
]);
}
}

View file

@ -1,45 +1,26 @@
<?php
use App\Http\Controllers\ArticlesController;
use App\Http\Controllers\LogsController;
use App\Http\Controllers\OnboardingController;
use App\Http\Controllers\SettingsController;
use Illuminate\Support\Facades\Route;
// React SPA - catch all routes and serve the React app
Route::get('/{path?}', function () {
return view('app');
})->where('path', '.*')->name('spa');
// Legacy routes (can be removed once fully migrated to React)
/*
// Onboarding routes
Route::get('/', [OnboardingController::class, 'index'])->name('onboarding.index');
Route::get('/onboarding/platform', [OnboardingController::class, 'platform'])->name('onboarding.platform');
Route::get('/onboarding/feed', [OnboardingController::class, 'feed'])->name('onboarding.feed');
Route::get('/onboarding/channel', [OnboardingController::class, 'channel'])->name('onboarding.channel');
Route::get('/onboarding/complete', [OnboardingController::class, 'complete'])->name('onboarding.complete');
Route::get('/articles', ArticlesController::class)->name('articles');
Route::post('/articles/{article}/approve', [ArticlesController::class, 'approve'])->name('articles.approve');
Route::post('/articles/{article}/reject', [ArticlesController::class, 'reject'])->name('articles.reject');
Route::get('/logs', LogsController::class)->name('logs');
Route::get('/settings', [SettingsController::class, 'index'])->name('settings.index');
Route::put('/settings', [SettingsController::class, 'update'])->name('settings.update');
Route::resource('platforms', App\Http\Controllers\PlatformAccountsController::class)->names('platforms');
Route::post('/platforms/{platformAccount}/set-active', [App\Http\Controllers\PlatformAccountsController::class, 'setActive'])->name('platforms.set-active');
Route::resource('channels', App\Http\Controllers\PlatformChannelsController::class)->names('channels');
Route::post('/channels/{channel}/toggle', [App\Http\Controllers\PlatformChannelsController::class, 'toggle'])->name('channels.toggle');
Route::resource('feeds', App\Http\Controllers\FeedsController::class)->names('feeds');
Route::post('/feeds/{feed}/toggle', [App\Http\Controllers\FeedsController::class, 'toggle'])->name('feeds.toggle');
Route::get('/routing', [App\Http\Controllers\RoutingController::class, 'index'])->name('routing.index');
Route::get('/routing/create', [App\Http\Controllers\RoutingController::class, 'create'])->name('routing.create');
Route::post('/routing', [App\Http\Controllers\RoutingController::class, 'store'])->name('routing.store');
Route::get('/routing/{feed}/{channel}/edit', [App\Http\Controllers\RoutingController::class, 'edit'])->name('routing.edit');
Route::put('/routing/{feed}/{channel}', [App\Http\Controllers\RoutingController::class, 'update'])->name('routing.update');
Route::delete('/routing/{feed}/{channel}', [App\Http\Controllers\RoutingController::class, 'destroy'])->name('routing.destroy');
Route::post('/routing/{feed}/{channel}/toggle', [App\Http\Controllers\RoutingController::class, 'toggle'])->name('routing.toggle');
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Note: This Laravel backend now serves as an API for a separate React frontend.
| All UI routes are handled by the React application.
|
*/
// Simple API health check route
Route::get('/health', function () {
return response()->json(['status' => 'ok', 'service' => 'FFR API Backend']);
});
// For any unmatched routes, return a JSON response indicating this is an API backend
Route::fallback(function () {
return response()->json([
'message' => 'This is the FFR API backend. Use /api/v1/* endpoints or check the React frontend.',
'api_base' => '/api/v1'
], 404);
});

View file

@ -1,348 +0,0 @@
<?php
namespace Tests\Feature;
use App\Models\Article;
use App\Models\Feed;
use App\Models\PlatformAccount;
use App\Models\PlatformChannel;
use App\Models\Route;
use App\Models\Setting;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;
class ApiEndpointRegressionTest extends TestCase
{
use RefreshDatabase;
public function test_homepage_loads_successfully(): void
{
$response = $this->get('/');
$response->assertSuccessful();
}
public function test_onboarding_platform_page_loads(): void
{
$response = $this->get('/onboarding/platform');
$response->assertSuccessful();
}
public function test_onboarding_feed_page_loads(): void
{
$response = $this->get('/onboarding/feed');
// Accept both successful response and redirects for onboarding flow
$this->assertTrue(
$response->isSuccessful() || $response->isRedirect(),
"Expected successful response or redirect, got status: " . $response->getStatusCode()
);
}
public function test_onboarding_channel_page_loads(): void
{
$response = $this->get('/onboarding/channel');
// Accept both successful response and redirects for onboarding flow
$this->assertTrue(
$response->isSuccessful() || $response->isRedirect(),
"Expected successful response or redirect, got status: " . $response->getStatusCode()
);
}
public function test_onboarding_complete_page_loads(): void
{
$response = $this->get('/onboarding/complete');
$response->assertSuccessful();
}
public function test_articles_page_loads_successfully(): void
{
$response = $this->get('/articles');
$response->assertSuccessful();
}
public function test_articles_approve_endpoint_works(): void
{
Event::fake(); // Disable events to prevent side effects
$feed = Feed::factory()->create();
$article = Article::factory()->create([
'feed_id' => $feed->id,
'approval_status' => 'pending'
]);
// Ensure article exists in database
$this->assertDatabaseHas('articles', [
'id' => $article->id,
'approval_status' => 'pending'
]);
$response = $this->withoutMiddleware()
->post("/articles/{$article->id}/approve");
$response->assertRedirect();
// Check database directly
$this->assertDatabaseHas('articles', [
'id' => $article->id,
'approval_status' => 'approved'
]);
$article->refresh();
$this->assertEquals('approved', $article->approval_status, 'Article status should be approved after calling approve endpoint');
}
public function test_articles_reject_endpoint_works(): void
{
$feed = Feed::factory()->create();
$article = Article::factory()->create([
'feed_id' => $feed->id,
'approval_status' => 'pending'
]);
$response = $this->withoutMiddleware()
->post("/articles/{$article->id}/reject");
$response->assertRedirect();
$article->refresh();
$this->assertEquals('rejected', $article->approval_status);
}
public function test_logs_page_loads_successfully(): void
{
$response = $this->get('/logs');
$response->assertSuccessful();
}
public function test_settings_page_loads_successfully(): void
{
$response = $this->get('/settings');
$response->assertSuccessful();
}
public function test_settings_update_endpoint_works(): void
{
Setting::factory()->create([
'key' => 'test_setting',
'value' => 'old_value'
]);
$response = $this->withoutMiddleware()
->put('/settings', [
'test_setting' => 'new_value'
]);
$response->assertRedirect();
$this->assertEquals('new_value', Setting::where('key', 'test_setting')->first()->value);
}
public function test_platforms_index_loads_successfully(): void
{
$response = $this->get('/platforms');
$response->assertSuccessful();
}
public function test_platforms_create_loads_successfully(): void
{
$response = $this->get('/platforms/create');
$response->assertSuccessful();
}
public function test_platforms_show_loads_successfully(): void
{
$platform = PlatformAccount::factory()->create();
$response = $this->get("/platforms/{$platform->id}");
$response->assertSuccessful();
}
public function test_platforms_edit_loads_successfully(): void
{
$platform = PlatformAccount::factory()->create();
$response = $this->get("/platforms/{$platform->id}/edit");
$response->assertSuccessful();
}
public function test_platforms_set_active_endpoint_works(): void
{
$platform = PlatformAccount::factory()->create(['is_active' => false]);
$response = $this->withoutMiddleware()
->post("/platforms/{$platform->id}/set-active");
$response->assertRedirect();
$platform->refresh();
$this->assertTrue($platform->is_active);
}
public function test_channels_index_loads_successfully(): void
{
$response = $this->get('/channels');
$response->assertSuccessful();
}
public function test_channels_create_loads_successfully(): void
{
$response = $this->get('/channels/create');
$response->assertSuccessful();
}
public function test_channels_show_loads_successfully(): void
{
$channel = PlatformChannel::factory()->create();
$response = $this->get("/channels/{$channel->id}");
$response->assertSuccessful();
}
public function test_channels_edit_loads_successfully(): void
{
$channel = PlatformChannel::factory()->create();
$response = $this->get("/channels/{$channel->id}/edit");
$response->assertSuccessful();
}
public function test_channels_toggle_endpoint_works(): void
{
$channel = PlatformChannel::factory()->create(['is_active' => false]);
$response = $this->withoutMiddleware()
->post("/channels/{$channel->id}/toggle");
$response->assertRedirect();
$channel->refresh();
$this->assertTrue($channel->is_active);
}
public function test_feeds_index_loads_successfully(): void
{
$response = $this->get('/feeds');
$response->assertSuccessful();
}
public function test_feeds_create_loads_successfully(): void
{
$response = $this->get('/feeds/create');
$response->assertSuccessful();
}
public function test_feeds_show_loads_successfully(): void
{
$feed = Feed::factory()->create();
$response = $this->get("/feeds/{$feed->id}");
$response->assertSuccessful();
}
public function test_feeds_edit_loads_successfully(): void
{
$feed = Feed::factory()->create();
$response = $this->get("/feeds/{$feed->id}/edit");
$response->assertSuccessful();
}
public function test_feeds_toggle_endpoint_works(): void
{
$feed = Feed::factory()->create(['is_active' => false]);
$response = $this->withoutMiddleware()
->post("/feeds/{$feed->id}/toggle");
$response->assertRedirect();
$feed->refresh();
$this->assertTrue($feed->is_active);
}
public function test_routing_index_loads_successfully(): void
{
$response = $this->get('/routing');
$response->assertSuccessful();
}
public function test_routing_create_loads_successfully(): void
{
$response = $this->get('/routing/create');
$response->assertSuccessful();
}
public function test_routing_edit_loads_successfully(): void
{
$feed = Feed::factory()->create();
$channel = PlatformChannel::factory()->create();
Route::factory()->create([
'feed_id' => $feed->id,
'platform_channel_id' => $channel->id
]);
$response = $this->get("/routing/{$feed->id}/{$channel->id}/edit");
$response->assertSuccessful();
}
public function test_routing_toggle_endpoint_works(): void
{
$feed = Feed::factory()->create();
$channel = PlatformChannel::factory()->create();
$route = Route::factory()->create([
'feed_id' => $feed->id,
'platform_channel_id' => $channel->id,
'is_active' => false
]);
$response = $this->withoutMiddleware()
->post("/routing/{$feed->id}/{$channel->id}/toggle");
$response->assertRedirect();
$route->refresh();
$this->assertTrue($route->is_active);
}
public function test_all_get_routes_return_successful_status(): void
{
// Create necessary test data
$feed = Feed::factory()->create();
$channel = PlatformChannel::factory()->create();
$platform = PlatformAccount::factory()->create();
Route::factory()->create([
'feed_id' => $feed->id,
'platform_channel_id' => $channel->id
]);
$routes = [
'/',
'/onboarding/platform',
'/onboarding/feed',
'/onboarding/channel',
'/onboarding/complete',
'/articles',
'/logs',
'/settings',
'/platforms',
'/platforms/create',
"/platforms/{$platform->id}",
"/platforms/{$platform->id}/edit",
'/channels',
'/channels/create',
"/channels/{$channel->id}",
"/channels/{$channel->id}/edit",
'/feeds',
'/feeds/create',
"/feeds/{$feed->id}",
"/feeds/{$feed->id}/edit",
'/routing',
'/routing/create',
"/routing/{$feed->id}/{$channel->id}/edit",
];
foreach ($routes as $route) {
$response = $this->get($route);
$this->assertTrue(
$response->isSuccessful(),
"Route {$route} failed with status {$response->getStatusCode()}"
);
}
}
}

View file

@ -218,14 +218,22 @@ public function test_platform_channel_post_model_creates_successfully(): void
public function test_keyword_model_creates_successfully(): void
{
$keyword = Keyword::factory()->create([
$feed = Feed::factory()->create();
$channel = PlatformChannel::factory()->create();
$keyword = Keyword::factory()
->forFeed($feed)
->forChannel($channel)
->create([
'keyword' => 'test keyword',
'is_blocked' => false
'is_active' => true
]);
$this->assertDatabaseHas('keywords', [
'keyword' => 'test keyword',
'is_blocked' => false
'is_active' => true,
'feed_id' => $feed->id,
'platform_channel_id' => $channel->id
]);
}

View file

@ -53,7 +53,7 @@ public function test_article_discovery_for_feed_job_processes_feed(): void
$job = new ArticleDiscoveryForFeedJob($feed);
// Mock the ArticleFetcher to return created articles
$mockFetcher = \Mockery::mock('alias:' . \App\Services\Article\ArticleFetcher::class);
$mockFetcher = \Mockery::mock('overload:' . \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')
@ -210,7 +210,8 @@ public function test_validate_article_listener_processes_new_article(): void
]);
// Mock ArticleFetcher to return valid article data
$mockFetcher = \Mockery::mock('alias:' . \App\Services\Article\ArticleFetcher::class);
$mockFetcher = \Mockery::mock('alias:ArticleFetcher2');
$this->app->instance(\App\Services\Article\ArticleFetcher::class, $mockFetcher);
$mockFetcher->shouldReceive('fetchArticleData')
->with($article)
->andReturn([