节点在线、应用在线、配置在线使用令牌查询
大石头 authored at 2021-12-16 19:49:30
4.45 KiB
Stardust
using System.Text.RegularExpressions;
using NewLife;
using Stardust.WebApiTests.Fixtures;
using Xunit;
using Xunit.Abstractions;

namespace Stardust.WebApiTests.Contract;

/// <summary>列表列头契约:meta 下发的列名不得残留未解析的单元格模板(如 <c>{Name}</c>)</summary>
/// <remarks>
/// 控制器习惯把单元格文字模板写在 <c>ListField.DisplayName</c>(本地化会把它当列头输出)或 <c>ListField.Text</c>。
/// 统一约定:单元格模板写 <c>Text</c>,列名由 <c>Header</c> 或实体字段描述提供;
/// 本契约遍历全部实体页面,防止 <c>{Xxx}</c> 这类模板再次漏到表头。
/// </remarks>
[Collection(WebApiCollection.Name)]
public class MetaHeaderContractTests
{
    private static readonly Regex _template = new(@"^\s*\{[^{}]+\}\s*$", RegexOptions.Compiled);

    private readonly ApiClient _api;

    public MetaHeaderContractTests(WebApiTestFactory factory, ITestOutputHelper output) => _api = new(factory, output);

    public static TheoryData<String> Endpoints => new()
    {
        "Configs/AppConfig/meta",
        "Configs/AppRule/meta",
        "Configs/ConfigData/meta",
        "Configs/ConfigHistory/meta",
        "Deployment/AppBuildNode/meta",
        "Deployment/AppDeploy/meta",
        "Deployment/AppDeployHistory/meta",
        "Deployment/AppDeployNode/meta",
        "Deployment/AppDeployVersion/meta",
        "Deployment/AppPipeline/meta",
        "Deployment/AppPipelineRun/meta",
        "Deployment/AppPipelineStep/meta",
        "Deployment/Attachment/meta",
        "Deployment/SslCertificate/meta",
        "Gateway/GatewayCert/meta",
        "Gateway/GatewayCluster/meta",
        "Gateway/GatewayNode/meta",
        "Gateway/GatewayRoute/meta",
        "Monitors/AlarmGroup/meta",
        "Monitors/AlarmHistory/meta",
        "Monitors/AlarmRecord/meta",
        "Monitors/AppDayStat/meta",
        "Monitors/AppMinuteStat/meta",
        "Monitors/AppTracer/meta",
        "Monitors/SampleData/meta",
        "Monitors/Trace/meta",
        "Monitors/TraceData/meta",
        "Monitors/TraceDayStat/meta",
        "Monitors/TraceHourStat/meta",
        "Monitors/TraceItem/meta",
        "Monitors/TraceMinuteStat/meta",
        "Monitors/TraceRule/meta",
        "MySql/MySqlData/meta",
        "MySql/MySqlNode/meta",
        "Nodes/DomainProvider/meta",
        "Nodes/DotNetPackage/meta",
        "Nodes/Node/meta",
        "Nodes/NodeCommand/meta",
        "Nodes/NodeData/meta",
        "Nodes/NodeFramework/meta",
        "Nodes/NodeHistory/meta",
        "Nodes/NodeLocation/meta",
        "Nodes/NodeOnline/meta",
        "Nodes/NodeRule/meta",
        "Nodes/NodeStat/meta",
        "Nodes/NodeVersion/meta",
        "Nodes/ProductPackage/meta",
        "Nodes/ProductRelease/meta",
        "Platform/GalaxyProject/meta",
        "Platform/McpAudit/meta",
        "Platform/McpToken/meta",
        "Platform/McpTokenResource/meta",
        "Platform/ProjectUser/meta",
        "Redis/RedisData/meta",
        "Redis/RedisMessageQueue/meta",
        "Redis/RedisNode/meta",
        "Registry/App/meta",
        "Registry/AppClientLog/meta",
        "Registry/AppCommand/meta",
        "Registry/AppConsume/meta",
        "Registry/AppHistory/meta",
        "Registry/AppMeter/meta",
        "Registry/AppOnline/meta",
        "Registry/AppService/meta",
        "Registry/Service/meta",
    };

    [Theory]
    [MemberData(nameof(Endpoints))]
    public async Task ListHeaders_NotTemplate(String endpoint)
    {
        var api = await _api.GetJsonAsync(endpoint);

        Assert.True(api.Data.TryGetProperty("list", out var fields), $"[{endpoint}] meta 响应缺少 list 区段");

        foreach (var field in fields.EnumerateArray())
        {
            var name = GetString(field, "name");
            foreach (var key in new[] { "header", "displayName" })
            {
                var text = GetString(field, key);
                if (text.IsNullOrEmpty()) continue;

                Assert.False(_template.IsMatch(text), $"[{endpoint}] 字段 {name} 的列名仍是模板:{text}");
            }
        }
    }

    private static String? GetString(System.Text.Json.JsonElement field, String name)
    {
        foreach (var prop in field.EnumerateObject())
        {
            if (String.Equals(prop.Name, name, StringComparison.OrdinalIgnoreCase))
                return prop.Value.ValueKind == System.Text.Json.JsonValueKind.String ? prop.Value.GetString() : null;
        }

        return null;
    }
}