Files
lmxopcua/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Deployments/DeploymentDriftProbeTests.cs
T

84 lines
3.0 KiB
C#

using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.Deployments;
using ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;
using ZB.MOM.WW.OtOpcUa.Configuration;
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Deployments;
/// <summary>
/// Pins the <see cref="DeploymentDriftProbe"/> watermark contract: the expensive
/// <c>ConfigComposer</c> recomposition runs only when the <c>ConfigEdit</c> audit watermark (or the
/// sealed revision hash) has moved, and it definitely still runs when it has.
/// </summary>
public sealed class DeploymentDriftProbeTests
{
private const string SealedHash = "sealed-hash-aaaa";
[Fact]
public async Task An_unchanged_watermark_reuses_the_cached_verdict()
{
var name = $"drift-{Guid.NewGuid():N}";
var factory = UnsTreeTestDb.Factory(name);
var compose = new CountingCompose(SealedHash);
var probe = new DeploymentDriftProbe(factory, compose.ComposeAsync);
var first = await probe.GetDriftAsync(SealedHash, CancellationToken.None);
var second = await probe.GetDriftAsync(SealedHash, CancellationToken.None);
// Nothing mutated between the calls, so the second call must be served from the cache.
compose.Count.ShouldBe(1);
first.ShouldBe(false);
second.ShouldBe(false);
}
[Fact]
public async Task A_new_config_edit_recomposes()
{
var name = $"drift-{Guid.NewGuid():N}";
var factory = UnsTreeTestDb.Factory(name);
var compose = new CountingCompose(SealedHash);
var probe = new DeploymentDriftProbe(factory, compose.ComposeAsync);
var before = await probe.GetDriftAsync(SealedHash, CancellationToken.None);
before.ShouldBe(false);
// An edit lands: the audit row moves the watermark, and the (faked) composer now reports a
// different revision — the probe must recompose and flip the verdict.
await using (var db = UnsTreeTestDb.CreateNamed(name))
{
db.ConfigEdits.Add(new ConfigEdit
{
EntityType = "Equipment",
EntityId = Guid.NewGuid(),
FieldsJson = "{}",
EditedBy = "test",
SourceNode = "node-1",
EditedAtUtc = DateTime.UtcNow,
});
await db.SaveChangesAsync();
}
compose.Result = "a-different-hash";
var after = await probe.GetDriftAsync(SealedHash, CancellationToken.None);
compose.Count.ShouldBe(2);
after.ShouldBe(true);
}
/// <summary>Stand-in for the <c>ConfigComposer</c> seam that counts how often it was invoked.</summary>
private sealed class CountingCompose(string result)
{
public string Result { get; set; } = result;
public int Count { get; private set; }
public Task<string> ComposeAsync(OtOpcUaConfigDbContext db)
{
Count++;
return Task.FromResult(Result);
}
}
}