NALU/tests/Nalu.Tests/ValidatorLoaderTests.cs
Ricardo Carneiro 9a68120adf
Some checks failed
NALU Deployment Pipeline / Run Tests (push) Failing after 2m29s
NALU Deployment Pipeline / PR Validation (push) Has been skipped
NALU Deployment Pipeline / Build and Push Image (push) Has been skipped
NALU Deployment Pipeline / Deploy naluai.dev (push) Has been skipped
NALU Deployment Pipeline / Cleanup Old Resources (push) Has been skipped
Fix CI: update test project reference and namespaces for Nalu.Web
- Test project was referencing non-existent Nalu.Api.csproj → Nalu.Web.csproj
- Replace using Nalu.Api.* → Nalu.Web.* in all test files
- Skip PipelineIntegration tests in CI (require live MongoDB)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 12:49:54 -03:00

199 lines
5.9 KiB
C#

using FluentAssertions;
using Nalu.Web.Services;
using Xunit;
namespace Nalu.Tests;
public class ValidatorLoaderTests
{
private const string FullNameMd = """
# validate_full_name
Extrai o nome completo do usuário.
## config
- type: extraction
- version: 1.0
- languages: pt-BR, es-ES, en-US
- endpoint: /v1/extract/name
- mcp_tool: nalu_extract_name
- mcp_description: Extrai o nome completo.
## deterministic_rules
### stop_words
bom dia, boa tarde, boa noite, olá, oi
### reject_patterns
- ^(não|nao|sei la)$
- ^\d+$
### accept_patterns
- ^meu nome é\s+(.+)$
- ^me chamo\s+(.+)$
### constraints
- min_length: 2
- must_have_alpha: true
- max_length: 120
## prompt
Você é um extrator de nomes.
Diálogo:
Agente: {{agent_input}}
Usuário: {{user_input}}
Contexto do agente: {{agent_context}}
Responda SOMENTE com JSON válido.
## few_shot_examples
### example 1
- agent_input: Qual seu nome?
- user_input: Bom dia!
- output: {"extracted_value": null, "certain": false, "reasoning": "Saudação"}
### example 2
- agent_input: Qual seu nome?
- user_input: Maria Silva
- output: {"extracted_value": "Maria Silva", "certain": true, "reasoning": "Nome plausível"}
## post_processors
- capitalize_proper_name
- remove_titles
## enrichment
(nenhum)
## suggestions
### when_null_greeting
{{greeting_response}} Mas preciso do seu nome. Pode me dizer?
### when_null_evasive
Preciso do seu nome para continuar. Qual é?
### when_uncertain
confirmando: seu nome é {{extracted_value}}?
### when_certain
(sem sugestão agente segue o fluxo)
""";
[Fact]
public void Parse_Config_ReadsAllFields()
{
var def = ValidatorLoader.ParseFromContent("validate_full_name", FullNameMd);
def.Id.Should().Be("validate_full_name");
def.Type.Should().Be("extraction");
def.Version.Should().Be("1.0");
def.Languages.Should().BeEquivalentTo(["pt-BR", "es-ES", "en-US"]);
def.Endpoint.Should().Be("/v1/extract/name");
def.McpTool.Should().Be("nalu_extract_name");
def.McpDescription.Should().Contain("Extrai o nome completo");
}
[Fact]
public void Parse_StopWords_ParsesCommaSeparated()
{
var def = ValidatorLoader.ParseFromContent("validate_full_name", FullNameMd);
def.StopWords.Should().Contain("bom dia");
def.StopWords.Should().Contain("boa tarde");
def.StopWords.Should().Contain("oi");
def.StopWords.Should().HaveCount(5);
}
[Fact]
public void Parse_RejectPatterns_ParsesBulletList()
{
var def = ValidatorLoader.ParseFromContent("validate_full_name", FullNameMd);
def.RejectPatterns.Should().HaveCount(2);
def.RejectPatterns[0].Should().Be(@"^(não|nao|sei la)$");
def.RejectPatterns[1].Should().Be(@"^\d+$");
}
[Fact]
public void Parse_AcceptPatterns_ParsesBulletList()
{
var def = ValidatorLoader.ParseFromContent("validate_full_name", FullNameMd);
def.AcceptPatterns.Should().HaveCount(2);
def.AcceptPatterns[0].Should().Be(@"^meu nome é\s+(.+)$");
}
[Fact]
public void Parse_Constraints_ParsesKeyValues()
{
var def = ValidatorLoader.ParseFromContent("validate_full_name", FullNameMd);
def.Constraints.Should().ContainKey("min_length").WhoseValue.Should().Be("2");
def.Constraints.Should().ContainKey("max_length").WhoseValue.Should().Be("120");
def.Constraints.Should().ContainKey("must_have_alpha").WhoseValue.Should().Be("true");
}
[Fact]
public void Parse_Prompt_CapturesFullText()
{
var def = ValidatorLoader.ParseFromContent("validate_full_name", FullNameMd);
def.Prompt.Should().Contain("Você é um extrator de nomes");
def.Prompt.Should().Contain("{{agent_input}}");
def.Prompt.Should().Contain("{{user_input}}");
}
[Fact]
public void Parse_FewShots_ParsesAllExamples()
{
var def = ValidatorLoader.ParseFromContent("validate_full_name", FullNameMd);
def.FewShotExamples.Should().HaveCount(2);
def.FewShotExamples[0].AgentInput.Should().Be("Qual seu nome?");
def.FewShotExamples[0].UserInput.Should().Be("Bom dia!");
def.FewShotExamples[1].UserInput.Should().Be("Maria Silva");
}
[Fact]
public void Parse_PostProcessors_ParsesBulletList()
{
var def = ValidatorLoader.ParseFromContent("validate_full_name", FullNameMd);
def.PostProcessors.Should().BeEquivalentTo(["capitalize_proper_name", "remove_titles"]);
}
[Fact]
public void Parse_Enrichment_NenhumYieldsEmptyList()
{
var def = ValidatorLoader.ParseFromContent("validate_full_name", FullNameMd);
def.Enrichers.Should().BeEmpty();
}
[Fact]
public void Parse_Suggestions_ParsesTemplates()
{
var def = ValidatorLoader.ParseFromContent("validate_full_name", FullNameMd);
def.Suggestions.Should().ContainKey("when_null_greeting");
def.Suggestions.Should().ContainKey("when_null_evasive");
def.Suggestions.Should().ContainKey("when_uncertain");
// when_certain has "(sem sugestão..." so it should NOT be stored
def.Suggestions.Should().NotContainKey("when_certain");
}
[Fact]
public void Parse_Suggestions_ContainsPlaceholders()
{
var def = ValidatorLoader.ParseFromContent("validate_full_name", FullNameMd);
def.Suggestions["when_null_greeting"].Should().Contain("{{greeting_response}}");
def.Suggestions["when_uncertain"].Should().Contain("{{extracted_value}}");
}
}