RPC远程过程调用,二进制封装,提供高吞吐低延迟的高性能RPC框架
大石头 authored at 2022-08-10 13:26:19
8.18 KiB
NewLife.Remoting
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using NewLife.Remoting;
using NewLife.Remoting.Clients;
using NewLife.Remoting.Models;
using Xunit;

namespace XUnitTest.Clients;

/// <summary>命令客户端测试</summary>
public class CommandClientTests
{
    /// <summary>测试注册简单字符串命令</summary>
    [Fact]
    public void RegisterCommand_StringFunc_ShouldAddToCommands()
    {
        // Arrange
        var client = new TestCommandClient();
        Func<String?, String?> handler = arg => $"Echo: {arg}";

        // Act
        client.RegisterCommand("Echo", handler);

        // Assert
        Assert.True(client.Commands.ContainsKey("Echo"));
        Assert.Equal(handler, client.Commands["Echo"]);
    }

    /// <summary>测试注册异步字符串命令</summary>
    [Fact]
    public void RegisterCommand_AsyncStringFunc_ShouldAddToCommands()
    {
        // Arrange
        var client = new TestCommandClient();
        Func<String?, Task<String?>> handler = async arg =>
        {
            await Task.Delay(1);
            return $"Async Echo: {arg}";
        };

        // Act
        client.RegisterCommand("AsyncEcho", handler);

        // Assert
        Assert.True(client.Commands.ContainsKey("AsyncEcho"));
    }

    /// <summary>测试注册命令模型处理器</summary>
    [Fact]
    public void RegisterCommand_CommandModelFunc_ShouldAddToCommands()
    {
        // Arrange
        var client = new TestCommandClient();
        Func<CommandModel, CommandReplyModel> handler = model => new CommandReplyModel
        {
            Id = model.Id,
            Status = CommandStatus.已完成,
            Data = "Done"
        };

        // Act
        client.RegisterCommand("Process", handler);

        // Assert
        Assert.True(client.Commands.ContainsKey("Process"));
    }

    /// <summary>测试执行简单字符串命令</summary>
    [Fact]
    public async Task ExecuteCommand_StringFunc_ShouldReturnResult()
    {
        // Arrange
        var client = new TestCommandClient();
        client.RegisterCommand("Echo", (String? arg) => $"Echo: {arg}");

        var model = new CommandModel
        {
            Id = 1,
            Command = "Echo",
            Argument = "Hello"
        };

        // Act
        var result = await client.ExecuteCommand(model);

        // Assert
        Assert.NotNull(result);
        Assert.Equal(CommandStatus.已完成, result.Status);
        Assert.Equal("Echo: Hello", result.Data);
    }

    /// <summary>测试执行异步命令</summary>
    [Fact]
    public async Task ExecuteCommand_AsyncFunc_ShouldReturnResult()
    {
        // Arrange
        var client = new TestCommandClient();
        client.RegisterCommand("AsyncEcho", async (String? arg) =>
        {
            await Task.Delay(1);
            return $"Async: {arg}";
        });

        var model = new CommandModel
        {
            Id = 2,
            Command = "AsyncEcho",
            Argument = "World"
        };

        // Act
        var result = await client.ExecuteCommand(model);

        // Assert
        Assert.NotNull(result);
        Assert.Equal(CommandStatus.已完成, result.Status);
        Assert.Equal("Async: World", result.Data);
    }

    /// <summary>测试执行命令模型处理器</summary>
    [Fact]
    public async Task ExecuteCommand_CommandModelFunc_ShouldReturnReply()
    {
        // Arrange
        var client = new TestCommandClient();
        client.RegisterCommand("Process", (CommandModel m) => new CommandReplyModel
        {
            Id = m.Id,
            Status = CommandStatus.已完成,
            Data = $"Processed: {m.Argument}"
        });

        var model = new CommandModel
        {
            Id = 3,
            Command = "Process",
            Argument = "Test"
        };

        // Act
        var result = await client.ExecuteCommand(model);

        // Assert
        Assert.NotNull(result);
        Assert.Equal(3, result.Id);
        Assert.Equal(CommandStatus.已完成, result.Status);
        Assert.Equal("Processed: Test", result.Data);
    }

