Namespaces, assemblies, projects and directories, the solution, binaries, images, systemd units, config paths, environment variables, protocol method names, the media type and the raw chunk magic all move to MoShiro/moshiro. Existing data and deployments are not compatible and must be recreated. Also fix the architecture tests after the solution layout change and make the update tests independent of their order.
132 lines
5.4 KiB
C#
132 lines
5.4 KiB
C#
using System.Text.Json;
|
|
using Fleet.Core;
|
|
using MoShiro.Protocol;
|
|
|
|
namespace MoShiro.Protocol.Tests;
|
|
|
|
public sealed class CatalogTests
|
|
{
|
|
private static JsonElement Json(string text) => JsonDocument.Parse(text).RootElement.Clone();
|
|
|
|
[Fact]
|
|
public void EveryKind_HasBoundedParams_ValidDefaults_AndUniqueNames()
|
|
{
|
|
Assert.Equal(Kinds.All.Count, Kinds.All.Select(k => k.Id).Distinct().Count());
|
|
foreach (KindSpec kind in Kinds.All)
|
|
{
|
|
ParamResolution defaults = ParamResolver.Resolve(kind.Params, null);
|
|
Assert.True(defaults.IsValid, $"{kind.Id}: defaults must be valid");
|
|
Assert.Equal(kind.Params.Count, kind.Params.Select(p => p.Name).Distinct().Count());
|
|
Assert.True(kind.BudgetBitsPerSecond(defaults.Values!) >= 0);
|
|
foreach (ParamSpec spec in kind.Params)
|
|
{
|
|
// 每个参数的默认值必须能通过自己的校验(上下限在构造时已写明)
|
|
Assert.True(spec.TryNormalize(spec.DefaultValue, out _, out string error), $"{kind.Id}.{spec.Name}: {error}");
|
|
}
|
|
|
|
foreach (StreamSpec stream in kind.Streams)
|
|
{
|
|
Assert.Equal(stream.Columns.Count, stream.Columns.Select(c => c.Name).Distinct().Count());
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void DefaultLineTemplates_StayWithinTheLineBudget()
|
|
{
|
|
long total = 0;
|
|
foreach (KindSpec kind in new[] { Kinds.UdpEcho, Kinds.TcpEcho })
|
|
{
|
|
total += kind.BudgetBitsPerSecond(ParamResolver.Resolve(kind.Params, null).Values!);
|
|
}
|
|
|
|
Assert.InRange(total, 1, ProtocolLimits.MaxLineBudgetBitsPerSecond);
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_FillsDefaults_AndReportsEveryProblem()
|
|
{
|
|
var input = new Dictionary<string, JsonElement>
|
|
{
|
|
["rate_hz"] = Json("500"),
|
|
["size"] = Json("\"big\""),
|
|
["bogus"] = Json("1"),
|
|
};
|
|
ParamResolution result = ParamResolver.Resolve(Kinds.UdpEcho.Params, input);
|
|
|
|
Assert.False(result.IsValid);
|
|
Assert.Contains("between 1 and 200", result.Errors["rate_hz"]);
|
|
Assert.Equal("must be an integer", result.Errors["size"]);
|
|
Assert.Contains("not a parameter", result.Errors["bogus"]);
|
|
Assert.False(result.Errors.ContainsKey("port"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_AcceptsBoundariesAndRejectsJustOutside()
|
|
{
|
|
Assert.True(ParamResolver.Resolve(Kinds.UdpEcho.Params, new Dictionary<string, JsonElement> { ["rate_hz"] = Json("1") }).IsValid);
|
|
Assert.True(ParamResolver.Resolve(Kinds.UdpEcho.Params, new Dictionary<string, JsonElement> { ["rate_hz"] = Json("200") }).IsValid);
|
|
Assert.False(ParamResolver.Resolve(Kinds.UdpEcho.Params, new Dictionary<string, JsonElement> { ["rate_hz"] = Json("0") }).IsValid);
|
|
Assert.False(ParamResolver.Resolve(Kinds.UdpEcho.Params, new Dictionary<string, JsonElement> { ["rate_hz"] = Json("201") }).IsValid);
|
|
Assert.False(ParamResolver.Resolve(Kinds.UdpEcho.Params, new Dictionary<string, JsonElement> { ["rate_hz"] = Json("1.5") }).IsValid);
|
|
Assert.False(ParamResolver.Resolve(Kinds.UdpTrain.Params, new Dictionary<string, JsonElement> { ["direction"] = Json("\"sideways\"") }).IsValid);
|
|
Assert.False(ParamResolver.Resolve(Kinds.TcpEcho.Params, new Dictionary<string, JsonElement> { ["tls"] = Json("1") }).IsValid);
|
|
}
|
|
|
|
[Fact]
|
|
public void Kinds_AreUnknownWhenNotDeclared()
|
|
{
|
|
Assert.Null(Kinds.Find("nope"));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("continuous", true)]
|
|
[InlineData("once", true)]
|
|
[InlineData("every:60", true)]
|
|
[InlineData("every:59", false)]
|
|
[InlineData("every:", false)]
|
|
[InlineData("every:-5", false)]
|
|
[InlineData("hourly", false)]
|
|
public void Schedule_Validation(string schedule, bool valid)
|
|
{
|
|
Assert.Equal(valid, TaskSchedule.Validate(schedule).IsSuccess);
|
|
}
|
|
|
|
[Fact]
|
|
public void TaskSet_RoundTripsThroughTheJsonContext()
|
|
{
|
|
var set = new TaskSet
|
|
{
|
|
Version = 7,
|
|
Tasks = [new TaskSpec { Id = new TaskId("tsk_1"), Kind = "udp_echo", Params = new Dictionary<string, JsonElement> { ["rate_hz"] = Json("10") } }],
|
|
Peers = [new PeerSpec { ProbeId = new ProbeId("prb_1"), PublicKey = "AAAA" }],
|
|
};
|
|
string text = JsonSerializer.Serialize(set, MoShiroJsonContext.Default.TaskSet);
|
|
TaskSet back = JsonSerializer.Deserialize(text, MoShiroJsonContext.Default.TaskSet)!;
|
|
|
|
Assert.Contains("\"id\":\"tsk_1\"", text);
|
|
Assert.Equal(7, back.Version);
|
|
Assert.Equal("tsk_1", back.Tasks[0].Id.Value);
|
|
Assert.Equal(10, back.Tasks[0].Params["rate_hz"].GetInt64());
|
|
Assert.Equal("prb_1", back.Peers[0].ProbeId.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void AgentEnrollMeta_ToleratesUnknownFields_AndKeepsDefaults()
|
|
{
|
|
AgentEnrollMeta meta = JsonSerializer.Deserialize("{\"kinds\":[\"udp_echo\"],\"future\":1}", MoShiroJsonContext.Default.AgentEnrollMeta)!;
|
|
Assert.Equal(7443, meta.ProbeTcpPort);
|
|
Assert.Equal(["udp_echo"], meta.Kinds);
|
|
}
|
|
|
|
[Fact]
|
|
public void ErrorCodes_AreSnakeCase()
|
|
{
|
|
foreach (System.Reflection.FieldInfo field in typeof(MoShiroErrors).GetFields())
|
|
{
|
|
string value = ((ErrorCode)field.GetValue(null)!).Value;
|
|
Assert.Matches("^[a-z]+(_[a-z]+)*$", value);
|
|
}
|
|
}
|
|
}
|