using System.Net; using System.Net.Http.Json; using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using Nalu.Web.Models; using Xunit; namespace Nalu.Tests; /// /// Integration tests using WebApplicationFactory. /// The Validators directory must be available in the test output — the .csproj /// CopyToOutputDirectory on the main project handles this via project reference. /// public class PipelineIntegrationTests : IClassFixture> { private readonly HttpClient _client; public PipelineIntegrationTests(WebApplicationFactory factory) { _client = factory.CreateClient(); _client.DefaultRequestHeaders.Add("Authorization", "Bearer nalu-test-key-001"); } // ── Auth ────────────────────────────────────────────────────────────────── [Fact] public async Task NoApiKey_Returns401() { using var client = new HttpClient { BaseAddress = _client.BaseAddress }; var response = await client.PostAsJsonAsync("/v1/extract/name", new { agent_input = "Qual seu nome?", user_input = "João" }); response.StatusCode.Should().Be(HttpStatusCode.Unauthorized); } [Fact] public async Task InvalidApiKey_Returns401() { using var client = new HttpClient { BaseAddress = _client.BaseAddress }; client.DefaultRequestHeaders.Add("Authorization", "Bearer invalid-key"); var response = await client.PostAsJsonAsync("/v1/extract/name", new { agent_input = "Qual seu nome?", user_input = "João" }); response.StatusCode.Should().Be(HttpStatusCode.Unauthorized); } // ── Name extraction — deterministic path ────────────────────────────────── [Fact] public async Task PostName_WithGreeting_ReturnsObtainedFalse() { var response = await PostExtractName("Qual seu nome completo?", "Bom dia!"); response.Should().NotBeNull(); response!.Obtained.Should().BeFalse(); response.Certain.Should().BeFalse(); response.ExtractedValue.Should().BeNull(); response.SuggestionToAgent.Should().Contain("Bom dia!"); } [Fact] public async Task PostName_WithAcceptPattern_ReturnsObtainedTrue() { var response = await PostExtractName("Qual seu nome?", "meu nome é Carlos Silva"); response.Should().NotBeNull(); response!.Obtained.Should().BeTrue(); response.Certain.Should().BeTrue(); response.ExtractedValue.Should().Be("Carlos Silva"); response.Confidence.Should().Be("high"); } [Fact] public async Task PostName_RejectPattern_ReturnsObtainedFalse() { var response = await PostExtractName("Qual seu nome?", "sei la"); response.Should().NotBeNull(); response!.Obtained.Should().BeFalse(); } // ── GET /v1/validators ──────────────────────────────────────────────────── [Fact] public async Task GetValidators_ReturnsValidatorList() { var result = await _client.GetFromJsonAsync("/v1/validators"); result.Should().NotBeNull(); result!.Validators.Should().NotBeEmpty(); result.Validators.Should().Contain(v => v.Id == "validate_full_name"); var nameValidator = result.Validators.First(v => v.Id == "validate_full_name"); nameValidator.Endpoint.Should().Be("/v1/extract/name"); nameValidator.McpTool.Should().Be("nalu_extract_name"); nameValidator.Languages.Should().Contain("pt-BR"); } // ── Response structure ──────────────────────────────────────────────────── [Fact] public async Task PostName_ResponseContainsAllRequiredFields() { var response = await PostExtractName("Qual seu nome?", "Bom dia!"); response.Should().NotBeNull(); response!.Obtained.Should().BeFalse(); // greeting → not obtained response.Confidence.Should().BeOneOf("high", "medium", "low"); } // ── Helpers ─────────────────────────────────────────────────────────────── private async Task PostExtractName(string agentInput, string userInput) { var payload = new ExtractionRequest { AgentInput = agentInput, UserInput = userInput, Language = "pt-BR" }; var httpResponse = await _client.PostAsJsonAsync("/v1/extract/name", payload); httpResponse.EnsureSuccessStatusCode(); return await httpResponse.Content.ReadFromJsonAsync(); } private record ValidatorsResponse(List Validators); }