Compare commits

..

No commits in common. "6784af2ff6916b503396aeab8550a2e07f3fba3f" and "0b2fc5004be15bc3295a81170f2fc0c0f311201a" have entirely different histories.

168 changed files with 1368 additions and 1522 deletions

View file

@ -11,7 +11,6 @@ public function canParse(string $url): bool;
/**
* Extract article data from HTML
*
* @return array<string, mixed>
*/
public function extractData(string $html): array;

View file

@ -11,7 +11,6 @@ public function canParse(string $url): bool;
/**
* Extract article URLs from homepage HTML
*
* @return array<int, string>
*/
public function extractArticleUrls(string $html): array;

View file

@ -12,5 +12,6 @@ class ExceptionLogged
public function __construct(
public Log $log
) {}
) {
}
}

View file

@ -17,5 +17,6 @@ public function __construct(
public string $message,
/** @var array<string, mixed> */
public array $context = []
) {}
) {
}
}

View file

@ -11,5 +11,7 @@ class NewArticleFetched
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(public Article $article) {}
public function __construct(public Article $article)
{
}
}

View file

@ -4,4 +4,6 @@
use Exception;
class ChannelException extends Exception {}
class ChannelException extends Exception
{
}

View file

@ -11,7 +11,7 @@ class PublishException extends Exception
{
public function __construct(
private readonly Article $article,
private readonly ?PlatformEnum $platform,
private readonly PlatformEnum|null $platform,
?Throwable $previous = null
) {
$message = "Failed to publish article #$article->id";

View file

@ -4,4 +4,6 @@
use Exception;
class RoutingException extends Exception {}
class RoutingException extends Exception
{
}

View file

@ -3,12 +3,13 @@
namespace App\Http\Controllers\Api\V1;
use App\Http\Resources\ArticleResource;
use App\Jobs\ArticleDiscoveryJob;
use App\Models\Article;
use App\Models\Setting;
use App\Jobs\ArticleDiscoveryJob;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
class ArticlesController extends BaseController
{

View file

@ -5,6 +5,7 @@
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;

View file

@ -23,8 +23,6 @@ public function sendResponse(mixed $result, string $message = 'Success', int $co
/**
* Error response method
*
* @param array<string, mixed> $errorMessages
*/
public function sendError(string $error, array $errorMessages = [], int $code = 400): JsonResponse
{
@ -42,8 +40,6 @@ public function sendError(string $error, array $errorMessages = [], int $code =
/**
* Validation error response method
*
* @param array<string, mixed> $errors
*/
public function sendValidationError(array $errors): JsonResponse
{

View file

@ -3,6 +3,9 @@
namespace App\Http\Controllers\Api\V1;
use App\Models\Article;
use App\Models\Feed;
use App\Models\PlatformAccount;
use App\Models\PlatformChannel;
use App\Services\DashboardStatsService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@ -37,6 +40,7 @@ public function stats(Request $request): JsonResponse
'current_period' => $period,
]);
} catch (\Exception $e) {
throw $e;
return $this->sendError('Failed to fetch dashboard stats: ' . $e->getMessage(), [], 500);
}
}

View file

@ -10,8 +10,8 @@
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use InvalidArgumentException;
use Illuminate\Validation\ValidationException;
class FeedsController extends BaseController
{
@ -37,7 +37,7 @@ public function index(Request $request): JsonResponse
'total' => $feeds->total(),
'from' => $feeds->firstItem(),
'to' => $feeds->lastItem(),
],
]
], 'Feeds retrieved successfully.');
}

View file

@ -84,10 +84,8 @@ public function options(): JsonResponse
->get(['id', 'platform_instance_id', 'name', 'display_name', 'description']);
// Get feed providers from config
/** @var array<string, array<string, mixed>> $providers */
$providers = config('feed.providers', []);
$feedProviders = collect($providers)
->filter(fn (array $provider) => $provider['is_active'])
$feedProviders = collect(config('feed.providers', []))
->filter(fn($provider) => $provider['is_active'])
->values();
return $this->sendResponse([

View file

@ -53,7 +53,6 @@ public function store(StorePlatformAccountRequest $request, CreatePlatformAccoun
if (str_contains($e->getMessage(), 'Rate limited by')) {
return $this->sendError($e->getMessage(), [], 429);
}
return $this->sendError('Invalid username or password. Please check your credentials and try again.', [], 422);
} catch (Exception $e) {
return $this->sendError('Unable to connect to the Lemmy instance. Please check the URL and try again.', [], 422);

View file

@ -5,8 +5,8 @@
use App\Actions\CreateChannelAction;
use App\Http\Requests\StorePlatformChannelRequest;
use App\Http\Resources\PlatformChannelResource;
use App\Models\PlatformAccount;
use App\Models\PlatformChannel;
use App\Models\PlatformAccount;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@ -144,7 +144,6 @@ public function attachAccount(PlatformChannel $channel, Request $request): JsonR
'priority' => 'nullable|integer|min:1|max:100',
]);
/** @var PlatformAccount $platformAccount */
$platformAccount = PlatformAccount::findOrFail($validated['platform_account_id']);
// Check if account is already attached

View file

@ -4,7 +4,6 @@
use App\Http\Controllers\Controller;
use Illuminate\Auth\Events\Verified;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\EmailVerificationRequest;
use Illuminate\Http\RedirectResponse;
@ -20,9 +19,7 @@ public function __invoke(EmailVerificationRequest $request): RedirectResponse
}
if ($request->user()->markEmailAsVerified()) {
/** @var MustVerifyEmail $user */
$user = $request->user();
event(new Verified($user));
event(new Verified($request->user()));
}
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');

View file

@ -23,7 +23,7 @@ public function rules(): array
'provider' => "required|in:{$providers}",
'language_id' => 'required|exists:languages,id',
'description' => 'nullable|string',
'is_active' => 'boolean',
'is_active' => 'boolean'
];
}
}

View file

@ -23,7 +23,7 @@ public function rules(): array
'type' => 'required|in:website,rss',
'language_id' => 'required|exists:languages,id',
'description' => 'nullable|string',
'is_active' => 'boolean',
'is_active' => 'boolean'
];
}
}

View file

@ -5,9 +5,6 @@
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @mixin \App\Models\ArticlePublication
*/
class ArticlePublicationResource extends JsonResource
{
/**
@ -20,8 +17,8 @@ public function toArray(Request $request): array
return [
'id' => $this->id,
'article_id' => $this->article_id,
'platform' => $this->platform,
'published_at' => $this->published_at->toISOString(),
'status' => $this->status,
'published_at' => $this->published_at?->toISOString(),
'created_at' => $this->created_at->toISOString(),
'updated_at' => $this->updated_at->toISOString(),
];

View file

@ -6,13 +6,10 @@
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @mixin \App\Models\Article
* @property int $id
*/
class ArticleResource extends JsonResource
{
/**
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
@ -22,8 +19,12 @@ public function toArray(Request $request): array
'title' => $this->title,
'description' => $this->description,
'is_valid' => $this->is_valid,
'is_duplicate' => $this->is_duplicate,
'approval_status' => $this->approval_status,
'publish_status' => $this->publish_status,
'approved_at' => $this->approved_at?->toISOString(),
'approved_by' => $this->approved_by,
'fetched_at' => $this->fetched_at?->toISOString(),
'validated_at' => $this->validated_at?->toISOString(),
'is_published' => $this->relationLoaded('articlePublication') && $this->articlePublication !== null,
'created_at' => $this->created_at->toISOString(),

View file

@ -5,9 +5,6 @@
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @mixin \App\Models\Feed
*/
class FeedResource extends JsonResource
{
/**

View file

@ -5,12 +5,6 @@
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @mixin \App\Models\PlatformAccount
*/
/**
* @mixin \App\Models\PlatformAccount
*/
class PlatformAccountResource extends JsonResource
{
/**

View file

@ -5,9 +5,6 @@
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @mixin \App\Models\PlatformChannel
*/
class PlatformChannelResource extends JsonResource
{
/**

View file

@ -5,9 +5,6 @@
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @mixin \App\Models\PlatformInstance
*/
class PlatformInstanceResource extends JsonResource
{
/**

View file

@ -5,9 +5,6 @@
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @mixin \App\Models\Route
*/
class RouteResource extends JsonResource
{
/**
@ -18,6 +15,7 @@ class RouteResource extends JsonResource
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'feed_id' => $this->feed_id,
'platform_channel_id' => $this->platform_channel_id,
'is_active' => $this->is_active,

View file

@ -25,7 +25,7 @@ public function handle(LogSaver $logSaver, ArticleFetcher $articleFetcher): void
$logSaver->info('Starting feed article fetch', null, [
'feed_id' => $this->feed->id,
'feed_name' => $this->feed->name,
'feed_url' => $this->feed->url,
'feed_url' => $this->feed->url
]);
$articles = $articleFetcher->getArticlesFromFeed($this->feed);
@ -33,7 +33,7 @@ public function handle(LogSaver $logSaver, ArticleFetcher $articleFetcher): void
$logSaver->info('Feed article fetch completed', null, [
'feed_id' => $this->feed->id,
'feed_name' => $this->feed->name,
'articles_count' => $articles->count(),
'articles_count' => $articles->count()
]);
$this->feed->update(['last_fetched_at' => now()]);
@ -56,7 +56,7 @@ public static function dispatchForAllActiveFeeds(): void
$logSaver->info('Dispatched feed discovery job', null, [
'feed_id' => $feed->id,
'feed_name' => $feed->name,
'delay_minutes' => $delayMinutes,
'delay_minutes' => $delayMinutes
]);
});
}

View file

@ -12,7 +12,7 @@
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
class PublishNextArticleJob implements ShouldBeUnique, ShouldQueue
class PublishNextArticleJob implements ShouldQueue, ShouldBeUnique
{
use Queueable;
@ -28,7 +28,6 @@ public function __construct()
/**
* Execute the job.
*
* @throws PublishException
*/
public function handle(ArticleFetcher $articleFetcher, ArticlePublishingService $publishingService): void
@ -57,7 +56,7 @@ public function handle(ArticleFetcher $articleFetcher, ArticlePublishingService
'article_id' => $article->id,
'title' => $article->title,
'url' => $article->url,
'created_at' => $article->created_at,
'created_at' => $article->created_at
]);
// Fetch article data
@ -68,12 +67,12 @@ public function handle(ArticleFetcher $articleFetcher, ArticlePublishingService
logger()->info('Successfully published article', [
'article_id' => $article->id,
'title' => $article->title,
'title' => $article->title
]);
} catch (PublishException $e) {
logger()->error('Failed to publish article', [
'article_id' => $article->id,
'error' => $e->getMessage(),
'error' => $e->getMessage()
]);
throw $e;

View file

@ -15,7 +15,7 @@
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Cache;
class SyncChannelPostsJob implements ShouldBeUnique, ShouldQueue
class SyncChannelPostsJob implements ShouldQueue, ShouldBeUnique
{
use Queueable;
@ -78,7 +78,7 @@ private function syncLemmyChannelPosts(LogSaver $logSaver): void
} catch (Exception $e) {
$logSaver->error('Failed to sync channel posts', $this->channel, [
'error' => $e->getMessage(),
'error' => $e->getMessage()
]);
throw $e;

View file

@ -5,9 +5,9 @@
use App\Events\ExceptionLogged;
use App\Events\ExceptionOccurred;
use App\Models\Log;
class LogExceptionToDatabase
{
public function handle(ExceptionOccurred $event): void
{
// Truncate the message to prevent database errors
@ -24,15 +24,15 @@ public function handle(ExceptionOccurred $event): void
'file' => $event->exception->getFile(),
'line' => $event->exception->getLine(),
'trace' => $event->exception->getTraceAsString(),
...$event->context,
],
...$event->context
]
]);
ExceptionLogged::dispatch($log);
} catch (\Exception $e) {
// Prevent infinite recursion by not logging this exception
// Optionally log to file or other non-database destination
error_log('Failed to log exception to database: '.$e->getMessage());
error_log("Failed to log exception to database: " . $e->getMessage());
}
}
}

View file

@ -41,7 +41,6 @@ public function handle(NewArticleFetched $event): void
'article_id' => $article->id,
'error' => $e->getMessage(),
]);
return;
}

View file

