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

namespace ServerTest.Controllers;

/// <summary>原生 Agent 协议契约测试。使用与 StarAgent.Native/spec/protocol.md 一致的 PascalCase 报文验证 node/login、node/ping 与命令回复。</summary>
[Collection(StarTestServerCollection.Name)]
public class NativeAgentProtocolTests : StarTestBase
{
    public NativeAgentProtocolTests(StarTestServer fixture) : base(fixture) { }

    private async Task<(String token, Node node)> LoginNativeAsync(String productCode)
    {
        var client = _server.CreateClient();
        var macs = "AA-BB-CC-" + Rand.NextBytes(3).ToHex("-");
        var body = new
        {
            Code = "",
            Secret = "",
            ProductCode = productCode,
            ClientId = "127.0.0.1@1",
            Node = new
            {
                Version = "1.0.2026.1017",
                Compile = "2026-10-17 08:00:00",
                OSName = "Ubuntu",
                OSVersion = "24.04",
                Architecture = "X64",
                MachineName = "native_" + Rand.NextString(6),
                UserName = "root",
                Macs = macs,
                Runtime = productCode == "StarAgent-Go" ? "go1.24.0" : "rust1.99.0",
                Framework = productCode == "StarAgent-Go" ? "go1.24.0" : "rust1.99.0",
                TimeZone = "Asia/Shanghai",
                Time = "2026-10-17 10:00:00",
                Plugins = new[] { "agent:go", "cap:exec:false", "cap:upgrade=false", "os:linux" },
            },
        };

        var resp = await client.PostAsync("node/login", JsonContent.Create(body));
        var text = await resp.Content.ReadAsStringAsync();
        Assert.True(resp.IsSuccessStatusCode, "原生 Agent 登录应成功:" + text);

        var rs = ParseData<LoginResponse>(text);
        Assert.NotNull(rs);
        Assert.False(rs!.Token.IsNullOrEmpty(), "登录响应缺少令牌");
        Assert.False(rs.Code.IsNullOrEmpty(), "首次登录应下发节点编码");
        Assert.False(rs.Secret.IsNullOrEmpty(), "首次登录应下发节点密钥");

        var node = Node.FindByCode(rs.Code);
        Assert.NotNull(node);
        Assert.Equal(productCode, node!.ProductCode);

        return (rs.Token!, node);
    }

    /// <summary>解析裸对象或 {code,data,msg} 包装的响应。</summary>
    private static T? ParseData<T>(String text)
    {
        using var doc = JsonDocument.Parse(text);
        var root = doc.RootElement;
        if (root.ValueKind == JsonValueKind.Object && root.TryGetProperty("data", out var data))
            return data.Deserialize<T>(new JsonSerializerOptions(JsonSerializerDefaults.Web));

        return root.Deserialize<T>(new JsonSerializerOptions(JsonSerializerDefaults.Web));
    }

    [Theory]
    [InlineData("StarAgent-Go")]
    [InlineData("StarAgent-Rust")]
    public async Task NativeLogin_WithPascalCase_Works(String productCode)
    {
        var (token, node) = await LoginNativeAsync(productCode);
        try
        {
            var client = _server.CreateClient();
            client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);

            var ping = new
            {
                Time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
                IP = "10.0.0.8",
                Gateway = "10.0.0.1",
                Dns = "223.5.5.5",
                Macs = node.MACs,
                ProcessCount = 10,
                Memory = 1024UL,
                AvailableMemory = 512UL,
                CpuRate = 12.5,
                Uptime = 100,
                SystemLoad = 0.5,
            };

            var resp = await client.PostAsync("node/ping", JsonContent.Create(ping));
            var text = await resp.Content.ReadAsStringAsync();
            Assert.True(resp.IsSuccessStatusCode, "原生 Agent 心跳应成功:" + text);
            Assert.Contains("\"period\"", text, StringComparison.OrdinalIgnoreCase);
        }
        finally
        {
            node.Delete();
        }
    }
}