From f3406b171397f7009fb0518b33df9626ee2510dc Mon Sep 17 00:00:00 2001 From: myrmidex Date: Wed, 18 Mar 2026 16:23:46 +0100 Subject: [PATCH] 85 - Remove approval_status from Article, migrate to route_articles --- .../Controllers/Api/V1/ArticlesController.php | 34 ---- app/Http/Resources/ArticleResource.php | 2 - .../PublishApprovedArticleListener.php | 5 - app/Models/Article.php | 58 ------ .../Publishing/ArticlePublishingService.php | 4 - database/factories/ArticleFactory.php | 1 - ...eywords_and_approval_to_route_articles.php | 61 ++++++ routes/api.php | 2 - tests/Feature/DatabaseIntegrationTest.php | 2 - .../Api/V1/ArticlesControllerTest.php | 54 ----- tests/Feature/JobsAndEventsTest.php | 1 - .../PublishApprovedArticleListenerTest.php | 6 +- tests/Feature/ValidateArticleListenerTest.php | 3 - tests/Unit/Models/ArticleTest.php | 189 +++--------------- .../ArticlePublishingServiceTest.php | 34 +--- 15 files changed, 100 insertions(+), 356 deletions(-) create mode 100644 database/migrations/2024_01_01_000009_migrate_keywords_and_approval_to_route_articles.php diff --git a/app/Http/Controllers/Api/V1/ArticlesController.php b/app/Http/Controllers/Api/V1/ArticlesController.php index 7fa1d97..e565749 100644 --- a/app/Http/Controllers/Api/V1/ArticlesController.php +++ b/app/Http/Controllers/Api/V1/ArticlesController.php @@ -40,40 +40,6 @@ public function index(Request $request): JsonResponse ]); } - /** - * Approve an article - */ - public function approve(Article $article): JsonResponse - { - try { - $article->approve('manual'); - - return $this->sendResponse( - new ArticleResource($article->fresh(['feed', 'articlePublication'])), - 'Article approved and queued for publishing.' - ); - } catch (Exception $e) { - return $this->sendError('Failed to approve article: '.$e->getMessage(), [], 500); - } - } - - /** - * Reject an article - */ - public function reject(Article $article): JsonResponse - { - try { - $article->reject('manual'); - - return $this->sendResponse( - new ArticleResource($article->fresh(['feed', 'articlePublication'])), - 'Article rejected.' - ); - } catch (Exception $e) { - return $this->sendError('Failed to reject article: '.$e->getMessage(), [], 500); - } - } - /** * Manually refresh articles from all active feeds */ diff --git a/app/Http/Resources/ArticleResource.php b/app/Http/Resources/ArticleResource.php index 36dbbbf..086b1eb 100644 --- a/app/Http/Resources/ArticleResource.php +++ b/app/Http/Resources/ArticleResource.php @@ -21,8 +21,6 @@ public function toArray(Request $request): array 'url' => $this->url, 'title' => $this->title, 'description' => $this->description, - 'is_valid' => $this->is_valid, - 'approval_status' => $this->approval_status, 'publish_status' => $this->publish_status, 'validated_at' => $this->validated_at?->toISOString(), 'is_published' => $this->relationLoaded('articlePublication') && $this->articlePublication !== null, diff --git a/app/Listeners/PublishApprovedArticleListener.php b/app/Listeners/PublishApprovedArticleListener.php index aab3ddf..0bb3243 100644 --- a/app/Listeners/PublishApprovedArticleListener.php +++ b/app/Listeners/PublishApprovedArticleListener.php @@ -32,11 +32,6 @@ public function handle(ArticleApproved $event): void return; } - // Skip if not approved (safety check) - if (! $article->isApproved()) { - return; - } - $article->update(['publish_status' => 'publishing']); try { diff --git a/app/Models/Article.php b/app/Models/Article.php index bb834a0..6156321 100644 --- a/app/Models/Article.php +++ b/app/Models/Article.php @@ -2,7 +2,6 @@ namespace App\Models; -use App\Events\ArticleApproved; use App\Events\NewArticleFetched; use Database\Factories\ArticleFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -23,14 +22,11 @@ * @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 \Illuminate\Support\HigherOrderCollectionProxy|mixed $routeArticles */ class Article extends Model { @@ -46,7 +42,6 @@ class Article extends Model 'image_url', 'published_at', 'author', - 'approval_status', 'validated_at', 'publish_status', ]; @@ -57,7 +52,6 @@ class Article extends Model public function casts(): array { return [ - 'approval_status' => 'string', 'publish_status' => 'string', 'published_at' => 'datetime', 'validated_at' => 'datetime', @@ -66,58 +60,6 @@ public function casts(): array ]; } - public function isValid(): bool - { - return $this->validated_at !== null && ! $this->isRejected(); - } - - public function isApproved(): bool - { - return $this->approval_status === 'approved'; - } - - public function isPending(): bool - { - return $this->approval_status === 'pending'; - } - - public function isRejected(): bool - { - return $this->approval_status === 'rejected'; - } - - public function approve(?string $approvedBy = null): void - { - $this->update([ - 'approval_status' => 'approved', - ]); - - // Fire event to trigger publishing - event(new ArticleApproved($this)); - } - - public function reject(?string $rejectedBy = null): void - { - $this->update([ - 'approval_status' => 'rejected', - ]); - } - - public function canBePublished(): bool - { - if (! $this->isValid()) { - return false; - } - - // If approval system is disabled, auto-approve valid articles - if (! \App\Models\Setting::isPublishingApprovalsEnabled()) { - return true; - } - - // If approval system is enabled, only approved articles can be published - return $this->isApproved(); - } - public function getIsPublishedAttribute(): bool { return $this->articlePublication()->exists(); diff --git a/app/Services/Publishing/ArticlePublishingService.php b/app/Services/Publishing/ArticlePublishingService.php index 1d8d271..bac0db9 100644 --- a/app/Services/Publishing/ArticlePublishingService.php +++ b/app/Services/Publishing/ArticlePublishingService.php @@ -73,10 +73,6 @@ public function publishRouteArticle(RouteArticle $routeArticle, array $extracted */ public function publishToRoutedChannels(Article $article, array $extractedData): Collection { - if (! $article->isValid()) { - throw new PublishException($article, PlatformEnum::LEMMY, new RuntimeException('CANNOT_PUBLISH_INVALID_ARTICLE')); - } - $feed = $article->feed; $activeRoutes = Route::where('feed_id', $feed->id) diff --git a/database/factories/ArticleFactory.php b/database/factories/ArticleFactory.php index f35645d..f236a63 100644 --- a/database/factories/ArticleFactory.php +++ b/database/factories/ArticleFactory.php @@ -25,7 +25,6 @@ public function definition(): array 'image_url' => $this->faker->optional()->imageUrl(), 'published_at' => $this->faker->optional()->dateTimeBetween('-1 month', 'now'), 'author' => $this->faker->optional()->name(), - 'approval_status' => $this->faker->randomElement(['pending', 'approved', 'rejected']), 'publish_status' => 'unpublished', ]; } diff --git a/database/migrations/2024_01_01_000009_migrate_keywords_and_approval_to_route_articles.php b/database/migrations/2024_01_01_000009_migrate_keywords_and_approval_to_route_articles.php new file mode 100644 index 0000000..307dc0f --- /dev/null +++ b/database/migrations/2024_01_01_000009_migrate_keywords_and_approval_to_route_articles.php @@ -0,0 +1,61 @@ +whereIn('approval_status', ['approved', 'rejected']) + ->whereNotNull('validated_at') + ->get(); + + foreach ($validatedArticles as $article) { + $routes = DB::table('routes') + ->where('feed_id', $article->feed_id) + ->where('is_active', true) + ->get(); + + foreach ($routes as $route) { + $exists = DB::table('route_articles') + ->where('feed_id', $route->feed_id) + ->where('platform_channel_id', $route->platform_channel_id) + ->where('article_id', $article->id) + ->exists(); + + if ($exists) { + continue; + } + + DB::table('route_articles')->insert([ + 'feed_id' => $route->feed_id, + 'platform_channel_id' => $route->platform_channel_id, + 'article_id' => $article->id, + 'approval_status' => $article->approval_status, + 'validated_at' => $article->validated_at, + 'created_at' => $article->created_at, + 'updated_at' => now(), + ]); + } + } + + // Remove approval_status column from articles + Schema::table('articles', function (Blueprint $table) { + $table->dropIndex(['published_at', 'approval_status']); + $table->dropColumn('approval_status'); + }); + } + + public function down(): void + { + Schema::table('articles', function (Blueprint $table) { + $table->enum('approval_status', ['pending', 'approved', 'rejected'])->default('pending')->after('feed_id'); + $table->index(['published_at', 'approval_status']); + }); + } +}; diff --git a/routes/api.php b/routes/api.php index 7e12a04..0ab6dfc 100644 --- a/routes/api.php +++ b/routes/api.php @@ -47,8 +47,6 @@ // Articles Route::get('/articles', [ArticlesController::class, 'index'])->name('api.articles.index'); - Route::post('/articles/{article}/approve', [ArticlesController::class, 'approve'])->name('api.articles.approve'); - Route::post('/articles/{article}/reject', [ArticlesController::class, 'reject'])->name('api.articles.reject'); Route::post('/articles/refresh', [ArticlesController::class, 'refresh'])->name('api.articles.refresh'); // Platform Accounts diff --git a/tests/Feature/DatabaseIntegrationTest.php b/tests/Feature/DatabaseIntegrationTest.php index e7c9a1d..982ec93 100644 --- a/tests/Feature/DatabaseIntegrationTest.php +++ b/tests/Feature/DatabaseIntegrationTest.php @@ -134,14 +134,12 @@ public function test_article_model_creates_successfully(): void 'feed_id' => $feed->id, 'title' => 'Test Article', 'url' => 'https://example.com/article', - 'approval_status' => 'pending', ]); $this->assertDatabaseHas('articles', [ 'feed_id' => $feed->id, 'title' => 'Test Article', 'url' => 'https://example.com/article', - 'approval_status' => 'pending', ]); $this->assertEquals($feed->id, $article->feed->id); diff --git a/tests/Feature/Http/Controllers/Api/V1/ArticlesControllerTest.php b/tests/Feature/Http/Controllers/Api/V1/ArticlesControllerTest.php index 9b16f24..ced053d 100644 --- a/tests/Feature/Http/Controllers/Api/V1/ArticlesControllerTest.php +++ b/tests/Feature/Http/Controllers/Api/V1/ArticlesControllerTest.php @@ -102,60 +102,6 @@ public function test_index_orders_articles_by_created_at_desc(): void $this->assertEquals('First Article', $articles[1]['title']); } - public function test_approve_article_successfully(): void - { - $feed = Feed::factory()->create(); - $article = Article::factory()->create([ - 'feed_id' => $feed->id, - 'approval_status' => 'pending', - ]); - - $response = $this->postJson("/api/v1/articles/{$article->id}/approve"); - - $response->assertStatus(200) - ->assertJson([ - 'success' => true, - 'message' => 'Article approved and queued for publishing.', - ]); - - $article->refresh(); - $this->assertEquals('approved', $article->approval_status); - } - - public function test_approve_nonexistent_article_returns_404(): void - { - $response = $this->postJson('/api/v1/articles/999/approve'); - - $response->assertStatus(404); - } - - public function test_reject_article_successfully(): void - { - $feed = Feed::factory()->create(); - $article = Article::factory()->create([ - 'feed_id' => $feed->id, - 'approval_status' => 'pending', - ]); - - $response = $this->postJson("/api/v1/articles/{$article->id}/reject"); - - $response->assertStatus(200) - ->assertJson([ - 'success' => true, - 'message' => 'Article rejected.', - ]); - - $article->refresh(); - $this->assertEquals('rejected', $article->approval_status); - } - - public function test_reject_nonexistent_article_returns_404(): void - { - $response = $this->postJson('/api/v1/articles/999/reject'); - - $response->assertStatus(404); - } - public function test_index_includes_settings(): void { $response = $this->getJson('/api/v1/articles'); diff --git a/tests/Feature/JobsAndEventsTest.php b/tests/Feature/JobsAndEventsTest.php index c398cbc..aa76731 100644 --- a/tests/Feature/JobsAndEventsTest.php +++ b/tests/Feature/JobsAndEventsTest.php @@ -188,7 +188,6 @@ public function test_validate_article_listener_processes_new_article(): void $article = Article::factory()->create([ 'feed_id' => $feed->id, - 'approval_status' => 'pending', ]); // Mock ArticleFetcher to return valid article data diff --git a/tests/Feature/Listeners/PublishApprovedArticleListenerTest.php b/tests/Feature/Listeners/PublishApprovedArticleListenerTest.php index cf1ffb1..b23ac11 100644 --- a/tests/Feature/Listeners/PublishApprovedArticleListenerTest.php +++ b/tests/Feature/Listeners/PublishApprovedArticleListenerTest.php @@ -27,7 +27,7 @@ public function test_exception_during_publishing_creates_error_notification(): v $feed = Feed::factory()->create(); $article = Article::factory()->create([ 'feed_id' => $feed->id, - 'approval_status' => 'approved', + 'title' => 'Test Article', ]); @@ -58,7 +58,7 @@ public function test_no_publications_created_creates_warning_notification(): voi $feed = Feed::factory()->create(); $article = Article::factory()->create([ 'feed_id' => $feed->id, - 'approval_status' => 'approved', + 'title' => 'Test Article', ]); @@ -93,7 +93,7 @@ public function test_successful_publish_does_not_create_notification(): void $feed = Feed::factory()->create(); $article = Article::factory()->create([ 'feed_id' => $feed->id, - 'approval_status' => 'approved', + 'title' => 'Test Article', ]); diff --git a/tests/Feature/ValidateArticleListenerTest.php b/tests/Feature/ValidateArticleListenerTest.php index a5412ca..88a67e1 100644 --- a/tests/Feature/ValidateArticleListenerTest.php +++ b/tests/Feature/ValidateArticleListenerTest.php @@ -54,7 +54,6 @@ public function test_listener_validates_article_and_creates_route_articles(): vo $article = Article::factory()->create([ 'feed_id' => $feed->id, - 'approval_status' => 'pending', ]); $listener = $this->createListenerWithMockedFetcher('Article about Belgium'); @@ -91,7 +90,6 @@ public function test_listener_skips_articles_with_existing_publication(): void $article = Article::factory()->create([ 'feed_id' => $feed->id, - 'approval_status' => 'pending', ]); ArticlePublication::create([ @@ -120,7 +118,6 @@ public function test_listener_handles_validation_errors_gracefully(): void $feed = Feed::factory()->create(); $article = Article::factory()->create([ 'feed_id' => $feed->id, - 'approval_status' => 'pending', ]); $listener->handle(new NewArticleFetched($article)); diff --git a/tests/Unit/Models/ArticleTest.php b/tests/Unit/Models/ArticleTest.php index ed9cd0d..ead0e8b 100644 --- a/tests/Unit/Models/ArticleTest.php +++ b/tests/Unit/Models/ArticleTest.php @@ -2,11 +2,9 @@ namespace Tests\Unit\Models; -use App\Events\ArticleApproved; use App\Events\NewArticleFetched; use App\Models\Article; use App\Models\Feed; -use App\Models\Setting; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Http; @@ -20,169 +18,9 @@ protected function setUp(): void { parent::setUp(); - // Mock HTTP requests to prevent external calls Http::fake([ '*' => Http::response('', 500), ]); - - // Don't fake events globally - let individual tests control this - } - - public function test_is_valid_returns_false_when_approval_status_is_pending(): void - { - $article = Article::factory()->make([ - 'approval_status' => 'pending', - ]); - - $this->assertFalse($article->isValid()); - } - - public function test_is_valid_returns_false_when_approval_status_is_rejected(): void - { - $article = Article::factory()->make([ - 'approval_status' => 'rejected', - ]); - - $this->assertFalse($article->isValid()); - } - - public function test_is_valid_returns_true_when_validated_and_not_rejected(): void - { - $article = Article::factory()->make([ - 'approval_status' => 'approved', - 'validated_at' => now(), - ]); - - $this->assertTrue($article->isValid()); - } - - public function test_is_valid_returns_false_when_not_validated(): void - { - $article = Article::factory()->make([ - 'approval_status' => 'approved', - 'validated_at' => null, - ]); - - $this->assertFalse($article->isValid()); - } - - public function test_is_approved_returns_true_for_approved_status(): void - { - $article = Article::factory()->make(['approval_status' => 'approved']); - - $this->assertTrue($article->isApproved()); - } - - public function test_is_approved_returns_false_for_non_approved_status(): void - { - $article = Article::factory()->make(['approval_status' => 'pending']); - - $this->assertFalse($article->isApproved()); - } - - public function test_is_pending_returns_true_for_pending_status(): void - { - $article = Article::factory()->make(['approval_status' => 'pending']); - - $this->assertTrue($article->isPending()); - } - - public function test_is_rejected_returns_true_for_rejected_status(): void - { - $article = Article::factory()->make(['approval_status' => 'rejected']); - - $this->assertTrue($article->isRejected()); - } - - public function test_approve_updates_status_and_triggers_event(): void - { - $feed = Feed::factory()->create(); - $article = Article::factory()->create([ - 'feed_id' => $feed->id, - 'approval_status' => 'pending', - ]); - - Event::fake(); - - $article->approve('test_user'); - - $article->refresh(); - $this->assertEquals('approved', $article->approval_status); - - Event::assertDispatched(ArticleApproved::class, function ($event) use ($article) { - return $event->article->id === $article->id; - }); - } - - public function test_approve_without_approved_by_parameter(): void - { - $feed = Feed::factory()->create(); - $article = Article::factory()->create([ - 'feed_id' => $feed->id, - 'approval_status' => 'pending', - ]); - - Event::fake(); - - $article->approve(); - - $article->refresh(); - $this->assertEquals('approved', $article->approval_status); - } - - public function test_reject_updates_status(): void - { - $feed = Feed::factory()->create(); - $article = Article::factory()->create([ - 'feed_id' => $feed->id, - 'approval_status' => 'pending', - ]); - - $article->reject('test_user'); - - $article->refresh(); - $this->assertEquals('rejected', $article->approval_status); - } - - public function test_can_be_published_returns_false_for_invalid_article(): void - { - $article = Article::factory()->make([ - 'approval_status' => 'rejected', // rejected = not valid - ]); - - $this->assertFalse($article->canBePublished()); - } - - public function test_can_be_published_requires_approval_when_approvals_enabled(): void - { - // Create a setting that enables approvals - Setting::create(['key' => 'enable_publishing_approvals', 'value' => '1']); - - $pendingArticle = Article::factory()->make([ - 'approval_status' => 'pending', - 'validated_at' => now(), - ]); - - $approvedArticle = Article::factory()->make([ - 'approval_status' => 'approved', - 'validated_at' => now(), - ]); - - $this->assertFalse($pendingArticle->canBePublished()); - $this->assertTrue($approvedArticle->canBePublished()); - } - - public function test_can_be_published_returns_true_when_approvals_disabled(): void - { - // Make sure approvals are disabled (default behavior) - Setting::where('key', 'enable_publishing_approvals')->delete(); - - $article = Article::factory()->make([ - 'approval_status' => 'approved', - 'validated_at' => now(), - ]); - - $this->assertTrue($article->canBePublished()); } public function test_feed_relationship(): void @@ -194,6 +32,14 @@ public function test_feed_relationship(): void $this->assertEquals($feed->id, $article->feed->id); } + public function test_route_articles_relationship(): void + { + $feed = Feed::factory()->create(); + $article = Article::factory()->create(['feed_id' => $feed->id]); + + $this->assertCount(0, $article->routeArticles); + } + public function test_dispatch_fetched_event_fires_new_article_fetched_event(): void { Event::fake([NewArticleFetched::class]); @@ -207,4 +53,23 @@ public function test_dispatch_fetched_event_fires_new_article_fetched_event(): v return $event->article->id === $article->id; }); } + + public function test_is_published_attribute(): void + { + $feed = Feed::factory()->create(); + $article = Article::factory()->create(['feed_id' => $feed->id]); + + $this->assertFalse($article->is_published); + } + + public function test_validated_at_is_cast_to_datetime(): void + { + $feed = Feed::factory()->create(); + $article = Article::factory()->create([ + 'feed_id' => $feed->id, + 'validated_at' => now(), + ]); + + $this->assertInstanceOf(\Illuminate\Support\Carbon::class, $article->validated_at); + } } diff --git a/tests/Unit/Services/Publishing/ArticlePublishingServiceTest.php b/tests/Unit/Services/Publishing/ArticlePublishingServiceTest.php index 4302a83..40e2c8d 100644 --- a/tests/Unit/Services/Publishing/ArticlePublishingServiceTest.php +++ b/tests/Unit/Services/Publishing/ArticlePublishingServiceTest.php @@ -3,7 +3,6 @@ namespace Tests\Unit\Services\Publishing; use App\Enums\PlatformEnum; -use App\Exceptions\PublishException; use App\Models\Article; use App\Models\Feed; use App\Models\PlatformAccount; @@ -45,23 +44,12 @@ protected function tearDown(): void parent::tearDown(); } - public function test_publish_to_routed_channels_throws_exception_for_invalid_article(): void - { - $article = Article::factory()->create(['approval_status' => 'rejected']); - $extractedData = ['title' => 'Test Title']; - - $this->expectException(PublishException::class); - $this->expectExceptionMessage('CANNOT_PUBLISH_INVALID_ARTICLE'); - - $this->service->publishToRoutedChannels($article, $extractedData); - } - public function test_publish_to_routed_channels_returns_empty_collection_when_no_active_routes(): void { $feed = Feed::factory()->create(); $article = Article::factory()->create([ 'feed_id' => $feed->id, - 'approval_status' => 'approved', + 'validated_at' => now(), ]); $extractedData = ['title' => 'Test Title']; @@ -78,7 +66,7 @@ public function test_publish_to_routed_channels_skips_routes_without_active_acco $feed = Feed::factory()->create(); $article = Article::factory()->create([ 'feed_id' => $feed->id, - 'approval_status' => 'approved', + 'validated_at' => now(), ]); @@ -106,8 +94,7 @@ public function test_publish_to_routed_channels_successfully_publishes_to_channe { // Arrange $feed = Feed::factory()->create(); - $article = Article::factory()->create(['feed_id' => $feed->id, 'approval_status' => 'approved', - 'validated_at' => now()]); + $article = Article::factory()->create(['feed_id' => $feed->id, 'validated_at' => now()]); $platformInstance = PlatformInstance::factory()->create(); $channel = PlatformChannel::factory()->create(['platform_instance_id' => $platformInstance->id]); @@ -153,8 +140,7 @@ public function test_publish_to_routed_channels_handles_publishing_failure_grace { // Arrange $feed = Feed::factory()->create(); - $article = Article::factory()->create(['feed_id' => $feed->id, 'approval_status' => 'approved', - 'validated_at' => now()]); + $article = Article::factory()->create(['feed_id' => $feed->id, 'validated_at' => now()]); $platformInstance = PlatformInstance::factory()->create(); $channel = PlatformChannel::factory()->create(['platform_instance_id' => $platformInstance->id]); @@ -195,8 +181,7 @@ public function test_publish_to_routed_channels_publishes_to_multiple_routes(): { // Arrange $feed = Feed::factory()->create(); - $article = Article::factory()->create(['feed_id' => $feed->id, 'approval_status' => 'approved', - 'validated_at' => now()]); + $article = Article::factory()->create(['feed_id' => $feed->id, 'validated_at' => now()]); $platformInstance = PlatformInstance::factory()->create(); $channel1 = PlatformChannel::factory()->create(['platform_instance_id' => $platformInstance->id]); @@ -251,8 +236,7 @@ public function test_publish_to_routed_channels_filters_out_failed_publications( { // Arrange $feed = Feed::factory()->create(); - $article = Article::factory()->create(['feed_id' => $feed->id, 'approval_status' => 'approved', - 'validated_at' => now()]); + $article = Article::factory()->create(['feed_id' => $feed->id, 'validated_at' => now()]); $platformInstance = PlatformInstance::factory()->create(); $channel1 = PlatformChannel::factory()->create(['platform_instance_id' => $platformInstance->id]); @@ -309,7 +293,7 @@ public function test_publish_skips_duplicate_when_url_already_posted_to_channel( $feed = Feed::factory()->create(); $article = Article::factory()->create([ 'feed_id' => $feed->id, - 'approval_status' => 'approved', + 'validated_at' => now(), 'url' => 'https://example.com/article-1', ]); @@ -361,7 +345,7 @@ public function test_publish_skips_duplicate_when_title_already_posted_to_channe $feed = Feed::factory()->create(); $article = Article::factory()->create([ 'feed_id' => $feed->id, - 'approval_status' => 'approved', + 'validated_at' => now(), 'url' => 'https://example.com/article-new-url', 'title' => 'Breaking News: Something Happened', @@ -414,7 +398,7 @@ public function test_publish_proceeds_when_no_duplicate_exists(): void $feed = Feed::factory()->create(); $article = Article::factory()->create([ 'feed_id' => $feed->id, - 'approval_status' => 'approved', + 'validated_at' => now(), 'url' => 'https://example.com/unique-article', ]);