@ -2,9 +2,9 @@
namespace App\Livewire;
use App\Jobs\ArticleDiscoveryJob;
use App\Models\Article;
use App\Models\Setting;
use App\Jobs\ArticleDiscoveryJob;
use Livewire\Component;
use Livewire\WithPagination;
@ -39,7 +39,7 @@ public function refresh(): void
$this->dispatch('refresh-started');
}
public function render(): \Illuminate\Contracts\View\View
public function render()
{
$articles = Article::with(['feed', 'articlePublication'])
->orderBy('created_at', 'desc')

View file

@ -51,7 +51,7 @@ public function detachAccount(int $channelId, int $accountId): void
$channel->platformAccounts()->detach($accountId);
}
public function render(): \Illuminate\Contracts\View\View
public function render()
{
$channels = PlatformChannel::with(['platformInstance', 'platformAccounts'])->orderBy('name')->get();
$allAccounts = PlatformAccount::where('is_active', true)->get();

View file

@ -19,7 +19,7 @@ public function setPeriod(string $period): void
$this->period = $period;
}
public function render(): \Illuminate\Contracts\View\View
public function render()
{
$service = app(DashboardStatsService::class);

View file

@ -14,7 +14,7 @@ public function toggle(int $feedId): void
$feed->save();
}
public function render(): \Illuminate\Contracts\View\View
public function render()
{
$feeds = Feed::orderBy('name')->get();

View file

@ -29,54 +29,36 @@ class Onboarding extends Component
// Platform form
public string $instanceUrl = '';
public string $username = '';
public string $password = '';
/** @var array<string, mixed>|null */
public ?array $existingAccount = null;
// Feed form
public string $feedName = '';
public string $feedProvider = 'vrt';
public ?int $feedLanguageId = null;
public string $feedDescription = '';
// Channel form
public string $channelName = '';
public ?int $platformInstanceId = null;
public ?int $channelLanguageId = null;
public string $channelDescription = '';
// Route form
public ?int $routeFeedId = null;
public ?int $routeChannelId = null;
public int $routePriority = 50;
// State
/** @var array<string, string> */
public array $formErrors = [];
public bool $isLoading = false;
#[\Livewire\Attributes\Locked]
public ?int $previousChannelLanguageId = null;
protected CreatePlatformAccountAction $createPlatformAccountAction;
protected CreateFeedAction $createFeedAction;
protected CreateChannelAction $createChannelAction;
protected CreateRouteAction $createRouteAction;
public function boot(
@ -337,9 +319,6 @@ public function completeOnboarding(): void
/**
* Get language codes that have at least one active provider.
*/
/**
* @return list<string>
*/
public function getAvailableLanguageCodes(): array
{
$providers = config('feed.providers', []);
@ -350,7 +329,7 @@ public function getAvailableLanguageCodes(): array
continue;
}
foreach (array_keys($provider['languages'] ?? []) as $code) {
$languageCodes[(string) $code] = true;
$languageCodes[$code] = true;
}
}
@ -360,9 +339,6 @@ public function getAvailableLanguageCodes(): array
/**
* Get providers available for the current channel language.
*/
/**
* @return array<int, array<string, string>>
*/
public function getProvidersForLanguage(): array
{
if (!$this->channelLanguageId) {
@ -402,11 +378,10 @@ public function getChannelLanguage(): ?Language
if (!$this->channelLanguageId) {
return null;
}
return Language::find($this->channelLanguageId);
}
public function render(): \Illuminate\Contracts\View\View
public function render()
{
// For channel step: only show languages that have providers
$availableCodes = $this->getAvailableLanguageCodes();

View file

@ -11,16 +11,12 @@
class Routes extends Component
{
public bool $showCreateModal = false;
public ?int $editingFeedId = null;
public ?int $editingChannelId = null;
// Create form
public ?int $newFeedId = null;
public ?int $newChannelId = null;
public int $newPriority = 50;
// Edit form
@ -28,7 +24,6 @@ class Routes extends Component
// Keyword management
public string $newKeyword = '';
public bool $showKeywordInput = false;
public function openCreateModal(): void
@ -58,7 +53,6 @@ public function createRoute(): void
if ($exists) {
$this->addError('newFeedId', 'This route already exists.');
return;
}
@ -159,7 +153,7 @@ public function deleteKeyword(int $keywordId): void
Keyword::destroy($keywordId);
}
public function render(): \Illuminate\Contracts\View\View
public function render()
{
$routes = Route::with(['feed', 'platformChannel'])
->orderBy('priority', 'desc')
@ -174,8 +168,7 @@ public function render(): \Illuminate\Contracts\View\View
$routes = $routes->map(function ($route) use ($allKeywords) {
$key = $route->feed_id . '-' . $route->platform_channel_id;
$route->setRelation('keywords', $allKeywords->get($key, collect()));
$route->keywords = $allKeywords->get($key, collect());
return $route;
});

View file

@ -8,13 +8,10 @@
class Settings extends Component
{
public bool $articleProcessingEnabled = true;
public bool $publishingApprovalsEnabled = false;
public int $articlePublishingInterval = 5;
public ?string $successMessage = null;
public ?string $errorMessage = null;
public function mount(): void
@ -63,7 +60,7 @@ public function clearMessages(): void
$this->errorMessage = null;
}
public function render(): \Illuminate\Contracts\View\View
public function render()
{
return view('livewire.settings')->layout('layouts.app');
}

View file

@ -15,20 +15,15 @@
* @method static firstOrCreate(array<string, mixed> $array)
* @method static where(string $string, string $url)
* @method static create(array<string, mixed> $array)
*
* @property int $id
* @property integer $id
* @property int $feed_id
* @property Feed $feed
* @property string $url
* @property string $title
* @property string|null $description
* @property string $approval_status
* @property string $publish_status
* @property bool|null $is_valid
* @property Carbon|null $validated_at
* @property Carbon $created_at
* @property Carbon $updated_at
* @property ArticlePublication|null $articlePublication
* @property ArticlePublication $articlePublication
*/
class Article extends Model
{
@ -84,7 +79,7 @@ public function isRejected(): bool
return $this->approval_status === 'rejected';
}
public function approve(?string $approvedBy = null): void
public function approve(string $approvedBy = null): void
{
$this->update([
'approval_status' => 'approved',
@ -94,7 +89,7 @@ public function approve(?string $approvedBy = null): void
event(new ArticleApproved($this));
}
public function reject(?string $rejectedBy = null): void
public function reject(string $rejectedBy = null): void
{
$this->update([
'approval_status' => 'rejected',

View file

@ -8,16 +8,9 @@
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $id
* @property int $article_id
* @property int $platform_channel_id
* @property string $post_id
* @property string $platform
* @property string $published_by
* @property array<string, mixed>|null $publication_data
* @property \Illuminate\Support\Carbon $published_at
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property integer $article_id
* @property integer $platform_channel_id
* @property integer $post_id
*
* @method static create(array<string, mixed> $array)
*/

View file

@ -24,7 +24,6 @@
* @property Carbon|null $last_fetched_at
* @property Carbon $created_at
* @property Carbon $updated_at
*
* @method static orderBy(string $string, string $string1)
* @method static where(string $string, true $true)
* @method static findOrFail(mixed $feed_id)
@ -33,9 +32,7 @@ class Feed extends Model
{
/** @use HasFactory<FeedFactory> */
use HasFactory;
private const RECENT_FETCH_THRESHOLD_HOURS = 2;
private const DAILY_FETCH_THRESHOLD_HOURS = 24;
protected $fillable = [
@ -47,13 +44,13 @@ class Feed extends Model
'description',
'settings',
'is_active',
'last_fetched_at',
'last_fetched_at'
];
protected $casts = [
'settings' => 'array',
'is_active' => 'boolean',
'last_fetched_at' => 'datetime',
'last_fetched_at' => 'datetime'
];
public function getTypeDisplayAttribute(): string
@ -82,12 +79,12 @@ public function getStatusAttribute(): string
} elseif ($hoursAgo < self::DAILY_FETCH_THRESHOLD_HOURS) {
return "Fetched {$hoursAgo}h ago";
} else {
return 'Fetched '.$this->last_fetched_at->diffForHumans();
return "Fetched " . $this->last_fetched_at->diffForHumans();
}
}
/**
* @return BelongsToMany<PlatformChannel, $this>
* @return BelongsToMany<PlatformChannel, $this, Route>
*/
public function channels(): BelongsToMany
{
@ -97,7 +94,7 @@ public function channels(): BelongsToMany
}
/**
* @return BelongsToMany<PlatformChannel, $this>
* @return BelongsToMany<PlatformChannel, $this, Route>
*/
public function activeChannels(): BelongsToMany
{

View file

@ -2,7 +2,6 @@
namespace App\Models;
use Database\Factories\KeywordFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -21,18 +20,17 @@
*/
class Keyword extends Model
{
/** @use HasFactory<KeywordFactory> */
use HasFactory;
protected $fillable = [
'feed_id',
'platform_channel_id',
'keyword',
'is_active',
'is_active'
];
protected $casts = [
'is_active' => 'boolean',
'is_active' => 'boolean'
];
/**
@ -50,4 +48,5 @@ public function platformChannel(): BelongsTo
{
return $this->belongsTo(PlatformChannel::class);
}
}

View file

@ -17,11 +17,11 @@ class Language extends Model
'short_code',
'name',
'native_name',
'is_active',
'is_active'
];
protected $casts = [
'is_active' => 'boolean',
'is_active' => 'boolean'
];
/**

View file

@ -3,14 +3,12 @@
namespace App\Models;
use App\Enums\LogLevelEnum;
use Database\Factories\LogFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
/**
* @method static create(array<string, mixed> $array)
*
* @method static create(array $array)
* @property LogLevelEnum $level
* @property string $message
* @property array<string, mixed> $context
@ -19,7 +17,6 @@
*/
class Log extends Model
{
/** @use HasFactory<LogFactory> */
use HasFactory;
protected $table = 'logs';

View file

@ -2,15 +2,15 @@
namespace App\Models;
use App\Enums\PlatformEnum;
use Database\Factories\PlatformAccountFactory;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Crypt;
use App\Enums\PlatformEnum;
/**
* @property int $id
@ -18,14 +18,13 @@
* @property string $instance_url
* @property string $username
* @property string $password
* @property array<string, mixed> $settings
* @property string $settings
* @property bool $is_active
* @property Carbon|null $last_tested_at
* @property Carbon $last_tested_at
* @property string $status
* @property Carbon $created_at
* @property Carbon $updated_at
* @property Collection<int, PlatformChannel> $activeChannels
*
* @method static where(string $string, PlatformEnum $platform)
* @method static orderBy(string $string)
* @method static create(array<string, mixed> $validated)
@ -43,14 +42,14 @@ class PlatformAccount extends Model
'settings',
'is_active',
'last_tested_at',
'status',
'status'
];
protected $casts = [
'platform' => PlatformEnum::class,
'settings' => 'array',
'is_active' => 'boolean',
'last_tested_at' => 'datetime',
'last_tested_at' => 'datetime'
];
// Encrypt password when storing
@ -94,6 +93,7 @@ protected function password(): Attribute
)->withoutObjectCaching();
}
// Get the active accounts for a platform (returns collection)
/**
* @return Collection<int, PlatformAccount>

View file

@ -10,16 +10,15 @@
/**
* @method static findMany(mixed $channel_ids)
* @method static create(array<string, mixed> $array)
*
* @property int $id
* @property int $platform_instance_id
* @method static create(array $array)
* @property integer $id
* @property integer $platform_instance_id
* @property PlatformInstance $platformInstance
* @property int $channel_id
* @property integer $channel_id
* @property string $name
* @property int $language_id
* @property Language|null $language
* @property bool $is_active
* @property boolean $is_active
*/
class PlatformChannel extends Model
{
@ -35,11 +34,11 @@ class PlatformChannel extends Model
'channel_id',
'description',
'language_id',
'is_active',
'is_active'
];
protected $casts = [
'is_active' => 'boolean',
'is_active' => 'boolean'
];
/**
@ -75,7 +74,7 @@ public function getFullNameAttribute(): string
}
/**
* @return BelongsToMany<Feed, $this>
* @return BelongsToMany<Feed, $this, Route>
*/
public function feeds(): BelongsToMany
{
@ -85,7 +84,7 @@ public function feeds(): BelongsToMany
}
/**
* @return BelongsToMany<Feed, $this>
* @return BelongsToMany<Feed, $this, Route>
*/
public function activeFeeds(): BelongsToMany
{

View file

@ -12,9 +12,7 @@
*/
class PlatformChannelPost extends Model
{
/** @use HasFactory<\Illuminate\Database\Eloquent\Factories\Factory<PlatformChannelPost>> */
use HasFactory;
protected $fillable = [
'platform',
'channel_id',

View file

@ -12,12 +12,11 @@
/**
* @method static updateOrCreate(array<string, mixed> $array, $instanceData)
* @method static where(string $string, mixed $operator)
*
* @property PlatformEnum $platform
* @property string $url
* @property string $name
* @property string $description
* @property bool $is_active
* @property boolean $is_active
*/
class PlatformInstance extends Model
{
@ -29,12 +28,12 @@ class PlatformInstance extends Model
'url',
'name',
'description',
'is_active',
'is_active'
];
protected $casts = [
'platform' => PlatformEnum::class,
'is_active' => 'boolean',
'is_active' => 'boolean'
];
/**

View file

@ -4,9 +4,9 @@
use Database\Factories\RouteFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
/**
@ -26,18 +26,17 @@ class Route extends Model
// Laravel doesn't handle composite primary keys well, so we'll use regular queries
protected $primaryKey = null;
public $incrementing = false;
protected $fillable = [
'feed_id',
'platform_channel_id',
'is_active',
'priority',
'priority'
];
protected $casts = [
'is_active' => 'boolean',
'is_active' => 'boolean'
];
/**

View file

@ -2,18 +2,16 @@
namespace App\Models;
use Database\Factories\SettingFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* @method static updateOrCreate(array<string, string> $array, array<string, mixed> $array1)
* @method static create(array<string, string> $array)
* @method static updateOrCreate(string[] $array, array $array1)
* @method static create(string[] $array)
* @method static where(string $string, string $key)
*/
class Setting extends Model
{
/** @use HasFactory<SettingFactory> */
use HasFactory;
protected $fillable = ['key', 'value'];

View file

@ -11,7 +11,7 @@
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasApiTokens, HasFactory, Notifiable;
use HasFactory, Notifiable, HasApiTokens;
/**
* The attributes that are mass assignable.

View file

@ -2,15 +2,13 @@
namespace App\Modules\Lemmy;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
use Illuminate\Http\Client\Response;
class LemmyRequest
{
private string $instance;
private ?string $token;
private string $scheme = 'https';
public function __construct(string $instance, ?string $token = null)
@ -47,7 +45,6 @@ public function withScheme(string $scheme): self
if (in_array($scheme, ['http', 'https'], true)) {
$this->scheme = $scheme;
}
return $this;
}
@ -86,7 +83,6 @@ public function post(string $endpoint, array $data = []): Response
public function withToken(string $token): self
{
$this->token = $token;
return $this;
}
}

View file

@ -61,7 +61,6 @@ public function login(string $username, string $password): ?string
}
$data = $response->json();
return $data['jwt'] ?? null;
} catch (Exception $e) {
// Re-throw rate limit exceptions immediately
@ -94,7 +93,6 @@ public function getCommunityId(string $communityName, string $token): int
}
$data = $response->json();
return $data['community_view']['community']['id'] ?? throw new Exception('Community not found');
} catch (Exception $e) {
logger()->error('Community lookup failed', ['error' => $e->getMessage()]);
@ -109,15 +107,14 @@ public function syncChannelPosts(string $token, int $platformChannelId, string $
$response = $request->get('post/list', [
'community_id' => $platformChannelId,
'limit' => 50,
'sort' => 'New',
'sort' => 'New'
]);
if (!$response->successful()) {
logger()->warning('Failed to sync channel posts', [
'status' => $response->status(),
'platform_channel_id' => $platformChannelId,
'platform_channel_id' => $platformChannelId
]);
return;
}
@ -140,13 +137,13 @@ public function syncChannelPosts(string $token, int $platformChannelId, string $
logger()->info('Synced channel posts', [
'platform_channel_id' => $platformChannelId,
'posts_count' => count($posts),
'posts_count' => count($posts)
]);
} catch (Exception $e) {
logger()->error('Exception while syncing channel posts', [
'error' => $e->getMessage(),
'platform_channel_id' => $platformChannelId,
'platform_channel_id' => $platformChannelId
]);
}
}
@ -201,20 +198,17 @@ public function getLanguages(): array
if (!$response->successful()) {
logger()->warning('Failed to fetch site languages', [
'status' => $response->status(),
'status' => $response->status()
]);
return [];
}
$data = $response->json();
return $data['all_languages'] ?? [];
} catch (Exception $e) {
logger()->error('Exception while fetching languages', [
'error' => $e->getMessage(),
'error' => $e->getMessage()
]);
return [];
}
}

View file

@ -12,7 +12,6 @@
class LemmyPublisher
{
private LemmyApiService $api;
private PlatformAccount $account;
public function __construct(PlatformAccount $account)
@ -24,7 +23,6 @@ public function __construct(PlatformAccount $account)
/**
* @param array<string, mixed> $extractedData
* @return array<string, mixed>
*
* @throws PlatformAuthException
* @throws Exception
*/
@ -39,7 +37,6 @@ public function publishToChannel(Article $article, array $extractedData, Platfor
// If the cached token was stale, refresh and retry once
if (str_contains($e->getMessage(), 'not_logged_in') || str_contains($e->getMessage(), 'Unauthorized')) {
$token = $authService->refreshToken($this->account);
return $this->createPost($token, $extractedData, $channel, $article);
}
throw $e;
@ -68,4 +65,5 @@ private function createPost(string $token, array $extractedData, PlatformChannel
$languageId
);
}
}

View file

@ -14,7 +14,9 @@
class AppServiceProvider extends ServiceProvider
{
public function register(): void {}
public function register(): void
{
}
public function boot(): void
{

View file

@ -4,9 +4,9 @@
use App\Models\Article;
use App\Models\Feed;
use App\Services\Http\HttpFetcher;
use App\Services\Factories\ArticleParserFactory;
use App\Services\Factories\HomepageParserFactory;
use App\Services\Http\HttpFetcher;
use App\Services\Log\LogSaver;
use Exception;
use Illuminate\Support\Collection;
@ -28,9 +28,9 @@ public function getArticlesFromFeed(Feed $feed): Collection
return $this->getArticlesFromWebsiteFeed($feed);
}
$this->logSaver->warning('Unsupported feed type', null, [
$this->logSaver->warning("Unsupported feed type", null, [
'feed_id' => $feed->id,
'feed_type' => $feed->type,
'feed_type' => $feed->type
]);
return collect();
@ -54,7 +54,7 @@ private function getArticlesFromRssFeed(Feed $feed): Collection
}
if ($rss === false || !isset($rss->channel->item)) {
$this->logSaver->warning('Failed to parse RSS feed XML', null, [
$this->logSaver->warning("Failed to parse RSS feed XML", null, [
'feed_id' => $feed->id,
'feed_url' => $feed->url,
]);
@ -72,7 +72,7 @@ private function getArticlesFromRssFeed(Feed $feed): Collection
return $articles;
} catch (Exception $e) {
$this->logSaver->error('Failed to fetch articles from RSS feed', null, [
$this->logSaver->error("Failed to fetch articles from RSS feed", null, [
'feed_id' => $feed->id,
'feed_url' => $feed->url,
'error' => $e->getMessage(),
@ -92,9 +92,9 @@ private function getArticlesFromWebsiteFeed(Feed $feed): Collection
$parser = HomepageParserFactory::getParserForFeed($feed);
if (! $parser) {
$this->logSaver->warning('No parser available for feed URL', null, [
$this->logSaver->warning("No parser available for feed URL", null, [
'feed_id' => $feed->id,
'feed_url' => $feed->url,
'feed_url' => $feed->url
]);
return collect();
@ -107,10 +107,10 @@ private function getArticlesFromWebsiteFeed(Feed $feed): Collection
->map(fn (string $url) => $this->saveArticle($url, $feed->id));
} catch (Exception $e) {
$this->logSaver->error('Failed to fetch articles from website feed', null, [
$this->logSaver->error("Failed to fetch articles from website feed", null, [
'feed_id' => $feed->id,
'feed_url' => $feed->url,
'error' => $e->getMessage(),
'error' => $e->getMessage()
]);
return collect();
@ -130,7 +130,7 @@ public function fetchArticleData(Article $article): array
} catch (Exception $e) {
$this->logSaver->error('Exception while fetching article data', null, [
'url' => $article->url,
'error' => $e->getMessage(),
'error' => $e->getMessage()
]);
return [];
@ -156,7 +156,7 @@ private function saveArticle(string $url, ?int $feedId = null): Article
return $article;
} catch (\Exception $e) {
$this->logSaver->error('Failed to create article', null, [
$this->logSaver->error("Failed to create article", null, [
'url' => $url,
'feed_id' => $feedId,
'error' => $e->getMessage(),

View file

@ -28,7 +28,7 @@ public function validate(Article $article): Article
if (!isset($articleData['full_article']) || empty($articleData['full_article'])) {
logger()->warning('Article data missing full_article content', [
'article_id' => $article->id,
'url' => $article->url,
'url' => $article->url
]);
$updateData['approval_status'] = 'rejected';
@ -67,7 +67,7 @@ private function validateByKeywords(string $full_article): bool
// Common Belgian news topics
'economy', 'economic', 'education', 'healthcare', 'transport', 'climate', 'energy',
'European', 'EU', 'migration', 'security', 'justice', 'culture', 'police',
'European', 'EU', 'migration', 'security', 'justice', 'culture', 'police'
];
foreach ($keywords as $keyword) {

View file

@ -52,12 +52,8 @@ public function refreshToken(PlatformAccount $account): string
/**
* Authenticate with Lemmy API and return user data with JWT
*
* @throws PlatformAuthException
*/
/**
* @return array<string, mixed>
*/
public function authenticate(string $instanceUrl, string $username, string $password): array
{
try {
@ -79,8 +75,8 @@ public function authenticate(string $instanceUrl, string $username, string $pass
'id' => 0, // Would need API call to get actual user info
'display_name' => null,
'bio' => null,
],
],
]
]
];
} catch (PlatformAuthException $e) {
// Re-throw PlatformAuthExceptions as-is to avoid nesting

View file

@ -9,12 +9,10 @@
use App\Models\PlatformChannel;
use App\Models\Route;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
class DashboardStatsService
{
/**
* @return array<string, mixed>
*/
public function getStats(string $period = 'today'): array
{
$dateRange = $this->getDateRange($period);
@ -75,9 +73,6 @@ private function getDateRange(string $period): ?array
};
}
/**
* @return array<string, int>
*/
public function getSystemStats(): array
{
$totalFeeds = Feed::query()->count();

View file

@ -4,9 +4,9 @@
use App\Contracts\ArticleParserInterface;
use App\Models\Feed;
use App\Services\Parsers\VrtArticleParser;
use App\Services\Parsers\BelgaArticleParser;
use App\Services\Parsers\GuardianArticleParser;
use App\Services\Parsers\VrtArticleParser;
use Exception;
class ArticleParserFactory
@ -26,7 +26,7 @@ class ArticleParserFactory
public static function getParser(string $url): ArticleParserInterface
{
foreach (self::$parsers as $parserClass) {
$parser = new $parserClass;
$parser = new $parserClass();
if ($parser->canParse($url)) {
return $parser;
@ -47,13 +47,12 @@ public static function getParserForFeed(Feed $feed, string $parserType = 'articl
return null;
}
/** @var class-string<ArticleParserInterface> $parserClass */
$parserClass = $providerConfig['parsers'][$parserType];
if (!class_exists($parserClass)) {
return null;
}
return new $parserClass;
return new $parserClass();
}
/**
@ -62,8 +61,7 @@ public static function getParserForFeed(Feed $feed, string $parserType = 'articl
public static function getSupportedSources(): array
{
return array_map(function($parserClass) {
$parser = new $parserClass;
$parser = new $parserClass();
return $parser->getSourceName();
}, self::$parsers);
}

View file

@ -18,13 +18,12 @@ public static function getParserForFeed(Feed $feed): ?HomepageParserInterface
return null;
}
/** @var class-string<HomepageParserInterface> $parserClass */
$parserClass = $providerConfig['parsers']['homepage'];
if (!class_exists($parserClass)) {
return null;
}
$language = $feed->language->short_code ?? 'en';
$language = $feed->language?->short_code ?? 'en';
return new $parserClass($language);
}

View file

@ -2,8 +2,8 @@
namespace App\Services\Http;
use Exception;
use Illuminate\Support\Facades\Http;
use Exception;
class HttpFetcher
{
@ -23,7 +23,7 @@ public static function fetchHtml(string $url): string
} catch (Exception $e) {
logger()->error('HTTP fetch failed', [
'url' => $url,
'error' => $e->getMessage(),
'error' => $e->getMessage()
]);
throw $e;
@ -54,14 +54,14 @@ public static function fetchMultipleUrls(array $urls): array
return [
'url' => $url,
'html' => $response->body(),
'success' => true,
'success' => true
];
} else {
return [
'url' => $url,
'html' => null,
'success' => false,
'status' => $response->status(),
'status' => $response->status()
];
}
} catch (Exception) {
@ -69,10 +69,11 @@ public static function fetchMultipleUrls(array $urls): array
'url' => $url,
'html' => null,
'success' => false,
'error' => 'Exception occurred',
'error' => 'Exception occurred'
];
}
})
->filter(fn($result) => $result !== null)
->toArray();
} catch (Exception $e) {
logger()->error('Multiple URL fetch failed', ['error' => $e->getMessage()]);

View file

@ -34,7 +34,6 @@ public static function extractArticleUrls(string $html): array
return false;
}
}
return true;
})
->map(function ($path) {

View file

@ -7,14 +7,9 @@
class BelgaHomepageParserAdapter implements HomepageParserInterface
{
public function __construct(
private readonly string $language = 'en',
private string $language = 'en',
) {}
public function getLanguage(): string
{
return $this->language;
}
public function canParse(string $url): bool
{
return str_contains($url, 'belganewsagency.eu');

View file

@ -12,13 +12,15 @@
use App\Modules\Lemmy\Services\LemmyPublisher;
use App\Services\Log\LogSaver;
use Exception;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Collection;
use RuntimeException;
class ArticlePublishingService
{
public function __construct(private LogSaver $logSaver) {}
public function __construct(private LogSaver $logSaver)
{
}
/**
* Factory seam to create publisher instances (helps testing without network calls)
*/
@ -26,11 +28,9 @@ protected function makePublisher(mixed $account): LemmyPublisher
{
return new LemmyPublisher($account);
}
/**
* @param array<string, mixed> $extractedData
* @return Collection<int, ArticlePublication>
*
* @throws PublishException
*/
public function publishToRoutedChannels(Article $article, array $extractedData): Collection
@ -60,7 +60,7 @@ public function publishToRoutedChannels(Article $article, array $extractedData):
if (! $account) {
$this->logSaver->warning('No active account for channel', $channel, [
'article_id' => $article->id,
'route_priority' => $route->priority,
'route_priority' => $route->priority
]);
return null;
@ -73,7 +73,6 @@ public function publishToRoutedChannels(Article $article, array $extractedData):
/**
* Check if a route matches an article based on keywords
*
* @param array<string, mixed> $extractedData
*/
private function routeMatchesArticle(Route $route, array $extractedData): bool
@ -146,14 +145,14 @@ private function publishToChannel(Article $article, array $extractedData, Platfo
]);
$this->logSaver->info('Published to channel via keyword-filtered routing', $channel, [
'article_id' => $article->id,
'article_id' => $article->id
]);
return $publication;
} catch (Exception $e) {
$this->logSaver->warning('Failed to publish to channel', $channel, [
'article_id' => $article->id,
'error' => $e->getMessage(),
'error' => $e->getMessage()
]);
return null;

View file

@ -11,7 +11,6 @@ class RoutingValidationService
{
/**
* @param Collection<int, PlatformChannel> $channels
*
* @throws RoutingMismatchException
*/
public function validateLanguageCompatibility(Feed $feed, Collection $channels): void

View file

@ -3,8 +3,8 @@
namespace App\Services;
use App\Models\Feed;
use App\Models\PlatformChannel;
use App\Models\Route;
use App\Models\PlatformChannel;
use App\Models\Setting;
class SystemStatusService

View file

@ -29,7 +29,6 @@
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"phpstan/phpstan": "^2.1",
"phpstan/phpstan-mockery": "^2.0",
"phpunit/phpunit": "^11.5.3"
},
"autoload": {

View file

@ -2,8 +2,8 @@
namespace Database\Factories;
use App\Models\Article;
use App\Models\ArticlePublication;
use App\Models\Article;
use App\Models\PlatformChannel;
use Illuminate\Database\Eloquent\Factories\Factory;

View file

@ -34,7 +34,7 @@ public function inactive(): static
]);
}
public function community(?string $name = null): static
public function community(string $name = null): static
{
$communityName = $name ?: $this->faker->word();

View file

@ -2,9 +2,9 @@
namespace Database\Factories;
use App\Models\Route;
use App\Models\Feed;
use App\Models\PlatformChannel;
use App\Models\Route;
use Illuminate\Database\Eloquent\Factories\Factory;
class RouteFactory extends Factory

View file

@ -17,7 +17,8 @@ public function run(): void
'name' => 'Belgae Social',
'description' => 'A Belgian Lemmy instance on the fediverse',
],
])->each(fn ($instanceData) => PlatformInstance::updateOrCreate(
])->each (fn ($instanceData) =>
PlatformInstance::updateOrCreate(
[
'platform' => $instanceData['platform'],
'url' => $instanceData['url'],

View file

@ -3,6 +3,7 @@
namespace Database\Seeders;
use App\Models\Setting;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class SettingsSeeder extends Seeder

View file

@ -1,6 +1,5 @@
includes:
- vendor/larastan/larastan/extension.neon
- vendor/phpstan/phpstan-mockery/extension.neon
parameters:
level: 7
@ -11,7 +10,3 @@ parameters:
excludePaths:
- bootstrap/*.php
- storage/*
ignoreErrors:
- identifier: method.alreadyNarrowedType
- identifier: function.alreadyNarrowedType

View file

@ -4,12 +4,12 @@
use App\Http\Controllers\Api\V1\AuthController;
use App\Http\Controllers\Api\V1\DashboardController;
use App\Http\Controllers\Api\V1\FeedsController;
use App\Http\Controllers\Api\V1\KeywordsController;
use App\Http\Controllers\Api\V1\LogsController;
use App\Http\Controllers\Api\V1\OnboardingController;
use App\Http\Controllers\Api\V1\PlatformAccountsController;
use App\Http\Controllers\Api\V1\PlatformChannelsController;
use App\Http\Controllers\Api\V1\RoutingController;
use App\Http\Controllers\Api\V1\KeywordsController;
use App\Http\Controllers\Api\V1\SettingsController;
use Illuminate\Support\Facades\Route;

View file

@ -2,6 +2,11 @@
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;
@ -26,7 +31,7 @@ public function test_api_routes_are_publicly_accessible(): void
'/api/v1/feeds',
'/api/v1/routing',
'/api/v1/settings',
'/api/v1/logs',
'/api/v1/logs'
];
foreach ($routes as $route) {
@ -44,7 +49,7 @@ public function test_fallback_route_returns_api_message(): void
$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',
'api_base' => '/api/v1'
]);
}
}

View file

@ -27,12 +27,12 @@ public function test_user_model_creates_successfully(): void
{
$user = User::factory()->create([
'name' => 'Test User',
'email' => 'test@example.com',
'email' => 'test@example.com'
]);
$this->assertDatabaseHas('users', [
'name' => 'Test User',
'email' => 'test@example.com',
'email' => 'test@example.com'
]);
$this->assertEquals('Test User', $user->name);
@ -43,40 +43,38 @@ public function test_language_model_creates_successfully(): void
{
$language = Language::factory()->create([
'name' => 'English',
'short_code' => 'en',
'short_code' => 'en'
]);
$this->assertDatabaseHas('languages', [
'name' => 'English',
'short_code' => 'en',
'short_code' => 'en'
]);
}
public function test_platform_instance_model_creates_successfully(): void
{
/** @var PlatformInstance $instance */
$instance = PlatformInstance::factory()->create([
'name' => 'Test Instance',
'url' => 'https://test.lemmy.world',
'url' => 'https://test.lemmy.world'
]);
$this->assertDatabaseHas('platform_instances', [
'name' => 'Test Instance',
'url' => 'https://test.lemmy.world',
'url' => 'https://test.lemmy.world'
]);
}
public function test_platform_account_model_creates_successfully(): void
{
/** @var PlatformAccount $account */
$account = PlatformAccount::factory()->create([
'username' => 'testuser',
'is_active' => true,
'is_active' => true
]);
$this->assertDatabaseHas('platform_accounts', [
'username' => 'testuser',
'is_active' => true,
'is_active' => true
]);
$this->assertEquals('testuser', $account->username);
@ -91,14 +89,14 @@ public function test_platform_channel_model_creates_successfully(): void
'platform_instance_id' => $instance->id,
'language_id' => $language->id,
'name' => 'Test Channel',
'is_active' => true,
'is_active' => true
]);
$this->assertDatabaseHas('platform_channels', [
'platform_instance_id' => $instance->id,
'language_id' => $language->id,
'name' => 'Test Channel',
'is_active' => true,
'is_active' => true
]);
$this->assertEquals($instance->id, $channel->platformInstance->id);
@ -113,14 +111,14 @@ public function test_feed_model_creates_successfully(): void
'language_id' => $language->id,
'name' => 'Test Feed',
'url' => 'https://example.com/feed.rss',
'is_active' => true,
'is_active' => true
]);
$this->assertDatabaseHas('feeds', [
'language_id' => $language->id,
'name' => 'Test Feed',
'url' => 'https://example.com/feed.rss',
'is_active' => true,
'is_active' => true
]);
$this->assertEquals($language->id, $feed->language->id);
@ -134,14 +132,14 @@ public function test_article_model_creates_successfully(): void
'feed_id' => $feed->id,
'title' => 'Test Article',
'url' => 'https://example.com/article',
'approval_status' => 'pending',
'approval_status' => 'pending'
]);
$this->assertDatabaseHas('articles', [
'feed_id' => $feed->id,
'title' => 'Test Article',
'url' => 'https://example.com/article',
'approval_status' => 'pending',
'approval_status' => 'pending'
]);
$this->assertEquals($feed->id, $article->feed->id);
@ -157,14 +155,14 @@ public function test_article_publication_model_creates_successfully(): void
'platform_channel_id' => $channel->id,
'post_id' => 'test-post-123',
'published_at' => now(),
'published_by' => 'test-user',
'published_by' => 'test-user'
]);
$this->assertDatabaseHas('article_publications', [
'article_id' => $article->id,
'platform_channel_id' => $channel->id,
'post_id' => 'test-post-123',
'published_by' => 'test-user',
'published_by' => 'test-user'
]);
$this->assertEquals($article->id, $publication->article->id);
@ -176,17 +174,16 @@ public function test_route_model_creates_successfully(): void
$feed = Feed::factory()->create();
$channel = PlatformChannel::factory()->create();
/** @var Route $route */
$route = Route::factory()->create([
'feed_id' => $feed->id,
'platform_channel_id' => $channel->id,
'is_active' => true,
'is_active' => true
]);
$this->assertDatabaseHas('routes', [
'feed_id' => $feed->id,
'platform_channel_id' => $channel->id,
'is_active' => true,
'is_active' => true
]);
$this->assertEquals($feed->id, $route->feed->id);
@ -231,14 +228,14 @@ public function test_keyword_model_creates_successfully(): void
->forChannel($channel)
->create([
'keyword' => 'test keyword',
'is_active' => true,
'is_active' => true
]);
$this->assertDatabaseHas('keywords', [
'keyword' => 'test keyword',
'is_active' => true,
'feed_id' => $feed->id,
'platform_channel_id' => $channel->id,
'platform_channel_id' => $channel->id
]);
}
@ -248,12 +245,12 @@ public function test_log_model_creates_successfully(): void
'level' => 'info',
'message' => 'Test log message',
'context' => json_encode(['key' => 'value']),
'logged_at' => now(),
'logged_at' => now()
]);
$this->assertDatabaseHas('logs', [
'level' => 'info',
'message' => 'Test log message',
'message' => 'Test log message'
]);
}
@ -261,12 +258,12 @@ public function test_setting_model_creates_successfully(): void
{
$setting = Setting::create([
'key' => 'test_setting',
'value' => 'test_value',
'value' => 'test_value'
]);
$this->assertDatabaseHas('settings', [
'key' => 'test_setting',
'value' => 'test_value',
'value' => 'test_value'
]);
}
@ -293,7 +290,7 @@ public function test_platform_account_channels_many_to_many_relationship(): void
$this->assertDatabaseHas('platform_account_channels', [
'platform_account_id' => $account->id,
'platform_channel_id' => $channel->id,
'is_active' => true,
'is_active' => true
]);
}
@ -327,7 +324,7 @@ public function test_model_soft_deletes_work_correctly(): void
// Should find with withTrashed if model uses soft deletes
if (method_exists($feed, 'withTrashed')) {
$this->assertNotNull(Feed::withTrashed()->find($feedId)); // @phpstan-ignore staticMethod.notFound
$this->assertNotNull(Feed::withTrashed()->find($feedId));
}
}

View file

@ -78,7 +78,7 @@ public function test_command_skips_when_article_processing_disabled(): void
Queue::fake();
Setting::create([
'key' => 'article_processing_enabled',
'value' => '0',
'value' => '0'
]);
// Act

View file

@ -2,7 +2,9 @@
namespace Tests\Feature\Http\Console\Commands;
use App\Jobs\SyncChannelPostsJob;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
use Illuminate\Testing\PendingCommand;
use Tests\TestCase;
@ -34,7 +36,6 @@ public function test_command_returns_failure_exit_code_for_unsupported_platform(
public function test_command_accepts_lemmy_platform_argument(): void
{
// Act - Test that the command accepts lemmy as a valid platform argument
/** @var PendingCommand $exitCode */
$exitCode = $this->artisan('channel:sync lemmy');
// Assert - Command should succeed (not fail with argument validation error)
@ -45,7 +46,6 @@ public function test_command_accepts_lemmy_platform_argument(): void
public function test_command_handles_default_platform(): void
{
// Act - Test that the command works with default platform (should be lemmy)
/** @var PendingCommand $exitCode */
$exitCode = $this->artisan('channel:sync');
// Assert - Command should succeed with default platform

View file

@ -4,6 +4,7 @@
use App\Models\Article;
use App\Models\Feed;
use App\Models\Setting;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
@ -32,7 +33,7 @@ public function test_index_returns_successful_response(): void
'publishing_approvals_enabled',
],
],
'message',
'message'
]);
}
@ -52,7 +53,7 @@ public function test_index_returns_articles_with_pagination(): void
'total' => 25,
'last_page' => 3,
],
],
]
]);
$this->assertCount(10, $response->json('data.articles'));
@ -73,7 +74,7 @@ public function test_index_respects_per_page_limit(): void
'pagination' => [
'per_page' => 100, // Should be capped at 100
],
],
]
]);
}
@ -84,13 +85,13 @@ public function test_index_orders_articles_by_created_at_desc(): void
$firstArticle = Article::factory()->create([
'feed_id' => $feed->id,
'created_at' => now()->subHours(2),
'title' => 'First Article',
'title' => 'First Article'
]);
$secondArticle = Article::factory()->create([
'feed_id' => $feed->id,
'created_at' => now()->subHour(),
'title' => 'Second Article',
'title' => 'Second Article'
]);
$response = $this->getJson('/api/v1/articles');
@ -107,7 +108,7 @@ public function test_approve_article_successfully(): void
$feed = Feed::factory()->create();
$article = Article::factory()->create([
'feed_id' => $feed->id,
'approval_status' => 'pending',
'approval_status' => 'pending'
]);
$response = $this->postJson("/api/v1/articles/{$article->id}/approve");
@ -115,7 +116,7 @@ public function test_approve_article_successfully(): void
$response->assertStatus(200)
->assertJson([
'success' => true,
'message' => 'Article approved and queued for publishing.',
'message' => 'Article approved and queued for publishing.'
]);
$article->refresh();
@ -134,7 +135,7 @@ public function test_reject_article_successfully(): void
$feed = Feed::factory()->create();
$article = Article::factory()->create([
'feed_id' => $feed->id,
'approval_status' => 'pending',
'approval_status' => 'pending'
]);
$response = $this->postJson("/api/v1/articles/{$article->id}/reject");
@ -142,7 +143,7 @@ public function test_reject_article_successfully(): void
$response->assertStatus(200)
->assertJson([
'success' => true,
'message' => 'Article rejected.',
'message' => 'Article rejected.'
]);
$article->refresh();
@ -164,9 +165,9 @@ public function test_index_includes_settings(): void
->assertJsonStructure([
'data' => [
'settings' => [
'publishing_approvals_enabled',
],
],
'publishing_approvals_enabled'
]
]
]);
}
}

