v9.10.2019.0101  全面巩固批量Insert/Update/Upsert,支持数据备份、恢复和同步,支持实体列表保存到文件以及加载
大石头 authored at 2019-01-01 13:38:33
8.10 KiB
X
using System.Net;
using NewLife.Http;
using NewLife.Log;
using Xunit;

namespace XUnitTest.Http;

/// <summary>HTTP中间件测试</summary>
public class HttpMiddlewareTests
{
    private static HttpServer CreateServer(Action<HttpServer> configure)
    {
        var server = new HttpServer
        {
            Port = 0,
            Log = XTrace.Log,
            SessionLog = XTrace.Log,
        };
        configure(server);
        server.Start();
        return server;
    }

    [Fact(DisplayName = "CORS设置响应头")]
    public async Task CorsSetsHeaders()
    {
        using var server = CreateServer(s =>
        {
            s.UseCors();
            s.Map("/test", () => "OK");
        });

        var url = $"http://127.0.0.1:{server.Port}/test";
        using var http = new HttpClient();
        var req = new HttpRequestMessage(HttpMethod.Get, url);
        req.Headers.Add("Origin", "http://example.com");
        var res = await http.SendAsync(req);

        Assert.True(res.IsSuccessStatusCode);
        Assert.Equal("*", res.Headers.GetValues("Access-Control-Allow-Origin").FirstOrDefault());
        Assert.NotNull(res.Headers.GetValues("Access-Control-Allow-Methods").FirstOrDefault());
        Assert.NotNull(res.Headers.GetValues("Access-Control-Allow-Headers").FirstOrDefault());
    }

    [Fact(DisplayName = "CORS OPTIONS返回204")]
    public async Task CorsOptionsReturns204()
    {
        using var server = CreateServer(s =>
        {
            s.UseCors();
            s.Map("/test", () => "OK");
        });

        var url = $"http://127.0.0.1:{server.Port}/test";
        using var http = new HttpClient();
        var req = new HttpRequestMessage(HttpMethod.Options, url);
        req.Headers.Add("Origin", "http://example.com");
        var res = await http.SendAsync(req);

        Assert.Equal(HttpStatusCode.NoContent, res.StatusCode);
    }

    [Fact(DisplayName = "CORS自定义AllowOrigin")]
    public async Task CorsCustomAllowOrigin()
    {
        var cors = new CorsMiddleware { AllowOrigin = "https://myapp.com" };
        using var server = CreateServer(s =>
        {
            s.Use(cors.Invoke);
            s.Map("/test", () => "OK");
        });

        var url = $"http://127.0.0.1:{server.Port}/test";
        using var http = new HttpClient();
        var req = new HttpRequestMessage(HttpMethod.Get, url);
        req.Headers.Add("Origin", "https://myapp.com");
        var res = await http.SendAsync(req);

        Assert.True(res.IsSuccessStatusCode);
        Assert.Equal("https://myapp.com", res.Headers.GetValues("Access-Control-Allow-Origin").FirstOrDefault());
    }

    [Fact(DisplayName = "ErrorHandler正常请求通过")]
    public async Task ErrorHandlerPassesThrough()
    {
        using var server = CreateServer(s =>
        {
            s.UseErrorHandler(false);
            s.Map("/ok", () => "All good");
        });

        var url = $"http://127.0.0.1:{server.Port}/ok";
        using var http = new HttpClient();
        var res = await http.GetAsync(url);

        Assert.Equal(HttpStatusCode.OK, res.StatusCode);
        var body = await res.Content.ReadAsStringAsync();
        Assert.Equal("All good", body);
    }

    [Fact(DisplayName = "ErrorHandler捕获异常返回500")]
    public async Task ErrorHandlerCatchesException()
    {
        using var server = CreateServer(s =>
        {
            s.UseErrorHandler(false);
            s.Map("/error", (HttpProcessDelegate)(ctx => { throw new InvalidOperationException("测试异常"); }));
        });

        var url = $"http://127.0.0.1:{server.Port}/error";
        using var http = new HttpClient();
        var res = await http.GetAsync(url);

        Assert.Equal(HttpStatusCode.InternalServerError, res.StatusCode);
    }

