- 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>
134 lines
4.7 KiB
C#
134 lines
4.7 KiB
C#
using FluentAssertions;
|
|
using Nalu.Api.Models;
|
|
using Nalu.Api.Services;
|
|
using Xunit;
|
|
|
|
namespace Nalu.Tests;
|
|
|
|
public class DeterministicLayerTests
|
|
{
|
|
private readonly DeterministicLayer _layer = new();
|
|
|
|
private static ValidatorDefinition NameValidator() => new ValidatorDefinition
|
|
{
|
|
Id = "validate_full_name",
|
|
StopWords = ["bom dia", "boa tarde", "boa noite", "olá", "oi"],
|
|
RejectPatterns =
|
|
[
|
|
@"^(não|nao|sei la|sei lá|tanto faz)$",
|
|
@"^\d+$"
|
|
],
|
|
AcceptPatterns =
|
|
[
|
|
@"^meu nome é\s+(.+)$",
|
|
@"^me chamo\s+(.+)$"
|
|
],
|
|
Constraints = new Dictionary<string, string>
|
|
{
|
|
{ "min_length", "2" },
|
|
{ "must_have_alpha", "true" },
|
|
{ "max_length", "120" }
|
|
}
|
|
};
|
|
|
|
// ── Stop words ────────────────────────────────────────────────────────────
|
|
|
|
[Theory]
|
|
[InlineData("Bom dia")]
|
|
[InlineData("bom dia")]
|
|
[InlineData("Bom dia!")]
|
|
[InlineData("Oi")]
|
|
[InlineData("olá")]
|
|
public void StopWord_ReturnsRejected(string input)
|
|
{
|
|
var result = _layer.Evaluate(NameValidator(), input);
|
|
|
|
result.Outcome.Should().Be(DeterministicOutcome.Rejected);
|
|
result.ExtractedValue.Should().BeNull();
|
|
}
|
|
|
|
// ── Reject patterns ───────────────────────────────────────────────────────
|
|
|
|
[Theory]
|
|
[InlineData("não")]
|
|
[InlineData("sei la")]
|
|
[InlineData("tanto faz")]
|
|
[InlineData("12345")]
|
|
public void RejectPattern_ReturnsRejected(string input)
|
|
{
|
|
var result = _layer.Evaluate(NameValidator(), input);
|
|
|
|
result.Outcome.Should().Be(DeterministicOutcome.Rejected);
|
|
}
|
|
|
|
// ── Accept patterns ───────────────────────────────────────────────────────
|
|
|
|
[Theory]
|
|
[InlineData("meu nome é João Silva", "João Silva")]
|
|
[InlineData("Meu nome é Maria", "Maria")]
|
|
[InlineData("me chamo Pedro", "Pedro")]
|
|
public void AcceptPattern_ReturnsAcceptedWithCapturedGroup(string input, string expectedValue)
|
|
{
|
|
var result = _layer.Evaluate(NameValidator(), input);
|
|
|
|
result.Outcome.Should().Be(DeterministicOutcome.Accepted);
|
|
result.ExtractedValue.Should().Be(expectedValue);
|
|
}
|
|
|
|
// ── Unresolved ────────────────────────────────────────────────────────────
|
|
|
|
[Theory]
|
|
[InlineData("Carlos Alberto Ferreira")]
|
|
[InlineData("Ana")]
|
|
public void FreeText_ReturnsUnresolved(string input)
|
|
{
|
|
var result = _layer.Evaluate(NameValidator(), input);
|
|
|
|
result.Outcome.Should().Be(DeterministicOutcome.Unresolved);
|
|
}
|
|
|
|
// ── Constraints ───────────────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void AcceptPattern_WhenValueTooShort_ReturnsConstraintFailed()
|
|
{
|
|
var validator = NameValidator();
|
|
validator.Constraints["min_length"] = "5";
|
|
|
|
var result = _layer.Evaluate(validator, "meu nome é Al");
|
|
|
|
result.Outcome.Should().Be(DeterministicOutcome.ConstraintFailed);
|
|
result.Reasoning.Should().Contain("mínimo");
|
|
}
|
|
|
|
[Fact]
|
|
public void AcceptPattern_WhenValueHasNoLetters_ReturnsConstraintFailed()
|
|
{
|
|
var result = _layer.Evaluate(NameValidator(), "meu nome é 999");
|
|
|
|
result.Outcome.Should().Be(DeterministicOutcome.ConstraintFailed);
|
|
result.Reasoning.Should().Contain("letras");
|
|
}
|
|
|
|
[Fact]
|
|
public void AcceptPattern_WhenValueTooLong_ReturnsConstraintFailed()
|
|
{
|
|
var longName = new string('A', 121);
|
|
var result = _layer.Evaluate(NameValidator(), $"meu nome é {longName}");
|
|
|
|
result.Outcome.Should().Be(DeterministicOutcome.ConstraintFailed);
|
|
result.Reasoning.Should().Contain("máximo");
|
|
}
|
|
|
|
// ── No patterns configured ────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void EmptyValidator_AlwaysUnresolved()
|
|
{
|
|
var validator = new ValidatorDefinition { Id = "empty" };
|
|
var result = _layer.Evaluate(validator, "qualquer coisa");
|
|
|
|
result.Outcome.Should().Be(DeterministicOutcome.Unresolved);
|
|
}
|
|
}
|