using ScadaLink.Commons.Types.Flattening; using ScadaLink.TemplateEngine.Flattening; namespace ScadaLink.TemplateEngine.Tests.Flattening; public class RevisionHashServiceTests { private readonly RevisionHashService _sut = new(); [Fact] public void ComputeHash_SameContent_SameHash() { var config1 = CreateConfig("Instance1", "25.0"); var config2 = CreateConfig("Instance1", "25.0"); var hash1 = _sut.ComputeHash(config1); var hash2 = _sut.ComputeHash(config2); Assert.Equal(hash1, hash2); } [Fact] public void ComputeHash_DifferentContent_DifferentHash() { var config1 = CreateConfig("Instance1", "25.0"); var config2 = CreateConfig("Instance1", "50.0"); var hash1 = _sut.ComputeHash(config1); var hash2 = _sut.ComputeHash(config2); Assert.NotEqual(hash1, hash2); } [Fact] public void ComputeHash_StartsWithSha256Prefix() { var config = CreateConfig("Instance1", "25.0"); var hash = _sut.ComputeHash(config); Assert.StartsWith("sha256:", hash); } [Fact] public void ComputeHash_DeterministicAcrossRuns() { // Different GeneratedAtUtc should NOT affect the hash (volatile field excluded) var config1 = new FlattenedConfiguration { InstanceUniqueName = "Instance1", TemplateId = 1, SiteId = 1, Attributes = [new ResolvedAttribute { CanonicalName = "A", Value = "1", DataType = "Int32" }], GeneratedAtUtc = new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero) }; var config2 = new FlattenedConfiguration { InstanceUniqueName = "Instance1", TemplateId = 1, SiteId = 1, Attributes = [new ResolvedAttribute { CanonicalName = "A", Value = "1", DataType = "Int32" }], GeneratedAtUtc = new DateTimeOffset(2026, 6, 15, 12, 0, 0, TimeSpan.Zero) }; Assert.Equal(_sut.ComputeHash(config1), _sut.ComputeHash(config2)); } [Fact] public void ComputeHash_NullConfig_ThrowsArgumentNull() { Assert.Throws(() => _sut.ComputeHash(null!)); } [Fact] public void ComputeHash_AttributeOrder_DoesNotAffectHash() { var config1 = new FlattenedConfiguration { InstanceUniqueName = "Instance1", TemplateId = 1, SiteId = 1, Attributes = [ new ResolvedAttribute { CanonicalName = "A", Value = "1", DataType = "Int32" }, new ResolvedAttribute { CanonicalName = "B", Value = "2", DataType = "Int32" } ] }; var config2 = new FlattenedConfiguration { InstanceUniqueName = "Instance1", TemplateId = 1, SiteId = 1, Attributes = [ new ResolvedAttribute { CanonicalName = "B", Value = "2", DataType = "Int32" }, new ResolvedAttribute { CanonicalName = "A", Value = "1", DataType = "Int32" } ] }; Assert.Equal(_sut.ComputeHash(config1), _sut.ComputeHash(config2)); } private static FlattenedConfiguration CreateConfig(string instanceName, string tempValue) { return new FlattenedConfiguration { InstanceUniqueName = instanceName, TemplateId = 1, SiteId = 1, Attributes = [ new ResolvedAttribute { CanonicalName = "Temperature", Value = tempValue, DataType = "Double" } ] }; } }