NALU/tests/Nalu.Tests/McpServerTests.cs
Ricardo Carneiro ea6cdb5395 Initial commit — NALU AI web platform
- 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>
2026-05-10 16:39:04 -03:00

200 lines
7.5 KiB
C#

using System.Net;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;
namespace Nalu.Tests;
public class McpServerTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public McpServerTests(WebApplicationFactory<Program> factory)
{
_client = factory.CreateClient();
_client.DefaultRequestHeaders.Add("Authorization", "Bearer nalu-test-key-001");
}
private async Task<JsonNode?> PostMcp(object body)
{
var json = JsonSerializer.Serialize(body);
var response = await _client.PostAsync("/mcp",
new StringContent(json, Encoding.UTF8, "application/json"));
response.EnsureSuccessStatusCode();
var text = await response.Content.ReadAsStringAsync();
return JsonNode.Parse(text);
}
// ── Auth ──────────────────────────────────────────────────────────────────
[Fact]
public async Task Mcp_NoApiKey_Returns401()
{
using var client = new HttpClient { BaseAddress = _client.BaseAddress };
var response = await client.PostAsync("/mcp",
new StringContent("{}", Encoding.UTF8, "application/json"));
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
}
// ── initialize ────────────────────────────────────────────────────────────
[Fact]
public async Task Mcp_Initialize_ReturnsServerInfo()
{
var result = await PostMcp(new
{
jsonrpc = "2.0",
method = "initialize",
@params = new { protocolVersion = "2024-11-05", capabilities = new { } },
id = 1
});
result.Should().NotBeNull();
result!["result"]!["serverInfo"]!["name"]!.GetValue<string>().Should().Be("nalu-ai");
result["result"]!["protocolVersion"]!.GetValue<string>().Should().Be("2024-11-05");
result["result"]!["capabilities"]!["tools"].Should().NotBeNull();
}
// ── tools/list ────────────────────────────────────────────────────────────
[Fact]
public async Task Mcp_ToolsList_ContainsAllValidators()
{
var result = await PostMcp(new
{
jsonrpc = "2.0",
method = "tools/list",
@params = new { },
id = 2
});
var tools = result!["result"]!["tools"]!.AsArray();
tools.Should().NotBeEmpty();
var toolNames = tools.Select(t => t!["name"]!.GetValue<string>()).ToList();
toolNames.Should().Contain("nalu_extract_name");
toolNames.Should().Contain("nalu_extract_cpf");
toolNames.Should().Contain("nalu_extract_cep");
toolNames.Should().Contain("nalu_extract_phone");
toolNames.Should().Contain("nalu_extract_email");
toolNames.Should().Contain("nalu_extract_yes_no");
toolNames.Should().Contain("nalu_extract_postal_code");
// Universal — new
toolNames.Should().Contain("nalu_extract_birthdate");
toolNames.Should().Contain("nalu_extract_handoff");
toolNames.Should().Contain("nalu_extract_cancel_intent");
// Brasil — new
toolNames.Should().Contain("nalu_extract_cnpj");
toolNames.Should().Contain("nalu_extract_plate_br");
toolNames.Should().Contain("nalu_extract_company_name");
}
[Fact]
public async Task Mcp_ToolsList_EachToolHasInputSchema()
{
var result = await PostMcp(new
{
jsonrpc = "2.0",
method = "tools/list",
@params = new { },
id = 3
});
var tools = result!["result"]!["tools"]!.AsArray();
foreach (var tool in tools)
{
var schema = tool!["inputSchema"];
schema.Should().NotBeNull($"tool {tool["name"]} should have inputSchema");
schema!["properties"]!["agent_input"].Should().NotBeNull();
schema["properties"]!["user_input"].Should().NotBeNull();
var required = schema["required"]!.AsArray().Select(r => r!.GetValue<string>()).ToList();
required.Should().Contain("agent_input");
required.Should().Contain("user_input");
}
}
// ── tools/call ────────────────────────────────────────────────────────────
[Fact]
public async Task Mcp_ToolsCall_ExtractName_WithGreeting_ObtainedFalse()
{
var result = await PostMcp(new
{
jsonrpc = "2.0",
method = "tools/call",
@params = new
{
name = "nalu_extract_name",
arguments = new
{
agent_input = "Qual seu nome completo?",
user_input = "Bom dia!"
}
},
id = 4
});
result!["result"]!["isError"]!.GetValue<bool>().Should().BeFalse();
var content = result["result"]!["content"]!.AsArray();
content.Should().HaveCount(1);
content[0]!["type"]!.GetValue<string>().Should().Be("text");
var text = content[0]!["text"]!.GetValue<string>();
var extracted = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(text)!;
extracted["obtained"].GetBoolean().Should().BeFalse();
}
[Fact]
public async Task Mcp_ToolsCall_UnknownTool_ReturnsError()
{
var result = await PostMcp(new
{
jsonrpc = "2.0",
method = "tools/call",
@params = new { name = "nalu_extract_unknown", arguments = new { } },
id = 5
});
result!["error"].Should().NotBeNull();
result["error"]!["code"]!.GetValue<int>().Should().Be(-32602);
}
// ── New .md file → auto-appears in tools/list ─────────────────────────────
[Fact]
public async Task Mcp_ToolsList_AllLoadedValidatorsHaveMcpTool()
{
// Any validator without mcp_tool should not appear in tools/list
var result = await PostMcp(new
{
jsonrpc = "2.0",
method = "tools/list",
@params = new { },
id = 6
});
var tools = result!["result"]!["tools"]!.AsArray();
foreach (var tool in tools)
{
tool!["name"]!.GetValue<string>().Should().NotBeNullOrWhiteSpace();
tool!["description"]!.GetValue<string>().Should().NotBeNullOrWhiteSpace();
}
}
// ── notifications ─────────────────────────────────────────────────────────
[Fact]
public async Task Mcp_Initialized_Notification_Returns202()
{
var json = JsonSerializer.Serialize(new { jsonrpc = "2.0", method = "initialized" });
var response = await _client.PostAsync("/mcp",
new StringContent(json, Encoding.UTF8, "application/json"));
response.StatusCode.Should().Be(HttpStatusCode.Accepted);
}
}