节点在线、应用在线、配置在线使用令牌查询
大石头 authored at 2021-12-16 19:49:30
4.35 KiB
Stardust
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using NewLife;
using NewLife.Log;
using NewLife.Security;
using Stardust.Data;
using Stardust.Server.Services;
using Xunit;

namespace ServerTest.Services;

/// <summary>服务健康检查测试。覆盖探测成功回写健康状态与探测失败的降级记录(REG-5)</summary>
public class RegistryHealthCheckTests
{
    /// <summary>创建只注入跟踪器的注册服务实例。HealthCheck 只用到 _tracer,其余依赖无需初始化</summary>
    private static RegistryService CreateService()
    {
        var svc = (RegistryService)RuntimeHelpers.GetUninitializedObject(typeof(RegistryService));
        typeof(RegistryService).GetField("_tracer", BindingFlags.NonPublic | BindingFlags.Instance)!
            .SetValue(svc, DefaultTracer.Instance);
        return svc;
    }

    /// <summary>取一个空闲端口</summary>
    private static Int32 GetFreePort()
    {
        var listener = new TcpListener(IPAddress.Loopback, 0);
        listener.Start();
        var port = ((IPEndPoint)listener.LocalEndpoint).Port;
        listener.Stop();
        return port;
    }

    private static (Service service, AppService appService) CreateAppService(String healthAddress, String address)
    {
        var service = new Service
        {
            Name = "svc_" + Rand.NextString(8),
            HealthAddress = healthAddress,
            Enable = true,
        };
        service.Insert();

        var appService = new AppService
        {
            ServiceId = service.Id,
            ServiceName = service.Name,
            Address = address,
            Enable = true,
        };
        appService.Insert();

        return (service, appService);
    }

    [Fact(DisplayName = "健康检查成功时回写健康状态与响应内容")]
    public async Task HealthCheck_Success_SetsHealthy()
    {
        var port = GetFreePort();
        var prefix = $"http://127.0.0.1:{port}/";
        var listener = new HttpListener();
        listener.Prefixes.Add(prefix);
        listener.Start();

        var (service, appService) = CreateAppService("/health", $"http://127.0.0.1:{port}");
        try
        {
            _ = Task.Run(async () =>
            {
                var ctx = await listener.GetContextAsync();
                var body = Encoding.UTF8.GetBytes("ok");
                ctx.Response.StatusCode = 200;
                ctx.Response.ContentType = "text/plain";
                await ctx.Response.OutputStream.WriteAsync(body, 0, body.Length);
                ctx.Response.Close();
            });

            await CreateService().HealthCheck(appService);

            Assert.True(appService.Healthy);
            Assert.Equal("ok", appService.CheckResult);
            Assert.Equal(1, appService.CheckTimes);
            Assert.True(appService.LastCheck.Year > 2000);

            // 已落库
            var saved = AppService.FindById(appService.Id);
            Assert.NotNull(saved);
            Assert.True(saved.Healthy);
        }
        finally
        {
            listener.Stop();
            appService.Delete();
            service.Delete();
        }
    }

    [Fact(DisplayName = "目标不可达时记为不健康并保留异常信息")]
    public async Task HealthCheck_Unreachable_MarksUnhealthy()
    {
        var port = GetFreePort();
        var (service, appService) = CreateAppService("/health", $"http://127.0.0.1:{port}");
        try
        {
            await CreateService().HealthCheck(appService);

            Assert.False(appService.Healthy);
            Assert.False(appService.CheckResult.IsNullOrEmpty());
            Assert.Equal(1, appService.CheckTimes);
        }
        finally
        {
            appService.Delete();
            service.Delete();
        }
    }

    [Fact(DisplayName = "未配置健康检测地址时直接跳过")]
    public async Task HealthCheck_NoAddress_Skip()
    {
        var (service, appService) = CreateAppService(null, "http://127.0.0.1:1");
        try
        {
            await CreateService().HealthCheck(appService);

            // 不探测、不回写
            Assert.Equal(0, appService.CheckTimes);
        }
        finally
        {
            appService.Delete();
            service.Delete();
        }
    }
}