节点在线、应用在线、配置在线使用令牌查询
大石头 authored at 2021-12-16 19:49:30
7.57 KiB
Stardust
using System;
using System.Collections.Generic;
using System.Linq;
using NewLife;
using NewLife.Caching;
using Stardust.Data.Nodes;
using Stardust.Models;
using Stardust.Server;
using Stardust.Server.Services;
using Xunit;

namespace ServerTest.Install;

/// <summary>原生 Agent 引导安装信息(InstallService.GetBootstrap)测试。
/// 语义:只有原生 Agent(StarAgent-Go/Rust/Cpp)才返回引导信息;
/// 完整版安装包按固定产品编码匹配,运行时包按节点系统与架构匹配,选包规则与安装页一致。</summary>
public class InstallServiceBootstrapTests
{
    /// <summary>临时停用其它 StarAgent 发布版本与运行时包,避免共享测试库遗留数据干扰断言。</summary>
    private sealed class CatalogIsolation : IDisposable
    {
        private readonly List<ProductRelease> _releases = [];
        private readonly List<DotNetPackage> _packages = [];

        public CatalogIsolation()
        {
            foreach (var item in ProductRelease.FindAll().Where(e => e.Enable && e.ProductCode.EqualIgnoreCase("StarAgent")).ToList())
            {
                item.Enable = false;
                item.Update();
                _releases.Add(item);
            }

            foreach (var item in DotNetPackage.FindAll().Where(e => e.Enable).ToList())
            {
                item.Enable = false;
                item.Update();
                _packages.Add(item);
            }
        }

        public void Dispose()
        {
            foreach (var item in _packages)
            {
                item.Enable = true;
                item.Update();
            }

            foreach (var item in _releases)
            {
                item.Enable = true;
                item.Update();
            }
        }
    }

    /// <summary>测试用发布版本、安装包、运行时包与节点,结束后清理。</summary>
    private sealed class BootstrapScope : IDisposable
    {
        private readonly List<ProductPackage> _packages = [];
        private readonly List<DotNetPackage> _runtimes = [];

        public ProductRelease Release { get; }

        public Node Node { get; }

        public BootstrapScope(String nodeProductCode)
        {
            Release = new ProductRelease
            {
                Version = "9.9.9999." + Guid.NewGuid().ToString("n")[..8],
                ProductCode = "StarAgent",
                Enable = true,
                Force = false,
                Channel = NodeChannels.Release,
            };
            Release.Insert();

            Node = new Node
            {
                Code = "test-bs-" + Guid.NewGuid().ToString("n")[..8],
                Name = "test-bs",
                ProductCode = nodeProductCode,
                Runtime = "cpp20",
                OSKind = OSKinds.Ubuntu,
                Architecture = "Arm64",
                Version = "3.6.2026.0104",
                Enable = true,
            };
            Node.Insert();
        }

        public ProductPackage AddPackage(OSKind os, CpuArch arch, String runtime = "*")
        {
            var pkg = new ProductPackage
            {
                ReleaseId = Release.Id,
                OSKind = os,
                Architecture = arch,
                TargetRuntime = runtime,
                FileName = $"staragent-{Guid.NewGuid():n}.tar.gz",
                Source = "http://127.0.0.1/files/" + Guid.NewGuid().ToString("n") + ".tar.gz",
                Size = 2048,
                FileHash = "0123456789abcdef0123456789abcdef",
            };
            pkg.Insert();
            _packages.Add(pkg);

            return pkg;
        }

        public DotNetPackage AddRuntime(String version, OSKind os, CpuArch arch)
        {
            var pkg = new DotNetPackage
            {
                Version = version,
                Kind = "runtime",
                OSKind = os,
                Architecture = arch,
                FileName = $"dotnet-runtime-{version}-linux-arm64.tar.gz",
                Source = "https://example.com/dotnet/" + Guid.NewGuid().ToString("n") + ".tar.gz",
                Size = 1024,
                FileHash = "0123456789abcdef0123456789abcdef",
                Enable = true,
                // 清单与运行时匹配按通道过滤,必须显式指定 Release(枚举值不为 0)
                Channel = NodeChannels.Release,
            };
            pkg.Insert();
            _runtimes.Add(pkg);

            return pkg;
        }

