From 5e571babdaeae567cfef0a95cc3feee1eb7f4409 Mon Sep 17 00:00:00 2001 From: myrmidex Date: Wed, 18 Mar 2026 16:35:12 +0100 Subject: [PATCH] 85 - Fix keyword matching to include title and description, add PHPStan type annotations --- app/Services/Article/ValidationService.php | 5 ++++- tests/Feature/JobsAndEventsTest.php | 1 + tests/Feature/ValidateArticleListenerTest.php | 1 + tests/Unit/Jobs/PublishNextArticleJobTest.php | 12 +++++++++++- tests/Unit/Models/RouteArticleTest.php | 15 ++++++++++++++- tests/Unit/Services/ValidationServiceTest.php | 9 +++++++++ 6 files changed, 40 insertions(+), 3 deletions(-) diff --git a/app/Services/Article/ValidationService.php b/app/Services/Article/ValidationService.php index aca2f8f..431417b 100644 --- a/app/Services/Article/ValidationService.php +++ b/app/Services/Article/ValidationService.php @@ -62,9 +62,12 @@ private function createRouteArticles(Article $article, string $content): void ->get() ->groupBy('platform_channel_id'); + // Match keywords against full article content, title, and description + $searchableContent = $content.' '.$article->title.' '.$article->description; + foreach ($activeRoutes as $route) { $routeKeywords = $keywordsByChannel->get($route->platform_channel_id, collect()); - $status = $this->evaluateKeywords($routeKeywords, $content); + $status = $this->evaluateKeywords($routeKeywords, $searchableContent); if ($status === ApprovalStatusEnum::PENDING && $this->shouldAutoApprove($route)) { $status = ApprovalStatusEnum::APPROVED; diff --git a/tests/Feature/JobsAndEventsTest.php b/tests/Feature/JobsAndEventsTest.php index aa76731..9c2e3ef 100644 --- a/tests/Feature/JobsAndEventsTest.php +++ b/tests/Feature/JobsAndEventsTest.php @@ -179,6 +179,7 @@ public function test_validate_article_listener_processes_new_article(): void Setting::setBool('enable_publishing_approvals', false); $feed = Feed::factory()->create(); + /** @var \App\Models\Route $route */ $route = \App\Models\Route::factory()->active()->create(['feed_id' => $feed->id]); \App\Models\Keyword::factory()->active()->create([ 'feed_id' => $feed->id, diff --git a/tests/Feature/ValidateArticleListenerTest.php b/tests/Feature/ValidateArticleListenerTest.php index 88a67e1..57529a6 100644 --- a/tests/Feature/ValidateArticleListenerTest.php +++ b/tests/Feature/ValidateArticleListenerTest.php @@ -45,6 +45,7 @@ protected function tearDown(): void public function test_listener_validates_article_and_creates_route_articles(): void { $feed = Feed::factory()->create(); + /** @var Route $route */ $route = Route::factory()->active()->create(['feed_id' => $feed->id]); Keyword::factory()->active()->create([ 'feed_id' => $feed->id, diff --git a/tests/Unit/Jobs/PublishNextArticleJobTest.php b/tests/Unit/Jobs/PublishNextArticleJobTest.php index e37079a..694789a 100644 --- a/tests/Unit/Jobs/PublishNextArticleJobTest.php +++ b/tests/Unit/Jobs/PublishNextArticleJobTest.php @@ -33,15 +33,23 @@ protected function setUp(): void $this->notificationService = new NotificationService; } + /** + * @param array $articleOverrides + * @param array $routeOverrides + */ private function createApprovedRouteArticle(array $articleOverrides = [], array $routeOverrides = []): RouteArticle { $feed = Feed::factory()->create(); + /** @var Route $route */ $route = Route::factory()->active()->create(array_merge(['feed_id' => $feed->id], $routeOverrides)); $article = Article::factory()->create(array_merge(['feed_id' => $feed->id], $articleOverrides)); - return RouteArticle::factory()->forRoute($route)->approved()->create([ + /** @var RouteArticle $routeArticle */ + $routeArticle = RouteArticle::factory()->forRoute($route)->approved()->create([ 'article_id' => $article->id, ]); + + return $routeArticle; } public function test_constructor_sets_correct_queue(): void @@ -112,6 +120,7 @@ public function test_handle_returns_early_when_no_unpublished_approved_route_art public function test_handle_skips_non_approved_route_articles(): void { $feed = Feed::factory()->create(); + /** @var Route $route */ $route = Route::factory()->active()->create(['feed_id' => $feed->id]); $article = Article::factory()->create(['feed_id' => $feed->id]); @@ -129,6 +138,7 @@ public function test_handle_skips_non_approved_route_articles(): void public function test_handle_publishes_oldest_approved_route_article(): void { $feed = Feed::factory()->create(); + /** @var Route $route */ $route = Route::factory()->active()->create(['feed_id' => $feed->id]); $olderArticle = Article::factory()->create(['feed_id' => $feed->id]); diff --git a/tests/Unit/Models/RouteArticleTest.php b/tests/Unit/Models/RouteArticleTest.php index 4ccd826..f5229e9 100644 --- a/tests/Unit/Models/RouteArticleTest.php +++ b/tests/Unit/Models/RouteArticleTest.php @@ -17,6 +17,7 @@ class RouteArticleTest extends TestCase public function test_route_article_belongs_to_article(): void { + /** @var RouteArticle $routeArticle */ $routeArticle = RouteArticle::factory()->create(); $this->assertInstanceOf(Article::class, $routeArticle->article); @@ -24,6 +25,7 @@ public function test_route_article_belongs_to_article(): void public function test_route_article_belongs_to_feed(): void { + /** @var RouteArticle $routeArticle */ $routeArticle = RouteArticle::factory()->create(); $this->assertInstanceOf(Feed::class, $routeArticle->feed); @@ -31,6 +33,7 @@ public function test_route_article_belongs_to_feed(): void public function test_route_article_belongs_to_platform_channel(): void { + /** @var RouteArticle $routeArticle */ $routeArticle = RouteArticle::factory()->create(); $this->assertInstanceOf(PlatformChannel::class, $routeArticle->platformChannel); @@ -38,6 +41,7 @@ public function test_route_article_belongs_to_platform_channel(): void public function test_route_article_has_default_pending_status(): void { + /** @var RouteArticle $routeArticle */ $routeArticle = RouteArticle::factory()->create(); $this->assertEquals(ApprovalStatusEnum::PENDING, $routeArticle->approval_status); @@ -48,15 +52,16 @@ public function test_route_article_has_default_pending_status(): void public function test_route_article_can_be_approved(): void { + /** @var RouteArticle $routeArticle */ $routeArticle = RouteArticle::factory()->create(); $routeArticle->approve(); - $this->assertEquals(ApprovalStatusEnum::APPROVED, $routeArticle->fresh()->approval_status); } public function test_route_article_can_be_rejected(): void { + /** @var RouteArticle $routeArticle */ $routeArticle = RouteArticle::factory()->create(); $routeArticle->reject(); @@ -66,7 +71,9 @@ public function test_route_article_can_be_rejected(): void public function test_article_has_many_route_articles(): void { + /** @var Route $route1 */ $route1 = Route::factory()->active()->create(); + /** @var Route $route2 */ $route2 = Route::factory()->active()->create(); $article = Article::factory()->create(['feed_id' => $route1->feed_id]); @@ -78,6 +85,7 @@ public function test_article_has_many_route_articles(): void public function test_route_has_many_route_articles(): void { + /** @var Route $route */ $route = Route::factory()->active()->create(); $article1 = Article::factory()->create(['feed_id' => $route->feed_id]); $article2 = Article::factory()->create(['feed_id' => $route->feed_id]); @@ -90,6 +98,7 @@ public function test_route_has_many_route_articles(): void public function test_unique_constraint_prevents_duplicate_route_articles(): void { + /** @var Route $route */ $route = Route::factory()->active()->create(); $article = Article::factory()->create(['feed_id' => $route->feed_id]); @@ -102,6 +111,7 @@ public function test_unique_constraint_prevents_duplicate_route_articles(): void public function test_route_article_cascade_deletes_when_article_deleted(): void { + /** @var RouteArticle $routeArticle */ $routeArticle = RouteArticle::factory()->create(); $articleId = $routeArticle->article_id; @@ -112,6 +122,7 @@ public function test_route_article_cascade_deletes_when_article_deleted(): void public function test_route_article_cascade_deletes_when_route_deleted(): void { + /** @var Route $route */ $route = Route::factory()->active()->create(); $article = Article::factory()->create(['feed_id' => $route->feed_id]); RouteArticle::factory()->forRoute($route)->create(['article_id' => $article->id]); @@ -128,8 +139,10 @@ public function test_route_article_cascade_deletes_when_route_deleted(): void public function test_route_article_belongs_to_route(): void { + /** @var Route $route */ $route = Route::factory()->active()->create(); $article = Article::factory()->create(['feed_id' => $route->feed_id]); + /** @var RouteArticle $routeArticle */ $routeArticle = RouteArticle::factory()->forRoute($route)->create(['article_id' => $article->id]); $loadedRoute = $routeArticle->route; diff --git a/tests/Unit/Services/ValidationServiceTest.php b/tests/Unit/Services/ValidationServiceTest.php index 03f69d9..4d216d0 100644 --- a/tests/Unit/Services/ValidationServiceTest.php +++ b/tests/Unit/Services/ValidationServiceTest.php @@ -60,6 +60,7 @@ private function mockFetchReturning(Article $article, ?string $content, ?string public function test_validate_sets_validated_at_on_article(): void { $feed = Feed::factory()->create(); + /** @var Route $route */ $route = Route::factory()->active()->create(['feed_id' => $feed->id]); Keyword::factory()->active()->create([ 'feed_id' => $feed->id, @@ -106,6 +107,7 @@ public function test_validate_skips_inactive_routes(): void public function test_validate_sets_pending_when_keywords_match(): void { $feed = Feed::factory()->create(); + /** @var Route $route */ $route = Route::factory()->active()->create(['feed_id' => $feed->id]); Keyword::factory()->active()->create([ 'feed_id' => $feed->id, @@ -125,6 +127,7 @@ public function test_validate_sets_pending_when_keywords_match(): void public function test_validate_sets_rejected_when_no_keywords_match(): void { $feed = Feed::factory()->create(); + /** @var Route $route */ $route = Route::factory()->active()->create(['feed_id' => $feed->id]); Keyword::factory()->active()->create([ 'feed_id' => $feed->id, @@ -200,6 +203,7 @@ public function test_validate_auto_approves_when_global_setting_off_and_keywords Setting::setBool('enable_publishing_approvals', false); $feed = Feed::factory()->create(); + /** @var Route $route */ $route = Route::factory()->active()->create(['feed_id' => $feed->id]); Keyword::factory()->active()->create([ 'feed_id' => $feed->id, @@ -221,6 +225,7 @@ public function test_validate_route_auto_approve_overrides_global_setting(): voi Setting::setBool('enable_publishing_approvals', true); $feed = Feed::factory()->create(); + /** @var Route $route */ $route = Route::factory()->active()->create([ 'feed_id' => $feed->id, 'auto_approve' => true, @@ -245,6 +250,7 @@ public function test_validate_route_auto_approve_false_overrides_global_off(): v Setting::setBool('enable_publishing_approvals', false); $feed = Feed::factory()->create(); + /** @var Route $route */ $route = Route::factory()->active()->create([ 'feed_id' => $feed->id, 'auto_approve' => false, @@ -269,6 +275,7 @@ public function test_validate_does_not_auto_approve_rejected_articles(): void Setting::setBool('enable_publishing_approvals', false); $feed = Feed::factory()->create(); + /** @var Route $route */ $route = Route::factory()->active()->create(['feed_id' => $feed->id]); Keyword::factory()->active()->create([ 'feed_id' => $feed->id, @@ -334,6 +341,7 @@ public function test_validate_sets_validated_at_on_route_articles(): void public function test_validate_keyword_matching_is_case_insensitive(): void { $feed = Feed::factory()->create(); + /** @var Route $route */ $route = Route::factory()->active()->create(['feed_id' => $feed->id]); Keyword::factory()->active()->create([ 'feed_id' => $feed->id, @@ -353,6 +361,7 @@ public function test_validate_keyword_matching_is_case_insensitive(): void public function test_validate_only_uses_active_keywords(): void { $feed = Feed::factory()->create(); + /** @var Route $route */ $route = Route::factory()->active()->create(['feed_id' => $feed->id]); Keyword::factory()->inactive()->create([ 'feed_id' => $feed->id,