create(); /** @var Route $route */ $route = Route::factory()->active()->create(['feed_id' => $feed->id]); $article = Article::factory()->create(['feed_id' => $feed->id]); /** @var RouteArticle $routeArticle */ $routeArticle = RouteArticle::factory()->forRoute($route)->create([ 'article_id' => $article->id, 'approval_status' => $status, 'validated_at' => now(), ]); return $routeArticle; } public function test_index_returns_successful_response(): void { $response = $this->getJson('/api/v1/route-articles'); $response->assertStatus(200) ->assertJsonStructure([ 'success', 'data' => [ 'route_articles', 'pagination' => [ 'current_page', 'last_page', 'per_page', 'total', 'from', 'to', ], ], 'message', ]); } public function test_index_returns_route_articles_with_pagination(): void { $feed = Feed::factory()->create(); /** @var Route $route */ $route = Route::factory()->active()->create(['feed_id' => $feed->id]); for ($i = 0; $i < 20; $i++) { $article = Article::factory()->create(['feed_id' => $feed->id]); RouteArticle::factory()->forRoute($route)->create([ 'article_id' => $article->id, 'validated_at' => now(), ]); } $response = $this->getJson('/api/v1/route-articles?per_page=10'); $response->assertStatus(200) ->assertJson([ 'success' => true, 'data' => [ 'pagination' => [ 'per_page' => 10, 'total' => 20, 'last_page' => 2, ], ], ]); $this->assertCount(10, $response->json('data.route_articles')); } public function test_index_filters_by_status(): void { $this->createRouteArticle(ApprovalStatusEnum::PENDING); $this->createRouteArticle(ApprovalStatusEnum::PENDING); $this->createRouteArticle(ApprovalStatusEnum::APPROVED); $this->createRouteArticle(ApprovalStatusEnum::REJECTED); $response = $this->getJson('/api/v1/route-articles?status=pending'); $response->assertStatus(200); $this->assertCount(2, $response->json('data.route_articles')); } public function test_index_returns_all_when_no_status_filter(): void { $this->createRouteArticle(ApprovalStatusEnum::PENDING); $this->createRouteArticle(ApprovalStatusEnum::APPROVED); $this->createRouteArticle(ApprovalStatusEnum::REJECTED); $response = $this->getJson('/api/v1/route-articles'); $response->assertStatus(200); $this->assertCount(3, $response->json('data.route_articles')); } public function test_index_includes_article_and_route_data(): void { $routeArticle = $this->createRouteArticle(); $response = $this->getJson('/api/v1/route-articles'); $response->assertStatus(200); $data = $response->json('data.route_articles.0'); $this->assertArrayHasKey('article', $data); $this->assertArrayHasKey('title', $data['article']); $this->assertArrayHasKey('url', $data['article']); $this->assertArrayHasKey('route_name', $data); $this->assertArrayHasKey('approval_status', $data); } public function test_approve_route_article_successfully(): void { Event::fake([RouteArticleApproved::class]); $routeArticle = $this->createRouteArticle(ApprovalStatusEnum::PENDING); $response = $this->postJson("/api/v1/route-articles/{$routeArticle->id}/approve"); $response->assertStatus(200) ->assertJson([ 'success' => true, 'message' => 'Route article approved and queued for publishing.', ]); $this->assertEquals(ApprovalStatusEnum::APPROVED, $routeArticle->fresh()->approval_status); Event::assertDispatched(RouteArticleApproved::class, function ($event) use ($routeArticle) { return $event->routeArticle->id === $routeArticle->id; }); } public function test_approve_nonexistent_route_article_returns_404(): void { $response = $this->postJson('/api/v1/route-articles/999/approve'); $response->assertStatus(404); } public function test_reject_route_article_successfully(): void { $routeArticle = $this->createRouteArticle(ApprovalStatusEnum::PENDING); $response = $this->postJson("/api/v1/route-articles/{$routeArticle->id}/reject"); $response->assertStatus(200) ->assertJson([ 'success' => true, 'message' => 'Route article rejected.', ]); $this->assertEquals(ApprovalStatusEnum::REJECTED, $routeArticle->fresh()->approval_status); } public function test_reject_nonexistent_route_article_returns_404(): void { $response = $this->postJson('/api/v1/route-articles/999/reject'); $response->assertStatus(404); } public function test_restore_route_article_successfully(): void { $routeArticle = $this->createRouteArticle(ApprovalStatusEnum::REJECTED); $response = $this->postJson("/api/v1/route-articles/{$routeArticle->id}/restore"); $response->assertStatus(200) ->assertJson([ 'success' => true, 'message' => 'Route article restored to pending.', ]); $this->assertEquals(ApprovalStatusEnum::PENDING, $routeArticle->fresh()->approval_status); } public function test_restore_nonexistent_route_article_returns_404(): void { $response = $this->postJson('/api/v1/route-articles/999/restore'); $response->assertStatus(404); } public function test_clear_rejects_all_pending_route_articles(): void { $this->createRouteArticle(ApprovalStatusEnum::PENDING); $this->createRouteArticle(ApprovalStatusEnum::PENDING); $this->createRouteArticle(ApprovalStatusEnum::PENDING); $this->createRouteArticle(ApprovalStatusEnum::APPROVED); $response = $this->postJson('/api/v1/route-articles/clear'); $response->assertStatus(200) ->assertJson([ 'success' => true, 'data' => [ 'rejected_count' => 3, ], ]); $this->assertEquals(0, RouteArticle::where('approval_status', ApprovalStatusEnum::PENDING)->count()); $this->assertEquals(1, RouteArticle::where('approval_status', ApprovalStatusEnum::APPROVED)->count()); $this->assertEquals(3, RouteArticle::where('approval_status', ApprovalStatusEnum::REJECTED)->count()); } public function test_clear_returns_zero_when_no_pending(): void { $this->createRouteArticle(ApprovalStatusEnum::APPROVED); $response = $this->postJson('/api/v1/route-articles/clear'); $response->assertStatus(200) ->assertJson([ 'success' => true, 'data' => [ 'rejected_count' => 0, ], ]); } public function test_index_respects_per_page_limit(): void { $response = $this->getJson('/api/v1/route-articles?per_page=150'); $response->assertStatus(200) ->assertJson([ 'data' => [ 'pagination' => [ 'per_page' => 100, ], ], ]); } }