map(fn ($route) => $route->uri()); $this->assertFalse($uris->contains(fn (string $uri) => str_starts_with($uri, 'api/v1/v1/'))); $this->assertSame(1, $uris->filter(fn (string $uri) => $uri === 'api/v1/health')->count()); $this->assertSame(1, $uris->filter(fn (string $uri) => $uri === 'api/v1/certificates/verify/{code}')->count()); $this->assertTrue($uris->contains('api/v1/ai/chat')); } public function test_ai_chat_route_uses_the_modular_ai_controller(): void { $route = collect(Route::getRoutes())->first(fn ($route) => $route->uri() === 'api/v1/ai/chat'); $this->assertNotNull($route); $this->assertSame(AiChatController::class.'@chat', $route->getActionName()); } public function test_ai_chat_requires_authentication_and_authorization(): void { $this->postJson('/api/v1/ai/chat', ['prompt' => 'test'])->assertUnauthorized(); $manager = User::factory()->create(['role' => UserRole::Manager]); Sanctum::actingAs($manager); $this->postJson('/api/v1/ai/chat', ['prompt' => 'test'])->assertForbidden(); $designer = User::factory()->create(['role' => UserRole::CourseDesigner]); Sanctum::actingAs($designer); $this->postJson('/api/v1/ai/chat', ['prompt' => 'test']) ->assertOk() ->assertJsonPath('data.answer', 'test'); } public function test_expensive_ai_chat_is_rate_limited_per_authenticated_user(): void { Sanctum::actingAs(User::factory()->create(['role' => UserRole::CourseDesigner])); foreach (range(1, 10) as $attempt) { $this->postJson('/api/v1/ai/chat', ['prompt' => "test {$attempt}"])->assertOk(); } $this->postJson('/api/v1/ai/chat', ['prompt' => 'limited'])->assertTooManyRequests(); } }