        public void Dispose()
        {
            foreach (var pkg in _packages) pkg.Delete();
            foreach (var pkg in _runtimes) pkg.Delete();
            Node.Delete();
            Release.Delete();
        }
    }

    private static InstallService CreateService()
    {
        var provider = new CacheProvider { Cache = new MemoryCache(), InnerCache = new MemoryCache() };
        return new InstallService(StarServerSetting.Current, provider, new DownloadTokenService(provider));
    }

    /// <summary>原生 Agent 能同时拿到运行时包与完整版安装包,运行时版本取自安装包自身。</summary>
    [Fact]
    [Trait("Category", "Install")]
    public void NativeAgent_ReturnsRuntimeAndAgentPackages()
    {
        using var isolation = new CatalogIsolation();
        using var scope = new BootstrapScope("StarAgent-Cpp");

        var agentPkg = scope.AddPackage(OSKind.Linux, CpuArch.Arm64);
        var runtimePkg = scope.AddRuntime("10.0.9", OSKind.Linux, CpuArch.Arm64);

        var model = CreateService().GetBootstrap(scope.Node);

        Assert.NotNull(model);
        Assert.Equal(scope.Release.Version, model!.Version);
        Assert.Equal(scope.Node.Code, model.Code);

        Assert.NotNull(model.Agent);
        Assert.Equal(agentPkg.FileName, model.Agent!.FileName);
        Assert.Equal(agentPkg.Source, model.Agent.Source);
        Assert.Equal(scope.Release.Version, model.Agent.Version);

        Assert.NotNull(model.Runtime);
        Assert.Equal("10.0.9", model.Runtime!.Version);
        Assert.Equal("runtime", model.Runtime.Kind);
        Assert.Equal(runtimePkg.Source, model.Runtime.Source);
    }

    /// <summary>.NET 版 Agent 自身就是完整版,不返回引导信息。</summary>
    [Fact]
    [Trait("Category", "Install")]
    public void DotNetAgent_ReturnsNull()
    {
        using var isolation = new CatalogIsolation();
        using var scope = new BootstrapScope("StarAgent");

        scope.AddPackage(OSKind.Linux, CpuArch.Arm64);
        scope.AddRuntime("10.0.9", OSKind.Linux, CpuArch.Arm64);

        Assert.Null(CreateService().GetBootstrap(scope.Node));
    }

    /// <summary>服务端没有完整版发布包时返回空,不产生半套引导信息。</summary>
    [Fact]
    [Trait("Category", "Install")]
    public void MissingAgentPackage_ReturnsNull()
    {
        using var isolation = new CatalogIsolation();
        using var scope = new BootstrapScope("StarAgent-Go");

        scope.AddRuntime("10.0.9", OSKind.Linux, CpuArch.Arm64);

        Assert.Null(CreateService().GetBootstrap(scope.Node));
    }

    /// <summary>节点系统与架构优先精确匹配,通用包兜底。</summary>
    [Fact]
    [Trait("Category", "Install")]
    public void NativeAgent_PrefersExactOSAndArch()
    {
        using var isolation = new CatalogIsolation();
        using var scope = new BootstrapScope("StarAgent-Rust");

        scope.AddPackage(OSKind.Any, CpuArch.Any, "10");
        var exact = scope.AddPackage(OSKind.Linux, CpuArch.Arm64, "10");

        var model = CreateService().GetBootstrap(scope.Node);

        Assert.NotNull(model);
        Assert.Equal(exact.FileName, model!.Agent!.FileName);
    }
}