View file

@ -38,7 +38,7 @@ public function test_stats_returns_successful_response(): void
'available_periods',
'current_period',
],
'message',
'message'
]);
}
@ -54,7 +54,7 @@ public function test_stats_with_different_periods(): void
'success' => true,
'data' => [
'current_period' => $period,
],
]
]);
}
}
@ -80,7 +80,7 @@ public function test_stats_with_sample_data(): void
ArticlePublication::factory()->create([
'article_id' => $articles->first()->id,
'platform_channel_id' => $channel->id,
'published_at' => now(),
'published_at' => now()
]);
$response = $this->getJson('/api/v1/dashboard/stats?period=all');
@ -93,7 +93,7 @@ public function test_stats_with_sample_data(): void
'articles_fetched' => $initialArticles + 3,
'articles_published' => $initialPublications + 1,
],
],
]
]);
// Just verify structure and that we have more items than we started with
@ -126,7 +126,7 @@ public function test_stats_returns_empty_data_with_no_records(): void
'total_routes' => 0,
'active_routes' => 0,
],
],
]
]);
}
}

View file

@ -20,9 +20,9 @@ public function test_index_returns_successful_response(): void
'success',
'data' => [
'feeds',
'pagination',
'pagination'
],
'message',
'message'
]);
}
@ -69,7 +69,7 @@ public function test_store_creates_vrt_feed_successfully(): void
'url' => 'https://www.vrt.be/vrtnws/en/',
'type' => 'website',
'is_active' => true,
],
]
]);
$this->assertDatabaseHas('feeds', [
@ -101,7 +101,7 @@ public function test_store_creates_belga_feed_successfully(): void
'url' => 'https://www.belganewsagency.eu/',
'type' => 'website',
'is_active' => true,
],
]
]);
$this->assertDatabaseHas('feeds', [
@ -133,7 +133,7 @@ public function test_store_creates_guardian_feed_successfully(): void
'url' => 'https://www.theguardian.com/international/rss',
'type' => 'rss',
'is_active' => true,
],
]
]);
$this->assertDatabaseHas('feeds', [
@ -160,7 +160,7 @@ public function test_store_sets_default_active_status(): void
->assertJson([
'data' => [
'is_active' => true, // Should default to true
],
]
]);
}
@ -201,7 +201,7 @@ public function test_show_returns_feed_successfully(): void
'data' => [
'id' => $feed->id,
'name' => $feed->name,
],
]
]);
}
@ -232,7 +232,7 @@ public function test_update_modifies_feed_successfully(): void
'message' => 'Feed updated successfully!',
'data' => [
'name' => 'Updated Name',
],
]
]);
$this->assertDatabaseHas('feeds', [
@ -260,7 +260,7 @@ public function test_update_preserves_active_status_when_not_provided(): void
->assertJson([
'data' => [
'is_active' => false, // Should preserve original value
],
]
]);
}
@ -298,7 +298,7 @@ public function test_toggle_activates_inactive_feed(): void
'message' => 'Feed activated successfully!',
'data' => [
'is_active' => true,
],
]
]);
$this->assertDatabaseHas('feeds', [
@ -319,7 +319,7 @@ public function test_toggle_deactivates_active_feed(): void
'message' => 'Feed deactivated successfully!',
'data' => [
'is_active' => false,
],
]
]);
$this->assertDatabaseHas('feeds', [

View file

@ -14,9 +14,7 @@ class KeywordsControllerTest extends TestCase
use RefreshDatabase;
protected Feed $feed;
protected PlatformChannel $channel;
protected Route $route;
protected function setUp(): void
@ -30,7 +28,7 @@ protected function setUp(): void
'feed_id' => $this->feed->id,
'platform_channel_id' => $this->channel->id,
'is_active' => true,
'priority' => 50,
'priority' => 50
]);
}
@ -40,7 +38,7 @@ public function test_can_get_keywords_for_route(): void
'feed_id' => $this->feed->id,
'platform_channel_id' => $this->channel->id,
'keyword' => 'test keyword',
'is_active' => true,
'is_active' => true
]);
$response = $this->getJson("/api/v1/routing/{$this->feed->id}/{$this->channel->id}/keywords");
@ -54,9 +52,9 @@ public function test_can_get_keywords_for_route(): void
'keyword',
'is_active',
'feed_id',
'platform_channel_id',
],
],
'platform_channel_id'
]
]
])
->assertJsonPath('data.0.keyword', 'test keyword');
}
@ -65,7 +63,7 @@ public function test_can_create_keyword_for_route(): void
{
$keywordData = [
'keyword' => 'new keyword',
'is_active' => true,
'is_active' => true
];
$response = $this->postJson("/api/v1/routing/{$this->feed->id}/{$this->channel->id}/keywords", $keywordData);
@ -78,8 +76,8 @@ public function test_can_create_keyword_for_route(): void
'keyword',
'is_active',
'feed_id',
'platform_channel_id',
],
'platform_channel_id'
]
])
->assertJsonPath('data.keyword', 'new keyword')
->assertJsonPath('data.is_active', true);
@ -88,7 +86,7 @@ public function test_can_create_keyword_for_route(): void
'keyword' => 'new keyword',
'feed_id' => $this->feed->id,
'platform_channel_id' => $this->channel->id,
'is_active' => true,
'is_active' => true
]);
}
@ -97,11 +95,11 @@ public function test_cannot_create_duplicate_keyword_for_route(): void
Keyword::factory()->create([
'feed_id' => $this->feed->id,
'platform_channel_id' => $this->channel->id,
'keyword' => 'duplicate keyword',
'keyword' => 'duplicate keyword'
]);
$response = $this->postJson("/api/v1/routing/{$this->feed->id}/{$this->channel->id}/keywords", [
'keyword' => 'duplicate keyword',
'keyword' => 'duplicate keyword'
]);
$response->assertStatus(409)
@ -111,15 +109,14 @@ public function test_cannot_create_duplicate_keyword_for_route(): void
public function test_can_update_keyword(): void
{
/** @var Keyword $keyword */
$keyword = Keyword::factory()->create([
'feed_id' => $this->feed->id,
'platform_channel_id' => $this->channel->id,
'is_active' => true,
'is_active' => true
]);
$response = $this->putJson("/api/v1/routing/{$this->feed->id}/{$this->channel->id}/keywords/{$keyword->id}", [
'is_active' => false,
'is_active' => false
]);
$response->assertStatus(200)
@ -127,16 +124,15 @@ public function test_can_update_keyword(): void
$this->assertDatabaseHas('keywords', [
'id' => $keyword->id,
'is_active' => false,
'is_active' => false
]);
}
public function test_can_delete_keyword(): void
{
/** @var Keyword $keyword */
$keyword = Keyword::factory()->create([
'feed_id' => $this->feed->id,
'platform_channel_id' => $this->channel->id,
'platform_channel_id' => $this->channel->id
]);
$response = $this->deleteJson("/api/v1/routing/{$this->feed->id}/{$this->channel->id}/keywords/{$keyword->id}");
@ -144,17 +140,16 @@ public function test_can_delete_keyword(): void
$response->assertStatus(200);
$this->assertDatabaseMissing('keywords', [
'id' => $keyword->id,
'id' => $keyword->id
]);
}
public function test_can_toggle_keyword(): void
{
/** @var Keyword $keyword */
$keyword = Keyword::factory()->create([
'feed_id' => $this->feed->id,
'platform_channel_id' => $this->channel->id,
'is_active' => true,
'is_active' => true
]);
$response = $this->postJson("/api/v1/routing/{$this->feed->id}/{$this->channel->id}/keywords/{$keyword->id}/toggle");
@ -164,7 +159,7 @@ public function test_can_toggle_keyword(): void
$this->assertDatabaseHas('keywords', [
'id' => $keyword->id,
'is_active' => false,
'is_active' => false
]);
}
@ -173,10 +168,9 @@ public function test_cannot_access_keyword_from_different_route(): void
$otherFeed = Feed::factory()->create();
$otherChannel = PlatformChannel::factory()->create();
/** @var Keyword $keyword */
$keyword = Keyword::factory()->create([
'feed_id' => $otherFeed->id,
'platform_channel_id' => $otherChannel->id,
'platform_channel_id' => $otherChannel->id
]);
$response = $this->deleteJson("/api/v1/routing/{$this->feed->id}/{$this->channel->id}/keywords/{$keyword->id}");

