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 Só 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}}"); } }