    [Fact(DisplayName = "ErrorHandler捕获HttpException带状态码")]
    public async Task ErrorHandlerCatchesHttpException()
    {
        using var server = CreateServer(s =>
        {
            s.UseErrorHandler(false);
            s.Map("/badrequest", (HttpProcessDelegate)(ctx => { throw new HttpException(HttpStatusCode.BadRequest, "参数错误"); }));
            s.Map("/forbidden", (HttpProcessDelegate)(ctx => { throw new HttpException(HttpStatusCode.Forbidden, "禁止访问"); }));
        });

        using var http = new HttpClient();

        var url1 = $"http://127.0.0.1:{server.Port}/badrequest";
        var res1 = await http.GetAsync(url1);
        Assert.Equal(HttpStatusCode.BadRequest, res1.StatusCode);

        var url2 = $"http://127.0.0.1:{server.Port}/forbidden";
        var res2 = await http.GetAsync(url2);
        Assert.Equal(HttpStatusCode.Forbidden, res2.StatusCode);
    }

    [Fact(DisplayName = "ErrorHandler IncludeDetails返回JSON错误")]
    public async Task ErrorHandlerIncludeDetails()
    {
        using var server = CreateServer(s =>
        {
            s.UseErrorHandler(includeDetails: true);
            s.Map("/error", (HttpProcessDelegate)(ctx => { throw new InvalidOperationException("详细异常"); }));
        });

        var url = $"http://127.0.0.1:{server.Port}/error";
        using var http = new HttpClient();
        var res = await http.GetAsync(url);

        Assert.Equal(HttpStatusCode.InternalServerError, res.StatusCode);
        var body = await res.Content.ReadAsStringAsync();
        Assert.Contains("详细异常", body);
        Assert.Contains("InvalidOperationException", body);
    }

    [Fact(DisplayName = "多个中间件串联")]
    public async Task MultipleMiddlewareChain()
    {
        using var server = CreateServer(s =>
        {
            s.UseErrorHandler(false);
            s.UseCors();
            s.Map("/test", () => "Chained");
        });

        var url = $"http://127.0.0.1:{server.Port}/test";
        using var http = new HttpClient();
        var req = new HttpRequestMessage(HttpMethod.Get, url);
        req.Headers.Add("Origin", "http://example.com");
        var res = await http.SendAsync(req);

        Assert.True(res.IsSuccessStatusCode);
        Assert.Equal("*", res.Headers.GetValues("Access-Control-Allow-Origin").FirstOrDefault());
        var body = await res.Content.ReadAsStringAsync();
        Assert.Equal("Chained", body);
    }

    [Fact(DisplayName = "CORS凭据模式配合通配源_不发出AllowOrigin")]
    public async Task CorsCredentialsWithWildcard_NoAllowOrigin()
    {
        var cors = new CorsMiddleware { AllowOrigin = "*", AllowCredentials = true };
        using var server = CreateServer(s =>
        {
            s.Use(cors.Invoke);
            s.Map("/test", () => "OK");
        });

        var url = $"http://127.0.0.1:{server.Port}/test";
        using var http = new HttpClient();
        var req = new HttpRequestMessage(HttpMethod.Get, url);
        req.Headers.Add("Origin", "http://example.com");
        var res = await http.SendAsync(req);

        // 通配源与凭据同用被规范禁止,失败关闭:既不回显请求源(回显等于放行任意源携带凭据),也不发通配源
        Assert.True(res.IsSuccessStatusCode);
        Assert.False(res.Headers.Contains("Access-Control-Allow-Origin"));
        Assert.Equal("true", res.Headers.GetValues("Access-Control-Allow-Credentials").FirstOrDefault());
    }

    [Fact(DisplayName = "CORS凭据模式配合明确源_正常发出AllowOrigin")]
    public async Task CorsCredentialsWithExplicitOrigin_EmitsOrigin()
    {
        var cors = new CorsMiddleware { AllowOrigin = "https://myapp.com", AllowCredentials = true };
        using var server = CreateServer(s =>
        {
            s.Use(cors.Invoke);
            s.Map("/test", () => "OK");
        });

        var url = $"http://127.0.0.1:{server.Port}/test";
        using var http = new HttpClient();
        var req = new HttpRequestMessage(HttpMethod.Get, url);
        req.Headers.Add("Origin", "https://myapp.com");
        var res = await http.SendAsync(req);

        // 回归护栏:失败关闭只针对通配源,明确配置的源仍须正常发出
        Assert.Equal("https://myapp.com", res.Headers.GetValues("Access-Control-Allow-Origin").FirstOrDefault());
        Assert.Equal("true", res.Headers.GetValues("Access-Control-Allow-Credentials").FirstOrDefault());
    }
}