View file

@ -37,7 +37,7 @@ public function test_index_returns_successful_response(): void
'context',
'created_at',
'updated_at',
],
]
],
'pagination' => [
'current_page',
@ -46,12 +46,12 @@ public function test_index_returns_successful_response(): void
'total',
'from',
'to',
],
],
]
]
])
->assertJson([
'success' => true,
'message' => 'Logs retrieved successfully.',
'message' => 'Logs retrieved successfully.'
]);
}
@ -147,8 +147,8 @@ public function test_index_handles_empty_logs(): void
'total' => 0,
'current_page' => 1,
'last_page' => 1,
],
],
]
]
]);
}

View file

@ -30,7 +30,7 @@ protected function setUp(): void
]);
}
public function test_status_shows_needs_onboarding_when_no_components_exist(): void
public function test_status_shows_needs_onboarding_when_no_components_exist()
{
$response = $this->getJson('/api/v1/onboarding/status');
@ -49,7 +49,7 @@ public function test_status_shows_needs_onboarding_when_no_components_exist(): v
]);
}
public function test_status_shows_feed_step_when_platform_account_exists(): void
public function test_status_shows_feed_step_when_platform_account_exists()
{
PlatformAccount::factory()->create(['is_active' => true]);
@ -69,7 +69,7 @@ public function test_status_shows_feed_step_when_platform_account_exists(): void
]);
}
public function test_status_shows_channel_step_when_platform_account_and_feed_exist(): void
public function test_status_shows_channel_step_when_platform_account_and_feed_exist()
{
$language = Language::first();
PlatformAccount::factory()->create(['is_active' => true]);
@ -91,7 +91,7 @@ public function test_status_shows_channel_step_when_platform_account_and_feed_ex
]);
}
public function test_status_shows_route_step_when_platform_account_feed_and_channel_exist(): void
public function test_status_shows_route_step_when_platform_account_feed_and_channel_exist()
{
$language = Language::first();
PlatformAccount::factory()->create(['is_active' => true]);
@ -114,7 +114,7 @@ public function test_status_shows_route_step_when_platform_account_feed_and_chan
]);
}
public function test_status_shows_no_onboarding_needed_when_all_components_exist(): void
public function test_status_shows_no_onboarding_needed_when_all_components_exist()
{
$language = Language::first();
PlatformAccount::factory()->create(['is_active' => true]);
@ -138,7 +138,7 @@ public function test_status_shows_no_onboarding_needed_when_all_components_exist
]);
}
public function test_status_shows_no_onboarding_needed_when_skipped(): void
public function test_status_shows_no_onboarding_needed_when_skipped()
{
// No components exist but onboarding is skipped
Setting::create([
@ -163,7 +163,7 @@ public function test_status_shows_no_onboarding_needed_when_skipped(): void
]);
}
public function test_options_returns_languages_and_platform_instances(): void
public function test_options_returns_languages_and_platform_instances()
{
PlatformInstance::factory()->create([
'platform' => 'lemmy',
@ -179,34 +179,34 @@ public function test_options_returns_languages_and_platform_instances(): void
'success',
'data' => [
'languages' => [
'*' => ['id', 'short_code', 'name', 'native_name', 'is_active'],
'*' => ['id', 'short_code', 'name', 'native_name', 'is_active']
],
'platform_instances' => [
'*' => ['id', 'platform', 'url', 'name', 'description', 'is_active'],
],
],
'*' => ['id', 'platform', 'url', 'name', 'description', 'is_active']
]
]
]);
}
public function test_complete_onboarding_returns_success(): void
public function test_complete_onboarding_returns_success()
{
$response = $this->postJson('/api/v1/onboarding/complete');
$response->assertStatus(200)
->assertJson([
'success' => true,
'data' => ['completed' => true],
'data' => ['completed' => true]
]);
}
public function test_skip_onboarding_creates_setting(): void
public function test_skip_onboarding_creates_setting()
{
$response = $this->postJson('/api/v1/onboarding/skip');
$response->assertStatus(200)
->assertJson([
'success' => true,
'data' => ['skipped' => true],
'data' => ['skipped' => true]
]);
$this->assertDatabaseHas('settings', [
@ -215,7 +215,7 @@ public function test_skip_onboarding_creates_setting(): void
]);
}
public function test_skip_onboarding_updates_existing_setting(): void
public function test_skip_onboarding_updates_existing_setting()
{
// Create existing setting with false value
Setting::create([
@ -236,7 +236,7 @@ public function test_skip_onboarding_updates_existing_setting(): void
$this->assertEquals(1, Setting::where('key', 'onboarding_skipped')->count());
}
public function test_reset_skip_removes_setting(): void
public function test_reset_skip_removes_setting()
{
// Create skipped setting
Setting::create([
@ -249,7 +249,7 @@ public function test_reset_skip_removes_setting(): void
$response->assertStatus(200)
->assertJson([
'success' => true,
'data' => ['reset' => true],
'data' => ['reset' => true]
]);
$this->assertDatabaseMissing('settings', [
@ -257,18 +257,18 @@ public function test_reset_skip_removes_setting(): void
]);
}
public function test_reset_skip_works_when_no_setting_exists(): void
public function test_reset_skip_works_when_no_setting_exists()
{
$response = $this->postJson('/api/v1/onboarding/reset-skip');
$response->assertStatus(200)
->assertJson([
'success' => true,
'data' => ['reset' => true],
'data' => ['reset' => true]
]);
}
public function test_onboarding_flow_integration(): void
public function test_onboarding_flow_integration()
{
// 1. Initial status - needs onboarding
$response = $this->getJson('/api/v1/onboarding/status');

View file

@ -33,12 +33,12 @@ public function test_index_returns_successful_response(): void
'is_active',
'created_at',
'updated_at',
],
],
]
]
])
->assertJson([
'success' => true,
'message' => 'Platform accounts retrieved successfully.',
'message' => 'Platform accounts retrieved successfully.'
]);
}
@ -75,11 +75,11 @@ public function test_store_creates_platform_account_successfully(): void
'is_active',
'created_at',
'updated_at',
],
]
])
->assertJson([
'success' => true,
'message' => 'Platform account created successfully!',
'message' => 'Platform account created successfully!'
]);
$this->assertDatabaseHas('platform_accounts', [
@ -115,7 +115,7 @@ public function test_show_returns_platform_account_successfully(): void
'is_active',
'created_at',
'updated_at',
],
]
])
->assertJson([
'success' => true,
@ -123,7 +123,7 @@ public function test_show_returns_platform_account_successfully(): void
'data' => [
'id' => $account->id,
'username' => $account->username,
],
]
]);
}
@ -134,7 +134,7 @@ public function test_update_modifies_platform_account_successfully(): void
$updateData = [
'instance_url' => 'https://updated.example.com',
'username' => 'updateduser',
'settings' => ['updated' => 'value'],
'settings' => ['updated' => 'value']
];
$response = $this->putJson("/api/v1/platform-accounts/{$account->id}", $updateData);
@ -142,7 +142,7 @@ public function test_update_modifies_platform_account_successfully(): void
$response->assertStatus(200)
->assertJson([
'success' => true,
'message' => 'Platform account updated successfully!',
'message' => 'Platform account updated successfully!'
]);
$this->assertDatabaseHas('platform_accounts', [
@ -161,11 +161,11 @@ public function test_destroy_deletes_platform_account_successfully(): void
$response->assertStatus(200)
->assertJson([
'success' => true,
'message' => 'Platform account deleted successfully!',
'message' => 'Platform account deleted successfully!'
]);
$this->assertDatabaseMissing('platform_accounts', [
'id' => $account->id,
'id' => $account->id
]);
}
@ -182,7 +182,7 @@ public function test_set_active_activates_platform_account(): void
$this->assertDatabaseHas('platform_accounts', [
'id' => $account->id,
'is_active' => true,
'is_active' => true
]);
}
@ -190,12 +190,12 @@ public function test_set_active_deactivates_other_accounts_of_same_platform(): v
{
$activeAccount = PlatformAccount::factory()->create([
'platform' => 'lemmy',
'is_active' => true,
'is_active' => true
]);
$newAccount = PlatformAccount::factory()->create([
'platform' => 'lemmy',
'is_active' => false,
'is_active' => false
]);
$response = $this->postJson("/api/v1/platform-accounts/{$newAccount->id}/set-active");
@ -204,12 +204,12 @@ public function test_set_active_deactivates_other_accounts_of_same_platform(): v
$this->assertDatabaseHas('platform_accounts', [
'id' => $activeAccount->id,
'is_active' => false,
'is_active' => false
]);
$this->assertDatabaseHas('platform_accounts', [
'id' => $newAccount->id,
'is_active' => true,
'is_active' => true
]);
}
}

