节点在线、应用在线、配置在线使用令牌查询
大石头 authored at 2021-12-16 19:49:30
4.31 KiB
Stardust
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using NewLife.Security;
using Stardust.Data;
using Stardust.Data.Configs;
using Stardust.Server;
using Xunit;

namespace ServerTest.Controllers;

/// <summary>配置中心写入测试。覆盖 Config/SetAll 落库与读取回环,以及只读应用拒绝写入</summary>
[Collection(StarTestServerCollection.Name)]
public class ConfigSetAllTests : StarTestBase
{
    public ConfigSetAllTests(StarTestServer fixture) : base(fixture) { }

    /// <summary>创建测试应用,返回应用名与密钥</summary>
    private static (String name, String secret) CreateApp()
    {
        var name = "csa_" + Rand.NextString(8);
        var secret = "csa-secret-" + Rand.NextString(6);
        var app = new App { Name = name, Secret = secret, Enable = true };
        app.Insert();

        return (name, secret);
    }

    private static void Cleanup(String name)
    {
        var config = AppConfig.FindByName(name);
        if (config != null)
        {
            foreach (var item in ConfigData.FindAllByApp(config.Id))
            {
                item.Delete();
            }
            config.Delete();
        }

        App.FindByName(name)?.Delete();
    }

    [Fact(DisplayName = "批量设置配置后可通过拉取配置读回")]
    public async Task SetAll_ThenGetAll_RoundTrip()
    {
        var (name, secret) = CreateApp();
        try
        {
            var key = "cs_key_" + Rand.NextString(4);
            var value = "cs_value_" + Rand.NextString(4);

            var client = _server.CreateClient();
            var configs = new Dictionary<String, Object> { [key] = value };
            var setResp = await client.PostAsync($"/Config/SetAll?appId={name}&secret={secret}",
                JsonContent.Create(new { AppId = name, Secret = secret, Configs = configs }));
            var setBody = await setResp.Content.ReadAsStringAsync();

            Assert.True(setResp.IsSuccessStatusCode, "设置配置应成功,实际响应:" + setBody);
            Assert.Equal("1", ExtractData(setBody));

            // 落库校验:新配置进入"未发布"的新版本
            var config = AppConfig.FindByName(name);
            Assert.NotNull(config);
            var data = ConfigData.FindAllByApp(config!.Id).FirstOrDefault(e => e.Key == key);
            Assert.NotNull(data);
            Assert.Equal(value, data!.Value);
            Assert.True(data.Version > 0);

            // 发布后拉取配置应能读回刚写入的键值
            config.Publish();

            var getResp = await client.GetAsync($"/Config/GetAll?appId={name}&secret={secret}");
            var getBody = await getResp.Content.ReadAsStringAsync();

            Assert.True(getResp.IsSuccessStatusCode, "拉取配置应成功,实际响应:" + getBody);
            Assert.Contains(key, getBody);
            Assert.Contains(value, getBody);
        }
        finally
        {
            Cleanup(name);
        }
    }

    [Fact(DisplayName = "只读应用禁止批量设置配置")]
    public async Task SetAll_ReadonlyApp_Rejected()
    {
        var (name, secret) = CreateApp();
        try
        {
            var client = _server.CreateClient();

            // 首次拉取会按应用名自动创建 AppConfig
            await client.GetAsync($"/Config/GetAll?appId={name}&secret={secret}");

            var config = AppConfig.FindByName(name);
            Assert.NotNull(config);
            config!.Readonly = true;
            config.Update();

            var configs = new Dictionary<String, Object> { ["cs_ro"] = "1" };
            var resp = await client.PostAsync($"/Config/SetAll?appId={name}&secret={secret}",
                JsonContent.Create(new { AppId = name, Secret = secret, Configs = configs }));
            var body = await resp.Content.ReadAsStringAsync();

            Assert.Contains("只读", body);
        }
        finally
        {
            Cleanup(name);
        }
    }

    private static String ExtractData(String body)
    {
        var m = Regex.Match(body, "\"data\"\\s*:\\s*([0-9]+)");
        return m.Success ? m.Groups[1].Value : null;
    }
}