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

namespace XUnitTest.Integration;

/// <summary>HTTP模式集成测试</summary>
/// <remarks>
/// 验证 ApiServer 使用 HTTP 协议时,ApiHttpClient 端到端通信能力。
/// 包括基础调用、参数传递、异常处理、令牌传递等场景。
/// </remarks>
public class HttpIntegrationTests : DisposeBase
{
    private readonly ApiServer _Server;
    private readonly String _Address;

    public HttpIntegrationTests()
    {
        _Server = new ApiServer(new NetUri(NetType.Http, "*", 0))
        {
            Log = XTrace.Log,
            ShowError = true,
        };
        _Server.Register<HttpTestController>();
        _Server.Start();

        _Address = $"http://127.0.0.1:{_Server.Port}";
    }

    protected override void Dispose(Boolean disposing)
    {
        base.Dispose(disposing);
        _Server.TryDispose();
    }

    #region 基础HTTP调用
    [Fact(DisplayName = "HTTP模式_基础API调用")]
    public async Task HttpBasicApiCallTest()
    {
        IApiClient client = new ApiHttpClient(_Address);
        using var _ = client as IDisposable;

        var apis = await client.InvokeAsync<String[]>("Api/All");
        Assert.NotNull(apis);
        Assert.True(apis.Length > 0);
    }

    [Fact(DisplayName = "HTTP模式_带参数调用")]
    public async Task HttpParameterCallTest()
    {
        IApiClient client = new ApiHttpClient(_Address);
        using var _ = client as IDisposable;

        var result = await client.InvokeAsync<String>("HttpTest/Greet", new { name = "TestUser" });
        Assert.Equal("Hello, TestUser!", result);
    }

    [Fact(DisplayName = "HTTP模式_复杂对象返回")]
    public async Task HttpComplexReturnTest()
    {
        IApiClient client = new ApiHttpClient(_Address);
        using var _ = client as IDisposable;

        var result = await client.InvokeAsync<HttpTestResult>("HttpTest/GetInfo", new { id = 42, name = "TestItem" });
        Assert.NotNull(result);
        Assert.Equal(42, result.Id);
        Assert.Equal("TestItem", result.Name);
        Assert.True(result.Timestamp.Year >= 2020);
    }

    [Fact(DisplayName = "HTTP模式_计算操作")]
    public async Task HttpCalculateTest()
    {
        IApiClient client = new ApiHttpClient(_Address);
        using var _ = client as IDisposable;

        var result = await client.InvokeAsync<Int32>("HttpTest/Add", new { a = 100, b = 200 });
        Assert.Equal(300, result);
    }
    #endregion

    #region HTTP异常处理
    [Fact(DisplayName = "HTTP模式_服务不存在404")]
    public async Task HttpNotFoundTest()
    {
        IApiClient client = new ApiHttpClient(_Address);
        using var _ = client as IDisposable;

        var ex = await Assert.ThrowsAsync<ApiException>(
            () => client.InvokeAsync<Object>("HttpTest/NonExistent"));

        Assert.Equal(404, ex.Code);
    }

    [Fact(DisplayName = "HTTP模式_服务端内部异常500")]
    public async Task HttpServerErrorTest()
    {
        IApiClient client = new ApiHttpClient(_Address);
        using var _ = client as IDisposable;

        var ex = await Assert.ThrowsAsync<ApiException>(
            () => client.InvokeAsync<Object>("HttpTest/ThrowError"));

        Assert.Equal(500, ex.Code);
    }

    [Fact(DisplayName = "HTTP模式_自定义ApiException")]
    public async Task HttpCustomApiExceptionTest()
    {
        IApiClient client = new ApiHttpClient(_Address);
        using var _ = client as IDisposable;

        var ex = await Assert.ThrowsAsync<ApiException>(
            () => client.InvokeAsync<Object>("HttpTest/CustomError"));

        Assert.Equal(1001, ex.Code);
        Assert.Equal("自定义错误", ex.Message);
    }
    #endregion

    #region HTTP令牌传递
    [Fact(DisplayName = "HTTP模式_令牌传递")]
    public async Task HttpTokenTest()
    {
        var token = Rand.NextString(16);

        IApiClient client = new ApiHttpClient(_Address) { Token = token };
        using var _ = client as IDisposable;

        var result = await client.InvokeAsync<IDictionary<String, Object>>("Api/Info", new { state = "test" });
        Assert.NotNull(result);
        // 令牌通过请求头传递
        Assert.Equal(token, result["token"]?.ToString());
    }
    #endregion

    #region HTTP多次调用
    [Fact(DisplayName = "HTTP模式_多次连续调用")]
    public async Task HttpMultipleCallsTest()
    {
        IApiClient client = new ApiHttpClient(_Address);
        using var _ = client as IDisposable;

        for (var i = 0; i < 10; i++)
        {
            var result = await client.InvokeAsync<Int32>("HttpTest/Add", new { a = i, b = 1 });
            Assert.Equal(i + 1, result);
        }
    }

    [Fact(DisplayName = "HTTP模式_并发调用")]
    public async Task HttpConcurrentCallsTest()
    {
        IApiClient client = new ApiHttpClient(_Address);
        using var _ = client as IDisposable;

        var tasks = new List<Task<Int32>>();
        for (var i = 0; i < 20; i++)
        {
            var index = i;
            tasks.Add(client.InvokeAsync<Int32>("HttpTest/Add", new { a = index, b = 1 }));
        }

        var results = await Task.WhenAll(tasks);
        for (var i = 0; i < 20; i++)
        {
            Assert.Equal(i + 1, results[i]);
        }
    }
    #endregion

    #region 辅助类
    class HttpTestController
    {
        public String Greet(String name) => $"Hello, {name}!";

        public Int32 Add(Int32 a, Int32 b) => a + b;

        public HttpTestResult GetInfo(Int32 id, String name) => new()
        {
            Id = id,
            Name = name,
            Timestamp = DateTime.Now,
        };

        public void ThrowError() => throw new InvalidOperationException("服务端异常");

        public void CustomError() => throw new ApiException(1001, "自定义错误");
    }

    class HttpTestResult
    {
        public Int32 Id { get; set; }
        public String Name { get; set; } = "";
        public DateTime Timestamp { get; set; }
    }
    #endregion
}