View file

@ -34,13 +34,13 @@ public function test_index_returns_successful_response(): void
'is_active',
'created_at',
'updated_at',
'platform_instance',
],
],
'platform_instance'
]
]
])
->assertJson([
'success' => true,
'message' => 'Platform channels retrieved successfully.',
'message' => 'Platform channels retrieved successfully.'
]);
}
@ -51,7 +51,7 @@ public function test_store_creates_platform_channel_successfully(): void
// Create a platform account for this instance first
PlatformAccount::factory()->create([
'instance_url' => $instance->url,
'is_active' => true,
'is_active' => true
]);
$data = [
@ -76,11 +76,11 @@ public function test_store_creates_platform_channel_successfully(): void
'is_active',
'created_at',
'updated_at',
],
]
])
->assertJson([
'success' => true,
'message' => 'Platform channel created successfully and linked to platform account!',
'message' => 'Platform channel created successfully and linked to platform account!'
]);
$this->assertDatabaseHas('platform_channels', [
@ -102,7 +102,7 @@ public function test_store_validates_platform_instance_exists(): void
{
$data = [
'platform_instance_id' => 999,
'name' => 'Test Channel',
'name' => 'Test Channel'
];
$response = $this->postJson('/api/v1/platform-channels', $data);
@ -132,8 +132,8 @@ public function test_show_returns_platform_channel_successfully(): void
'is_active',
'created_at',
'updated_at',
'platform_instance',
],
'platform_instance'
]
])
->assertJson([
'success' => true,
@ -141,7 +141,7 @@ public function test_show_returns_platform_channel_successfully(): void
'data' => [
'id' => $channel->id,
'name' => $channel->name,
],
]
]);
}
@ -154,7 +154,7 @@ public function test_update_modifies_platform_channel_successfully(): void
'name' => 'Updated Channel',
'display_name' => 'Updated Display Name',
'description' => 'Updated description',
'is_active' => false,
'is_active' => false
];
$response = $this->putJson("/api/v1/platform-channels/{$channel->id}", $updateData);
@ -162,7 +162,7 @@ public function test_update_modifies_platform_channel_successfully(): void
$response->assertStatus(200)
->assertJson([
'success' => true,
'message' => 'Platform channel updated successfully!',
'message' => 'Platform channel updated successfully!'
]);
$this->assertDatabaseHas('platform_channels', [
@ -183,11 +183,11 @@ public function test_destroy_deletes_platform_channel_successfully(): void
$response->assertStatus(200)
->assertJson([
'success' => true,
'message' => 'Platform channel deleted successfully!',
'message' => 'Platform channel deleted successfully!'
]);
$this->assertDatabaseMissing('platform_channels', [
'id' => $channel->id,
'id' => $channel->id
]);
}
@ -196,7 +196,7 @@ public function test_toggle_activates_inactive_channel(): void
$instance = PlatformInstance::factory()->create();
$channel = PlatformChannel::factory()->create([
'platform_instance_id' => $instance->id,
'is_active' => false,
'is_active' => false
]);
$response = $this->postJson("/api/v1/platform-channels/{$channel->id}/toggle");
@ -204,12 +204,12 @@ public function test_toggle_activates_inactive_channel(): void
$response->assertStatus(200)
->assertJson([
'success' => true,
'message' => 'Platform channel activated successfully!',
'message' => 'Platform channel activated successfully!'
]);
$this->assertDatabaseHas('platform_channels', [
'id' => $channel->id,
'is_active' => true,
'is_active' => true
]);
}
@ -218,7 +218,7 @@ public function test_toggle_deactivates_active_channel(): void
$instance = PlatformInstance::factory()->create();
$channel = PlatformChannel::factory()->create([
'platform_instance_id' => $instance->id,
'is_active' => true,
'is_active' => true
]);
$response = $this->postJson("/api/v1/platform-channels/{$channel->id}/toggle");
@ -226,12 +226,12 @@ public function test_toggle_deactivates_active_channel(): void
$response->assertStatus(200)
->assertJson([
'success' => true,
'message' => 'Platform channel deactivated successfully!',
'message' => 'Platform channel deactivated successfully!'
]);
$this->assertDatabaseHas('platform_channels', [
'id' => $channel->id,
'is_active' => false,
'is_active' => false
]);
}
}

