feat: 初始化NewLife Studio项目,完成基础框架与数据管理模块
何炳宏 authored at 2026-05-26 12:09:09
12.21 KiB
NewLife.Studio
using System.Text.Json;
using NewLife.Studio.AI.Models;
using Xunit;

namespace NewLife.Studio.AI.Tests;

public class ChatModelsTests
{
    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNameCaseInsensitive = true,
        PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
    };

    [Fact]
    public void ChatMessage_Serialize_ProducesCorrectJson()
    {
        var message = new ChatMessage
        {
            Role = "user",
            Content = "Hello, how are you?"
        };

        var json = JsonSerializer.Serialize(message, JsonOptions);

        Assert.Contains("\"role\"", json);
        Assert.Contains("\"user\"", json);
        Assert.Contains("\"content\"", json);
        Assert.Contains("Hello, how are you?", json);
    }

    [Fact]
    public void ChatMessage_Deserialize_FromValidJson()
    {
        var json = """{"role":"assistant","content":"I am fine, thank you!"}""";

        var message = JsonSerializer.Deserialize<ChatMessage>(json, JsonOptions);

        Assert.NotNull(message);
        Assert.Equal("assistant", message.Role);
        Assert.Equal("I am fine, thank you!", message.Content);
    }

    [Fact]
    public void ChatMessage_WithToolCalls_SerializeAndDeserialize()
    {
        var message = new ChatMessage
        {
            Role = "assistant",
            Content = null,
            ToolCalls = new List<ToolCall>
            {
                new()
                {
                    Id = "call_abc123",
                    Type = "function",
                    Function = new FunctionCall
                    {
                        Name = "get_weather",
                        Arguments = "{\"location\":\"Beijing\"}"
                    }
                }
            }
        };

        var json = JsonSerializer.Serialize(message, JsonOptions);
        var deserialized = JsonSerializer.Deserialize<ChatMessage>(json, JsonOptions);

        Assert.NotNull(deserialized);
        Assert.Equal("assistant", deserialized.Role);
        Assert.Null(deserialized.Content);
        Assert.NotNull(deserialized.ToolCalls);
        Assert.Single(deserialized.ToolCalls);
        Assert.Equal("call_abc123", deserialized.ToolCalls[0].Id);
        Assert.Equal("get_weather", deserialized.ToolCalls[0].Function.Name);
    }

    [Fact]
    public void ChatMessage_ToolMessage_SerializeAndDeserialize()
    {
        var message = new ChatMessage
        {
            Role = "tool",
            ToolCallId = "call_abc123",
            Content = "25°C, sunny"
        };

        var json = JsonSerializer.Serialize(message, JsonOptions);
        var deserialized = JsonSerializer.Deserialize<ChatMessage>(json, JsonOptions);

        Assert.NotNull(deserialized);
        Assert.Equal("tool", deserialized.Role);
        Assert.Equal("call_abc123", deserialized.ToolCallId);
        Assert.Equal("25°C, sunny", deserialized.Content);
    }

    [Fact]
    public void ChatMessage_SystemRole_SerializeCorrectly()
    {
        var message = new ChatMessage
        {
            Role = "system",
            Content = "You are a helpful assistant."
        };

        var json = JsonSerializer.Serialize(message, JsonOptions);

        Assert.Contains("\"role\":\"system\"", json);
        Assert.Contains("\"content\":\"You are a helpful assistant.\"", json);
    }

    [Fact]
    public void ChatRequest_Serialize_IncludesAllFields()
    {
        var request = new ChatRequest
        {
            Model = "gpt-4o",
            Messages = new List<ChatMessage>
            {
                new() { Role = "system", Content = "You are a helpful assistant." },
                new() { Role = "user", Content = "What is the weather?" }
            },
            MaxTokens = 2048,
            Temperature = 0.5
        };

        var json = JsonSerializer.Serialize(request, JsonOptions);

        Assert.Contains("\"model\":\"gpt-4o\"", json);
        Assert.Contains("\"messages\"", json);
        Assert.Contains("\"max_tokens\":2048", json);
        Assert.Contains("\"temperature\":0.5", json);
    }

    [Fact]
    public void ChatRequest_Serialize_WithToolsArray()
    {
        var request = new ChatRequest
        {
            Model = "gpt-4o",
            Messages = new List<ChatMessage>
            {
                new() { Role = "user", Content = "Find users" }
            },
            Tools = new List<ToolDefinition>
            {
                new()
                {
                    Type = "function",
                    Function = new FunctionDef
                    {
                        Name = "search_users",
                        Description = "Search users by criteria",
                        Parameters = new { type = "object", properties = new { } }
                    }
                }
            }
        };

        var json = JsonSerializer.Serialize(request, JsonOptions);

        Assert.Contains("\"tools\"", json);
        Assert.Contains("\"search_users\"", json);
    }

    [Fact]
    public void ChatRequest_Serialize_WithNullTools_SerializesAsNull()
    {
        var request = new ChatRequest
        {
            Model = "gpt-4o",
            Messages = new List<ChatMessage>
            {
                new() { Role = "user", Content = "Hello" }
            },
            Tools = null
        };

        var json = JsonSerializer.Serialize(request, JsonOptions);

        Assert.Contains("\"tools\":null", json);
    }

    [Fact]
    public void ChatRequest_Deserialize_FromCompleteJson()
    {
        var json = """
            {
                "model": "gpt-4o",
                "messages": [
                    {"role": "system", "content": "You are helpful."},
                    {"role": "user", "content": "Query data"}
                ],
                "max_tokens": 4096,
                "temperature": 0.1
            }
            """;

        var request = JsonSerializer.Deserialize<ChatRequest>(json, JsonOptions);

        Assert.NotNull(request);
        Assert.Equal("gpt-4o", request.Model);
        Assert.Equal(2, request.Messages.Count);
        Assert.Equal(4096, request.MaxTokens);
        Assert.Equal(0.1, request.Temperature);
    }

    [Fact]
    public void ChatResponse_Deserialize_FromSampleJson()
    {
        var json = """
            {
                "choices": [
                    {
                        "message": {
                            "role": "assistant",
                            "content": "I can help you with that!"
                        },
                        "finish_reason": "stop"
                    }
                ],
                "usage": {
                    "prompt_tokens": 25,
                    "completion_tokens": 15
                }
            }
            """;

        var response = JsonSerializer.Deserialize<ChatResponse>(json, JsonOptions);

        Assert.NotNull(response);
        Assert.Single(response.Choices);
        Assert.Equal("assistant", response.Choices[0].Message.Role);
        Assert.Equal("I can help you with that!", response.Choices[0].Message.Content);
        Assert.Equal("stop", response.Choices[0].FinishReason);
        Assert.NotNull(response.Usage);
        Assert.Equal(25, response.Usage.PromptTokens);
        Assert.Equal(15, response.Usage.CompletionTokens);
    }

    [Fact]
    public void ChatResponse_Deserialize_WithToolCalls()
    {
        var json = """
            {
                "choices": [
                    {
                        "message": {
                            "role": "assistant",
                            "content": null,
                            "tool_calls": [
                                {
                                    "id": "call_001",
                                    "type": "function",
                                    "function": {
                                        "name": "query.select",
                                        "arguments": "{\\\"sql\\\":\\\"SELECT * FROM users\\\"}"
                                    }
                                }
                            ]
                        },
                        "finish_reason": "tool_calls"
                    }
                ],
                "usage": {
                    "prompt_tokens": 50,
                    "completion_tokens": 20
                }
            }
            """;

        var response = JsonSerializer.Deserialize<ChatResponse>(json, JsonOptions);

        Assert.NotNull(response);
        Assert.Single(response.Choices);
        Assert.Equal("tool_calls", response.Choices[0].FinishReason);
        var toolCalls = response.Choices[0].Message.ToolCalls;
        Assert.NotNull(toolCalls);
        Assert.Single(toolCalls!);
        Assert.Equal("call_001", toolCalls![0].Id);
        Assert.Equal("query.select", toolCalls[0].Function.Name);
    }

    [Fact]
    public void ChatResponse_Deserialize_EmptyChoices_ReturnsEmpty()
    {
        var json = """{"choices":[],"usage":null}""";

        var response = JsonSerializer.Deserialize<ChatResponse>(json, JsonOptions);

        Assert.NotNull(response);
        Assert.Empty(response.Choices);
    }

    [Fact]
    public void ToolCall_DefaultValues_AreCorrect()
    {
        var toolCall = new ToolCall();

        Assert.Equal("", toolCall.Id);
        Assert.Equal("function", toolCall.Type);
        Assert.NotNull(toolCall.Function);
    }

    [Fact]
    public void ToolDefinition_DefaultValues_AreCorrect()
    {
        var definition = new ToolDefinition();

        Assert.Equal("function", definition.Type);
        Assert.NotNull(definition.Function);
    }

    [Fact]
    public void FunctionDef_DefaultValues_AreCorrect()
    {
        var def = new FunctionDef();

        Assert.Equal("", def.Name);
        Assert.Equal("", def.Description);
        Assert.Null(def.Parameters);
    }

    [Fact]
    public void ChatRequest_DefaultValues_AreCorrect()
    {
        var request = new ChatRequest();

        Assert.Equal("gpt-4o", request.Model);
        Assert.NotNull(request.Messages);
        Assert.Empty(request.Messages);
        Assert.Null(request.Tools);
        Assert.Equal(4096, request.MaxTokens);
        Assert.Equal(0.1, request.Temperature);
    }

    [Fact]
    public void ToolResult_SetProperties_WorkCorrectly()
    {
        var result = new ToolResult
        {
            ToolCallId = "call_test",
            Output = "Query executed successfully",
            Error = null
        };

        Assert.Equal("call_test", result.ToolCallId);
        Assert.Equal("Query executed successfully", result.Output);
        Assert.Null(result.Error);
    }

    [Fact]
    public void ToolResult_ErrorScenario_WorksCorrectly()
    {
        var result = new ToolResult
        {
            ToolCallId = "call_error",
            Output = null,
            Error = "Unknown tool: invalid.tool"
        };

        Assert.Equal("call_error", result.ToolCallId);
        Assert.Null(result.Output);
        Assert.Equal("Unknown tool: invalid.tool", result.Error);
    }

    [Fact]
    public void ChatRequest_SerializeDeserialize_RoundTrip()
    {
        var original = new ChatRequest
        {
            Model = "gpt-4o",
            Messages = new List<ChatMessage>
            {
                new() { Role = "system", Content = "System message" },
                new() { Role = "user", Content = "User message" }
            },
            Tools = new List<ToolDefinition>
            {
                new()
                {
                    Function = new FunctionDef
                    {
                        Name = "test_tool",
                        Description = "A test tool"
                    }
                }
            },
            MaxTokens = 100,
            Temperature = 0.7
        };

        var json = JsonSerializer.Serialize(original, JsonOptions);
        var deserialized = JsonSerializer.Deserialize<ChatRequest>(json, JsonOptions);

        Assert.NotNull(deserialized);
        Assert.Equal(original.Model, deserialized.Model);
        Assert.Equal(original.Messages.Count, deserialized.Messages.Count);
        Assert.Equal(original.MaxTokens, deserialized.MaxTokens);
        Assert.Equal(original.Temperature, deserialized.Temperature);
        Assert.NotNull(deserialized.Tools);
        Assert.Single(deserialized.Tools);
    }
}