- ASP.NET Core 9 Razor Pages + Minimal API hybrid - 14 validators (CPF, CEP, CNPJ, email, phone, name, yes-no, birthdate, handoff, cancel-intent, company-name, plate-br, postal-code, validate_reply) - OAuth login (Google, Microsoft, GitHub) + cookie auth - MongoDB usage tracking + CEP cache collection - Stripe checkout with inline PriceData (no Price IDs) - MCP server for Claude Code / Cursor integration - Playground (10 calls/IP/day, no auth) - Docs: Quickstart, API Reference, N8N, MCP, Créditos, Erros, Fluxos - Credit system: 3 cr standard validators, 5 cr validate_reply - SmartSuggestion: contextual re-ask via IA when obtained=false - Per-IP rate limiting + daily cap + shared-IP abuse detection - Lightbox for comic images - Validadores page split: Brasileiros / Universais + Em breve Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
137 lines
5.2 KiB
C#
137 lines
5.2 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public class PipelineIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
|
|
{
|
|
private readonly HttpClient _client;
|
|
|
|
public PipelineIntegrationTests(WebApplicationFactory<Program> 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<ValidatorsResponse>("/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<ExtractionResponse?> 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<ExtractionResponse>();
|
|
}
|
|
|
|
private record ValidatorsResponse(List<ValidatorInfo> Validators);
|
|
}
|