using Fleet.Core; using Microsoft.Extensions.Time.Testing; using MoShiro.Protocol; namespace MoShiro.Measurement.Tests; public sealed class RestartUpdaterTests { private readonly List _restarts = []; private RestartUpdater Create(string tag = "dev-aaa", string commit = "aaa", long builtAt = 1_000) => new("agent", _restarts.Add, TimeProvider.System, NullNodeLog.Instance, tag, commit, builtAt); private static UpdateOfferParams Offer(string tag, long seq, string component = "agent", string commit = "bbb") => new() { Manifest = $"{{\"component\":\"{component}\",\"tag\":\"{tag}\",\"version\":\"0.4.0\",\"commit\":\"{commit}\",\"digest\":\"sha256:{new string('a', 64)}\",\"seq\":{seq}}}", Signature = "ignored" }; [Fact] public void ANewerRelease_IsAvailable_AndOnlyARequestRestartsTheNode() { RestartUpdater updater = Create(); updater.Offer(Offer("dev-bbb", 2_000)); UpdateStatus status = updater.Status(); Assert.Equal("available", status.State); Assert.Equal("dev-bbb", status.Available!.Tag); Assert.Equal(2_000, status.Available.Seq); Assert.Empty(_restarts); Assert.True(updater.RequestApply()); Assert.Equal(["update"], _restarts); Assert.Equal("applying", updater.Status().State); } [Fact] public void TheReleaseThatIsAlreadyRunning_IsUpToDate_ByTagOrByCommit() { RestartUpdater updater = Create(); updater.Offer(Offer("dev-aaa", 2_000)); Assert.Equal("up_to_date", updater.Status().State); updater.Offer(Offer("dev-other-tag", 2_000, commit: "aaa")); Assert.Equal("up_to_date", updater.Status().State); Assert.False(updater.RequestApply()); Assert.Empty(_restarts); } [Fact] public void AnOfferOlderThanThisBuild_OrForAnotherComponent_IsNotAnUpdate() { RestartUpdater updater = Create(builtAt: 5_000); updater.Offer(Offer("dev-bbb", 2_000)); Assert.Equal("up_to_date", updater.Status().State); updater.Offer(Offer("dev-ccc", 9_000, component: "probe")); Assert.Equal("up_to_date", updater.Status().State); Assert.False(updater.RequestApply()); } [Fact] public void GarbageOffers_AreIgnored() { RestartUpdater updater = Create(); updater.Offer(new UpdateOfferParams { Manifest = "not json", Signature = "x" }); updater.Offer(new UpdateOfferParams { Manifest = new string('x', 5000), Signature = "x" }); updater.Offer(new UpdateOfferParams { Manifest = "{\"component\":\"agent\"}", Signature = "x" }); Assert.Equal("up_to_date", updater.Status().State); Assert.False(updater.RequestApply()); } [Fact] public async Task TheAutoPolicy_RestartsByItself_ButNotBeforeTheMinimumUptime() { var time = new FakeTimeProvider(DateTimeOffset.FromUnixTimeSeconds(2_000_000_000)); var updater = new RestartUpdater("agent", _restarts.Add, time, NullNodeLog.Instance, "dev-aaa", "aaa", 1_000, auto: true); updater.Offer(Offer("dev-bbb", 2_000)); updater.Offer(Offer("dev-bbb", 2_000)); // 重连时面板会再发一次:不能排两个 Assert.Empty(_restarts); time.Advance(TimeSpan.FromMinutes(9)); await Task.Delay(50); Assert.Empty(_restarts); time.Advance(TimeSpan.FromMinutes(2)); await WaitAsync(() => _restarts.Count == 1); Assert.Equal("applying", updater.Status().State); await Task.Delay(50); Assert.Single(_restarts); } [Fact] public async Task TheManualPolicy_NeverRestartsByItself() { var time = new FakeTimeProvider(DateTimeOffset.FromUnixTimeSeconds(2_000_000_000)); var updater = new RestartUpdater("agent", _restarts.Add, time, NullNodeLog.Instance, "dev-aaa", "aaa", 1_000, auto: false); updater.Offer(Offer("dev-bbb", 2_000)); time.Advance(TimeSpan.FromHours(1)); await Task.Delay(50); Assert.Empty(_restarts); Assert.Equal("available", updater.Status().State); } private static async Task WaitAsync(Func condition) { for (int i = 0; i < 100 && !condition(); i++) { await Task.Delay(20); } Assert.True(condition()); } [Fact] public void WithoutTheEnvironmentVariable_ThereIsNoUpdater() { Assert.Null(Environment.GetEnvironmentVariable(RestartUpdater.EnvironmentVariable)); Assert.Null(RestartUpdater.FromEnvironment("agent", _restarts.Add, TimeProvider.System, NullNodeLog.Instance)); } }