View file

@ -23,13 +23,13 @@ public function test_index_returns_successful_response(): void
$feeds = Feed::factory()->count(3)->create(['language_id' => $language->id]);
$channels = PlatformChannel::factory()->count(3)->create([
'platform_instance_id' => $instance->id,
'language_id' => $language->id,
'language_id' => $language->id
]);
foreach ($feeds as $index => $feed) {
Route::factory()->create([
'feed_id' => $feed->id,
'platform_channel_id' => $channels[$index]->id,
'platform_channel_id' => $channels[$index]->id
]);
}
@ -47,12 +47,12 @@ public function test_index_returns_successful_response(): void
'priority',
'created_at',
'updated_at',
],
],
]
]
])
->assertJson([
'success' => true,
'message' => 'Routing configurations retrieved successfully.',
'message' => 'Routing configurations retrieved successfully.'
]);
}
@ -63,14 +63,14 @@ public function test_store_creates_routing_configuration_successfully(): void
$feed = Feed::factory()->create(['language_id' => $language->id]);
$channel = PlatformChannel::factory()->create([
'platform_instance_id' => $instance->id,
'language_id' => $language->id,
'language_id' => $language->id
]);
$data = [
'feed_id' => $feed->id,
'platform_channel_id' => $channel->id,
'is_active' => true,
'priority' => 5,
'priority' => 5
];
$response = $this->postJson('/api/v1/routing', $data);
@ -86,11 +86,11 @@ public function test_store_creates_routing_configuration_successfully(): void
'priority',
'created_at',
'updated_at',
],
]
])
->assertJson([
'success' => true,
'message' => 'Routing configuration created successfully!',
'message' => 'Routing configuration created successfully!'
]);
$this->assertDatabaseHas('routes', [
@ -115,12 +115,12 @@ public function test_store_validates_feed_exists(): void
$instance = PlatformInstance::factory()->create();
$channel = PlatformChannel::factory()->create([
'platform_instance_id' => $instance->id,
'language_id' => $language->id,
'language_id' => $language->id
]);
$data = [
'feed_id' => 999,
'platform_channel_id' => $channel->id,
'platform_channel_id' => $channel->id
];
$response = $this->postJson('/api/v1/routing', $data);
@ -136,7 +136,7 @@ public function test_store_validates_platform_channel_exists(): void
$data = [
'feed_id' => $feed->id,
'platform_channel_id' => 999,
'platform_channel_id' => 999
];
$response = $this->postJson('/api/v1/routing', $data);
@ -152,12 +152,12 @@ public function test_show_returns_routing_configuration_successfully(): void
$feed = Feed::factory()->create(['language_id' => $language->id]);
$channel = PlatformChannel::factory()->create([
'platform_instance_id' => $instance->id,
'language_id' => $language->id,
'language_id' => $language->id
]);
$route = Route::factory()->create([
'feed_id' => $feed->id,
'platform_channel_id' => $channel->id,
'platform_channel_id' => $channel->id
]);
$response = $this->getJson("/api/v1/routing/{$feed->id}/{$channel->id}");
@ -173,11 +173,11 @@ public function test_show_returns_routing_configuration_successfully(): void
'priority',
'created_at',
'updated_at',
],
]
])
->assertJson([
'success' => true,
'message' => 'Routing configuration retrieved successfully.',
'message' => 'Routing configuration retrieved successfully.'
]);
}
@ -188,7 +188,7 @@ public function test_show_returns_404_for_nonexistent_routing(): void
$feed = Feed::factory()->create(['language_id' => $language->id]);
$channel = PlatformChannel::factory()->create([
'platform_instance_id' => $instance->id,
'language_id' => $language->id,
'language_id' => $language->id
]);
$response = $this->getJson("/api/v1/routing/{$feed->id}/{$channel->id}");
@ -196,7 +196,7 @@ public function test_show_returns_404_for_nonexistent_routing(): void
$response->assertStatus(404)
->assertJson([
'success' => false,
'message' => 'Routing configuration not found.',
'message' => 'Routing configuration not found.'
]);
}
@ -207,19 +207,19 @@ public function test_update_modifies_routing_configuration_successfully(): void
$feed = Feed::factory()->create(['language_id' => $language->id]);
$channel = PlatformChannel::factory()->create([
'platform_instance_id' => $instance->id,
'language_id' => $language->id,
'language_id' => $language->id
]);
$route = Route::factory()->create([
'feed_id' => $feed->id,
'platform_channel_id' => $channel->id,
'is_active' => true,
'priority' => 1,
'priority' => 1
]);
$updateData = [
'is_active' => false,
'priority' => 10,
'priority' => 10
];
$response = $this->putJson("/api/v1/routing/{$feed->id}/{$channel->id}", $updateData);
@ -227,7 +227,7 @@ public function test_update_modifies_routing_configuration_successfully(): void
$response->assertStatus(200)
->assertJson([
'success' => true,
'message' => 'Routing configuration updated successfully!',
'message' => 'Routing configuration updated successfully!'
]);
$this->assertDatabaseHas('routes', [
@ -245,17 +245,17 @@ public function test_update_returns_404_for_nonexistent_routing(): void
$feed = Feed::factory()->create(['language_id' => $language->id]);
$channel = PlatformChannel::factory()->create([
'platform_instance_id' => $instance->id,
'language_id' => $language->id,
'language_id' => $language->id
]);
$response = $this->putJson("/api/v1/routing/{$feed->id}/{$channel->id}", [
'is_active' => false,
'is_active' => false
]);
$response->assertStatus(404)
->assertJson([
'success' => false,
'message' => 'Routing configuration not found.',
'message' => 'Routing configuration not found.'
]);
}
@ -266,12 +266,12 @@ public function test_destroy_deletes_routing_configuration_successfully(): void
$feed = Feed::factory()->create(['language_id' => $language->id]);
$channel = PlatformChannel::factory()->create([
'platform_instance_id' => $instance->id,
'language_id' => $language->id,
'language_id' => $language->id
]);
$route = Route::factory()->create([
'feed_id' => $feed->id,
'platform_channel_id' => $channel->id,
'platform_channel_id' => $channel->id
]);
$response = $this->deleteJson("/api/v1/routing/{$feed->id}/{$channel->id}");
@ -279,12 +279,12 @@ public function test_destroy_deletes_routing_configuration_successfully(): void
$response->assertStatus(200)
->assertJson([
'success' => true,
'message' => 'Routing configuration deleted successfully!',
'message' => 'Routing configuration deleted successfully!'
]);
$this->assertDatabaseMissing('routes', [
'feed_id' => $feed->id,
'platform_channel_id' => $channel->id,
'platform_channel_id' => $channel->id
]);
}
@ -295,7 +295,7 @@ public function test_destroy_returns_404_for_nonexistent_routing(): void
$feed = Feed::factory()->create(['language_id' => $language->id]);
$channel = PlatformChannel::factory()->create([
'platform_instance_id' => $instance->id,
'language_id' => $language->id,
'language_id' => $language->id
]);
$response = $this->deleteJson("/api/v1/routing/{$feed->id}/{$channel->id}");
@ -303,7 +303,7 @@ public function test_destroy_returns_404_for_nonexistent_routing(): void
$response->assertStatus(404)
->assertJson([
'success' => false,
'message' => 'Routing configuration not found.',
'message' => 'Routing configuration not found.'
]);
}
@ -314,13 +314,13 @@ public function test_toggle_activates_inactive_routing(): void
$feed = Feed::factory()->create(['language_id' => $language->id]);
$channel = PlatformChannel::factory()->create([
'platform_instance_id' => $instance->id,
'language_id' => $language->id,
'language_id' => $language->id
]);
$route = Route::factory()->create([
'feed_id' => $feed->id,
'platform_channel_id' => $channel->id,
'is_active' => false,
'is_active' => false
]);
$response = $this->postJson("/api/v1/routing/{$feed->id}/{$channel->id}/toggle");
@ -328,13 +328,13 @@ public function test_toggle_activates_inactive_routing(): void
$response->assertStatus(200)
->assertJson([
'success' => true,
'message' => 'Routing configuration activated successfully!',
'message' => 'Routing configuration activated successfully!'
]);
$this->assertDatabaseHas('routes', [
'feed_id' => $feed->id,
'platform_channel_id' => $channel->id,
'is_active' => true,
'is_active' => true
]);
}
@ -345,13 +345,13 @@ public function test_toggle_deactivates_active_routing(): void
$feed = Feed::factory()->create(['language_id' => $language->id]);
$channel = PlatformChannel::factory()->create([
'platform_instance_id' => $instance->id,
'language_id' => $language->id,
'language_id' => $language->id
]);
$route = Route::factory()->create([
'feed_id' => $feed->id,
'platform_channel_id' => $channel->id,
'is_active' => true,
'is_active' => true
]);
$response = $this->postJson("/api/v1/routing/{$feed->id}/{$channel->id}/toggle");
@ -359,13 +359,13 @@ public function test_toggle_deactivates_active_routing(): void
$response->assertStatus(200)
->assertJson([
'success' => true,
'message' => 'Routing configuration deactivated successfully!',
'message' => 'Routing configuration deactivated successfully!'
]);
$this->assertDatabaseHas('routes', [
'feed_id' => $feed->id,
'platform_channel_id' => $channel->id,
'is_active' => false,
'is_active' => false
]);
}
@ -376,7 +376,7 @@ public function test_toggle_returns_404_for_nonexistent_routing(): void
$feed = Feed::factory()->create(['language_id' => $language->id]);
$channel = PlatformChannel::factory()->create([
'platform_instance_id' => $instance->id,
'language_id' => $language->id,
'language_id' => $language->id
]);
$response = $this->postJson("/api/v1/routing/{$feed->id}/{$channel->id}/toggle");
@ -384,7 +384,7 @@ public function test_toggle_returns_404_for_nonexistent_routing(): void
$response->assertStatus(404)
->assertJson([
'success' => false,
'message' => 'Routing configuration not found.',
'message' => 'Routing configuration not found.'
]);
}
}

