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;
///
/// Pins the watermark contract: the expensive
/// ConfigComposer recomposition runs only when the ConfigEdit audit watermark (or the
/// sealed revision hash) has moved, and it definitely still runs when it has.
///
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);
}
/// Stand-in for the ConfigComposer seam that counts how often it was invoked.
private sealed class CountingCompose(string result)
{
public string Result { get; set; } = result;
public int Count { get; private set; }
public Task ComposeAsync(OtOpcUaConfigDbContext db)
{
Count++;
return Task.FromResult(Result);
}
}
}