using NewLife.Studio.AI.Models;
using NewLife.Studio.AI.ToolCalling;
using Xunit;
namespace NewLife.Studio.AI.Tests;
public class ToolRegistryTests
{
private readonly ToolRegistry _registry;
public ToolRegistryTests()
{
_registry = new ToolRegistry();
}
[Fact]
public void GetAllDefinitions_BeforeAnyRegistration_ReturnsEmptyList()
{
var definitions = _registry.GetAllDefinitions();
Assert.NotNull(definitions);
Assert.Empty(definitions);
}
[Fact]
public void Register_ThenGetAllDefinitions_ReturnsRegisteredTool()
{
_registry.Register("test.tool", "A test tool", null, _ => Task.FromResult("done"));
var definitions = _registry.GetAllDefinitions();
Assert.Single(definitions);
Assert.Equal("test.tool", definitions[0].Function.Name);
Assert.Equal("A test tool", definitions[0].Function.Description);
Assert.Equal("function", definitions[0].Type);
}
[Fact]
public void Register_MultipleTools_ReturnsAllDefinitions()
{
_registry.Register("tool1", "Tool 1", null, _ => Task.FromResult("1"));
_registry.Register("tool2", "Tool 2", null, _ => Task.FromResult("2"));
_registry.Register("tool3", "Tool 3", null, _ => Task.FromResult("3"));
var definitions = _registry.GetAllDefinitions();
Assert.Equal(3, definitions.Count);
Assert.Contains(definitions, d => d.Function.Name == "tool1");
Assert.Contains(definitions, d => d.Function.Name == "tool2");
Assert.Contains(definitions, d => d.Function.Name == "tool3");
}
[Fact]
public void Register_DuplicateName_OverwritesPrevious()
{
_registry.Register("dup.tool", "First registration", null, _ => Task.FromResult("first"));
_registry.Register("dup.tool", "Second registration", null, _ => Task.FromResult("second"));
var definitions = _registry.GetAllDefinitions();
Assert.Single(definitions);
Assert.Equal("Second registration", definitions[0].Function.Description);
}
[Fact]
public async Task ExecuteAsync_RegisteredTool_CallsHandlerAndReturnsOutput()
{
_registry.Register("echo", "Echo tool", new { type = "object", properties = new { } },
args => Task.FromResult($"Echo: {args}"));
var toolCall = new ToolCall
{
Id = "call_001",
Function = new FunctionCall
{
Name = "echo",
Arguments = "{\"message\":\"hello\"}"
}
};
var result = await _registry.ExecuteAsync(toolCall);
Assert.NotNull(result);
Assert.Equal("call_001", result.ToolCallId);
Assert.Null(result.Error);
Assert.Equal("Echo: {\"message\":\"hello\"}", result.Output);
}
[Fact]
public async Task ExecuteAsync_UnknownTool_ReturnsError()
{
var toolCall = new ToolCall
{
Id = "call_002",
Function = new FunctionCall
{
Name = "nonexistent",
Arguments = "{}"
}
};
var result = await _registry.ExecuteAsync(toolCall);
Assert.NotNull(result);
Assert.Equal("call_002", result.ToolCallId);
Assert.NotNull(result.Error);
Assert.Contains("Unknown tool", result.Error);
Assert.Contains("nonexistent", result.Error);
Assert.Null(result.Output);
}
[Fact]
public async Task ExecuteAsync_HandlerThrowsException_ReturnsError()
{
_registry.Register("failing.tool", "Always fails", null, args =>
{
throw new InvalidOperationException("Simulated handler failure");
});
var toolCall = new ToolCall
{
Id = "call_003",
Function = new FunctionCall
{
Name = "failing.tool",
Arguments = "{}"
}
};
var result = await _registry.ExecuteAsync(toolCall);
Assert.NotNull(result);
Assert.Equal("call_003", result.ToolCallId);
Assert.NotNull(result.Error);
Assert.Contains("Simulated handler failure", result.Error);
Assert.Null(result.Output);
}
[Fact]
public void Register_WithParametersSchema_StoresParametersCorrectly()
{
var parameters = new
{
type = "object",
properties = new
{
sql = new { type = "string", description = "SQL 语句" }
},
required = new[] { "sql" }
};
_registry.Register("query.select", "Execute SELECT", parameters, _ => Task.FromResult("ok"));
var definitions = _registry.GetAllDefinitions();
Assert.NotNull(definitions[0].Function.Parameters);
}
}
|