View file

@ -22,11 +22,11 @@ public function test_index_returns_current_settings(): void
'publishing_approvals_enabled',
'article_publishing_interval',
],
'message',
'message'
])
->assertJson([
'success' => true,
'message' => 'Settings retrieved successfully.',
'message' => 'Settings retrieved successfully.'
]);
}
@ -42,7 +42,7 @@ public function test_update_modifies_article_processing_setting(): void
'message' => 'Settings updated successfully.',
'data' => [
'article_processing_enabled' => false,
],
]
]);
}
@ -58,7 +58,7 @@ public function test_update_modifies_publishing_approvals_setting(): void
'message' => 'Settings updated successfully.',
'data' => [
'publishing_approvals_enabled' => true,
],
]
]);
}
@ -72,7 +72,7 @@ public function test_update_validates_boolean_values(): void
$response->assertStatus(422)
->assertJsonValidationErrors([
'article_processing_enabled',
'publishing_approvals_enabled',
'publishing_approvals_enabled'
]);
}
@ -88,7 +88,7 @@ public function test_update_accepts_partial_updates(): void
'success' => true,
'data' => [
'article_processing_enabled' => true,
],
]
]);
// Should still have structure for all settings
@ -97,7 +97,7 @@ public function test_update_accepts_partial_updates(): void
'article_processing_enabled',
'publishing_approvals_enabled',
'article_publishing_interval',
],
]
]);
}