    /// <summary>测试执行带取消令牌的异步命令</summary>
    [Fact]
    public async Task ExecuteCommand_WithCancellationToken_ShouldWork()
    {
        // Arrange
        var client = new TestCommandClient();
        client.RegisterCommand("CancelableCmd", async (CommandModel m, CancellationToken ct) =>
        {
            await Task.Delay(1, ct);
            return new CommandReplyModel
            {
                Id = m.Id,
                Status = CommandStatus.已完成,
                Data = "Cancelable Done"
            };
        });

        var model = new CommandModel
        {
            Id = 4,
            Command = "CancelableCmd",
            Argument = "Data"
        };

        using var cts = new CancellationTokenSource();

        // Act
        var result = await client.ExecuteCommand(model, cts.Token);

        // Assert
        Assert.NotNull(result);
        Assert.Equal(CommandStatus.已完成, result.Status);
    }

    /// <summary>测试执行不存在的命令</summary>
    [Fact]
    public async Task ExecuteCommand_NotFound_ShouldReturnError()
    {
        // Arrange
        var client = new TestCommandClient();

        var model = new CommandModel
        {
            Id = 5,
            Command = "NotExist",
            Argument = "Data"
        };

        // Act
        var result = await client.ExecuteCommand(model);

        // Assert
        Assert.NotNull(result);
        Assert.Equal(CommandStatus.错误, result.Status);
        Assert.Contains("找不到服务", result.Data);
    }

    /// <summary>测试命令名称不区分大小写</summary>
    [Fact]
    public async Task ExecuteCommand_CaseInsensitive_ShouldWork()
    {
        // Arrange
        var client = new TestCommandClient();
        client.RegisterCommand("TestCmd", (String? arg) => $"Result: {arg}");

        var model = new CommandModel
        {
            Id = 6,
            Command = "testcmd", // 小写
            Argument = "CaseTest"
        };

        // Act
        var result = await client.ExecuteCommand(model);

        // Assert
        Assert.NotNull(result);
        Assert.Equal(CommandStatus.已完成, result.Status);
        Assert.Equal("Result: CaseTest", result.Data);
    }

    /// <summary>测试Action命令(无返回值)</summary>
    [Fact]
    public async Task ExecuteCommand_ActionFunc_ShouldComplete()
    {
        // Arrange
        var client = new TestCommandClient();
        var executed = false;
        client.RegisterCommand("ActionCmd", (CommandModel m) => { executed = true; });

        var model = new CommandModel
        {
            Id = 7,
            Command = "ActionCmd",
            Argument = "Data"
        };

        // Act
        var result = await client.ExecuteCommand(model);

        // Assert
        Assert.True(executed);
        Assert.NotNull(result);
        Assert.Equal(CommandStatus.已完成, result.Status);
    }

    /// <summary>测试命令处理器内部异常</summary>
    [Fact]
    public async Task ExecuteCommand_ThrowsException_ShouldReturnError()
    {
        // Arrange
        var client = new TestCommandClient();
        client.RegisterCommand("ErrorCmd", (String? arg) =>
        {
            throw new InvalidOperationException("Test error");
#pragma warning disable CS0162
            return "Never";
#pragma warning restore CS0162
        });

        var model = new CommandModel
        {
            Id = 8,
            Command = "ErrorCmd",
            Argument = "Data"
        };

        // Act
        var result = await client.ExecuteCommand(model);

        // Assert
        Assert.NotNull(result);
        Assert.Equal(CommandStatus.错误, result.Status);
        Assert.Contains("Test error", result.Data);
    }

    /// <summary>测试命令客户端实现</summary>
    private class TestCommandClient : ICommandClient
    {
        public event EventHandler<CommandEventArgs>? Received;

        public IDictionary<String, Delegate> Commands { get; } = new Dictionary<String, Delegate>(StringComparer.OrdinalIgnoreCase);

        public void OnReceived(CommandEventArgs e) => Received?.Invoke(this, e);
    }
}