using System.Net;
using System.Net.Http.Json;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Nalu.Api.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);
}