View file

@ -18,10 +18,11 @@
use App\Models\Article;
use App\Models\Feed;
use App\Models\Log;
use App\Models\PlatformChannel;
use App\Models\Setting;
use App\Services\Article\ArticleFetcher;
use App\Models\PlatformChannel;
use App\Services\Log\LogSaver;
use App\Services\Article\ArticleFetcher;
use App\Services\Article\ValidationService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Queue;
@ -43,7 +44,7 @@ public function test_article_discovery_job_processes_successfully(): void
$feed = Feed::factory()->create(['is_active' => true]);
$logSaver = app(LogSaver::class);
$job = new ArticleDiscoveryJob;
$job = new ArticleDiscoveryJob();
$job->handle($logSaver);
// Should dispatch individual feed jobs
@ -56,7 +57,7 @@ public function test_article_discovery_for_feed_job_processes_feed(): void
$feed = Feed::factory()->create([
'url' => 'https://example.com/feed',
'is_active' => true,
'is_active' => true
]);
// Mock the ArticleFetcher service in the container
@ -93,9 +94,10 @@ public function test_sync_channel_posts_job_processes_successfully(): void
$this->assertTrue(true);
}
public function test_publish_next_article_job_has_correct_configuration(): void
{
$job = new PublishNextArticleJob;
$job = new PublishNextArticleJob();
$this->assertEquals('publishing', $job->queue);
$this->assertInstanceOf(PublishNextArticleJob::class, $job);
@ -162,12 +164,12 @@ public function test_exception_logged_event_is_dispatched(): void
$log = Log::factory()->create([
'level' => 'error',
'message' => 'Test error',
'context' => json_encode(['key' => 'value']),
'context' => json_encode(['key' => 'value'])
]);
event(new ExceptionLogged($log));
Event::assertDispatched(ExceptionLogged::class, function (ExceptionLogged $event) {
Event::assertDispatched(ExceptionLogged::class, function (ExceptionLogged $event) use ($log) {
return $event->log->message === 'Test error';
});
}
@ -193,7 +195,7 @@ public function test_validate_article_listener_processes_new_article(): void
->andReturn([
'title' => 'Belgian News',
'description' => 'News from Belgium',
'full_article' => 'This is a test article about Belgium and Belgian politics.',
'full_article' => 'This is a test article about Belgium and Belgian politics.'
]);
$listener = app(ValidateArticleListener::class);
@ -246,10 +248,10 @@ public function test_log_exception_to_database_listener_creates_log(): void
$log = Log::factory()->create([
'level' => 'error',
'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');
$event = new ExceptionOccurred($exception, \App\Enums\LogLevelEnum::ERROR, 'Test exception message');
@ -257,7 +259,7 @@ public function test_log_exception_to_database_listener_creates_log(): void
$this->assertDatabaseHas('logs', [
'level' => 'error',
'message' => 'Test exception message',
'message' => 'Test exception message'
]);
$savedLog = Log::where('message', 'Test exception message')->first();
@ -285,7 +287,7 @@ public function test_event_listener_registration_works(): void
public function test_job_retry_configuration(): void
{
$job = new PublishNextArticleJob;
$job = new PublishNextArticleJob();
// Test that job has unique configuration
$this->assertObjectHasProperty('uniqueFor', $job);
@ -298,9 +300,9 @@ public function test_job_queue_configuration(): void
$channel = PlatformChannel::factory()->create();
$article = Article::factory()->create(['feed_id' => $feed->id]);
$discoveryJob = new ArticleDiscoveryJob;
$discoveryJob = new ArticleDiscoveryJob();
$feedJob = new ArticleDiscoveryForFeedJob($feed);
$publishJob = new PublishNextArticleJob;
$publishJob = new PublishNextArticleJob();
$syncJob = new SyncChannelPostsJob($channel);
// Test queue assignments

View file

@ -2,7 +2,7 @@
namespace Tests\Feature;
use App\Events\ArticleApproved;
use App\Events\ArticleReadyToPublish;
use App\Events\NewArticleFetched;
use App\Listeners\ValidateArticleListener;
use App\Models\Article;
@ -20,11 +20,11 @@ class ValidateArticleListenerTest extends TestCase
public function test_listener_validates_article_and_dispatches_ready_to_publish_event(): void
{
Event::fake([ArticleApproved::class]);
Event::fake([ArticleReadyToPublish::class]);
// Mock HTTP requests
Http::fake([
'https://example.com/article' => Http::response('<html><body>Article content</body></html>', 200),
'https://example.com/article' => Http::response('<html><body>Article content</body></html>', 200)
]);
$feed = Feed::factory()->create();
@ -42,17 +42,17 @@ public function test_listener_validates_article_and_dispatches_ready_to_publish_
$article->refresh();
if ($article->isValid()) {
Event::assertDispatched(ArticleApproved::class, function (ArticleApproved $event) use ($article) {
Event::assertDispatched(ArticleReadyToPublish::class, function (ArticleReadyToPublish $event) use ($article) {
return $event->article->id === $article->id;
});
} else {
Event::assertNotDispatched(ArticleApproved::class);
Event::assertNotDispatched(ArticleReadyToPublish::class);
}
}
public function test_listener_skips_already_validated_articles(): void
{
Event::fake([ArticleApproved::class]);
Event::fake([ArticleReadyToPublish::class]);
$feed = Feed::factory()->create();
$article = Article::factory()->create([
@ -66,12 +66,12 @@ public function test_listener_skips_already_validated_articles(): void
$listener->handle($event);
Event::assertNotDispatched(ArticleApproved::class);
Event::assertNotDispatched(ArticleReadyToPublish::class);
}
public function test_listener_skips_articles_with_existing_publication(): void
{
Event::fake([ArticleApproved::class]);
Event::fake([ArticleReadyToPublish::class]);
$feed = Feed::factory()->create();
$article = Article::factory()->create([
@ -93,16 +93,16 @@ public function test_listener_skips_articles_with_existing_publication(): void
$listener->handle($event);
Event::assertNotDispatched(ArticleApproved::class);
Event::assertNotDispatched(ArticleReadyToPublish::class);
}
public function test_listener_calls_validation_service(): void
{
Event::fake([ArticleApproved::class]);
Event::fake([ArticleReadyToPublish::class]);
// Mock HTTP requests
Http::fake([
'https://example.com/article' => Http::response('<html><body>Article content</body></html>', 200),
'https://example.com/article' => Http::response('<html><body>Article content</body></html>', 200)
]);
$feed = Feed::factory()->create();

View file

@ -3,8 +3,8 @@
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Support\Facades\Facade;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Facade;
use Mockery;
abstract class TestCase extends BaseTestCase

View file

@ -21,7 +21,6 @@ protected function createArticleFetcher(?LogSaver $logSaver = null): ArticleFetc
return new ArticleFetcher($logSaver);
}
/** @return array{ArticleFetcher, \Mockery\MockInterface} */
protected function createArticleFetcherWithMockedLogSaver(): array
{
$logSaver = Mockery::mock(LogSaver::class);

View file

@ -19,7 +19,7 @@ class CreateChannelActionTest extends TestCase
protected function setUp(): void
{
parent::setUp();
$this->action = new CreateChannelAction;
$this->action = new CreateChannelAction();
}
public function test_creates_channel_and_attaches_account(): void
@ -44,7 +44,7 @@ public function test_creates_channel_and_attaches_account(): void
// Verify account is attached
$this->assertTrue($channel->platformAccounts->contains($account));
$this->assertEquals(1, $channel->platformAccounts->first()->pivot->priority); // @phpstan-ignore property.notFound
$this->assertEquals(1, $channel->platformAccounts->first()->pivot->priority);
}
public function test_creates_channel_without_language(): void
@ -57,7 +57,7 @@ public function test_creates_channel_without_language(): void
$channel = $this->action->execute('test_community', $instance->id);
$this->assertNull($channel->language_id); // @phpstan-ignore method.impossibleType
$this->assertNull($channel->language_id);
}
public function test_fails_when_no_active_accounts(): void

View file

@ -17,7 +17,7 @@ class CreateFeedActionTest extends TestCase
protected function setUp(): void
{
parent::setUp();
$this->action = new CreateFeedAction;
$this->action = new CreateFeedAction();
}
public function test_creates_vrt_feed_with_correct_url(): void
@ -45,7 +45,7 @@ public function test_creates_belga_feed_with_correct_url(): void
$this->assertEquals('https://www.belganewsagency.eu/', $feed->url);
$this->assertEquals('website', $feed->type);
$this->assertEquals('belga', $feed->provider);
$this->assertNull($feed->description); // @phpstan-ignore method.impossibleType
$this->assertNull($feed->description);
}
public function test_creates_guardian_feed_with_correct_url(): void
@ -57,7 +57,7 @@ public function test_creates_guardian_feed_with_correct_url(): void
$this->assertEquals('https://www.theguardian.com/international/rss', $feed->url);
$this->assertEquals('rss', $feed->type);
$this->assertEquals('guardian', $feed->provider);
$this->assertNull($feed->description); // @phpstan-ignore method.impossibleType
$this->assertNull($feed->description);
}
public function test_creates_vrt_feed_with_dutch_language(): void

View file

@ -17,8 +17,6 @@ class CreatePlatformAccountActionTest extends TestCase
use RefreshDatabase;
private CreatePlatformAccountAction $action;
/** @var LemmyAuthService&\Mockery\MockInterface */
private LemmyAuthService $lemmyAuthService;
protected function setUp(): void
@ -58,6 +56,7 @@ public function test_creates_platform_account_with_new_instance(): void
$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',

View file

@ -19,7 +19,7 @@ class CreateRouteActionTest extends TestCase
protected function setUp(): void
{
parent::setUp();
$this->action = new CreateRouteAction;
$this->action = new CreateRouteAction();
}
public function test_creates_route_with_defaults(): void
@ -69,7 +69,7 @@ public function test_returns_existing_route_for_duplicate_feed_channel_pair(): v
$first = $this->action->execute($feed->id, $channel->id, 10);
$second = $this->action->execute($feed->id, $channel->id, 99);
$this->assertEquals($first->id, $second->id); // @phpstan-ignore property.notFound, property.notFound
$this->assertEquals($first->id, $second->id);
$this->assertEquals(10, $second->priority);
$this->assertDatabaseCount('routes', 1);
}

View file

@ -83,8 +83,8 @@ public function test_enum_can_be_compared(): void
$debug2 = LogLevelEnum::DEBUG;
$info = LogLevelEnum::INFO;
$this->assertTrue($debug1 === $debug2); // @phpstan-ignore identical.alwaysTrue
$this->assertFalse($debug1 === $info); // @phpstan-ignore identical.alwaysFalse, method.impossibleType
$this->assertTrue($debug1 === $debug2);
$this->assertFalse($debug1 === $info);
}
public function test_enum_can_be_used_in_match_expression(): void

View file

@ -54,7 +54,7 @@ public function test_enum_can_be_compared(): void
$lemmy1 = PlatformEnum::LEMMY;
$lemmy2 = PlatformEnum::LEMMY;
$this->assertTrue($lemmy1 === $lemmy2); // @phpstan-ignore identical.alwaysTrue
$this->assertTrue($lemmy1 === $lemmy2);
}
public function test_enum_can_be_used_in_match_expression(): void

View file

@ -3,11 +3,11 @@
namespace Tests\Unit\Facades;
use App\Enums\LogLevelEnum;
use App\Enums\PlatformEnum;
use App\Facades\LogSaver;
use App\Models\Log;
use App\Models\PlatformChannel;
use App\Models\PlatformInstance;
use App\Enums\PlatformEnum;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
@ -92,12 +92,12 @@ public function test_facade_works_with_channel(): void
{
$platformInstance = PlatformInstance::factory()->create([
'platform' => PlatformEnum::LEMMY,
'url' => 'https://facade.test.com',
'url' => 'https://facade.test.com'
]);
$channel = PlatformChannel::factory()->create([
'name' => 'Facade Test Channel',
'platform_instance_id' => $platformInstance->id,
'platform_instance_id' => $platformInstance->id
]);
$message = 'Facade channel test';

Some files were not shown because too many files have changed in this diff Show more