docs(tests): fix CommentChecker findings across tests/ — 13,392 → 0 (repo-wide scan clean)

Comment/XML-doc changes only:
- add summaries to ~10k test methods, fixtures, fakes and helpers
  (+ <param> on theories, <returns> on async Task methods,
  <inheritdoc/> on interface implementations the checker can resolve)
- strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N,
  #NN, Component-0NN) from comments, keeping the explanatory text;
  reword false positives in comments only
- real docs on fakes implementing BCL/library interfaces
  (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime)
- reword the last 5 src/ false positives (CWE-1236, failures-1,
  year-0001, M365)

Only non-comment edits: four one-line nested classes split across lines
so members can carry docs. Build 0 errors / 0 warnings.
This commit is contained in:
Joseph Doherty
2026-10-07 15:05:49 -04:00
parent 0ffd4bdbea
commit 513fa1aa16
998 changed files with 17896 additions and 2395 deletions
@@ -35,6 +35,7 @@ public class ConnectionBindingOverrideTests
[id] = new("OPC-Server1", "OpcUa", 1) { Id = id, PrimaryConfiguration = "opc.tcp://localhost:4840" }
};
/// <summary>Verifies that a set override replaces the template's data source reference.</summary>
[Fact]
public void Override_replaces_template_DataSourceReference_when_set()
{
@@ -58,6 +59,7 @@ public class ConnectionBindingOverrideTests
Assert.Equal("ns=2;s=Pump1.Speed", attr.DataSourceReference);
}
/// <summary>Verifies that a null override falls back to the template default.</summary>
[Fact]
public void Null_override_falls_back_to_template_default()
{
@@ -81,6 +83,7 @@ public class ConnectionBindingOverrideTests
Assert.Equal("TemplateDefault", attr.DataSourceReference);
}
/// <summary>Verifies that changing an override changes the revision hash.</summary>
[Fact]
public void Override_change_changes_revision_hash()
{
@@ -15,6 +15,7 @@ namespace ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests.Flattening;
/// </summary>
public class DefaultDataSourceReferenceTests
{
/// <summary>Verifies that the revision hash ignores the default data source reference.</summary>
[Fact]
public void RevisionHash_IgnoresDefaultDataSourceReference()
{
@@ -32,6 +33,7 @@ public class DefaultDataSourceReferenceTests
Assert.Equal(svc.ComputeHash(without), svc.ComputeHash(with));
}
/// <summary>Verifies that a resolved attribute's default data source reference is null by default.</summary>
[Fact]
public void ResolvedAttribute_DefaultDataSourceReference_IsNullByDefault()
{
@@ -39,6 +41,7 @@ public class DefaultDataSourceReferenceTests
Assert.Null(a.DefaultDataSourceReference);
}
/// <summary>Verifies that flattening populates the default data source reference for both bound and unbound data-sourced attributes.</summary>
[Fact]
public void Flatten_PopulatesDefault_ForBoundAndUnboundDataSourcedAttributes()
{
@@ -5,6 +5,8 @@ namespace ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests.Flattening;
public class DeploymentPackageTests
{
/// <summary>Verifies that a deployment package round-trips through JSON serialization.</summary>
/// <returns>The result.</returns>
[Fact]
public void DeploymentPackage_JsonSerializable()
{
@@ -67,6 +69,8 @@ public class DeploymentPackageTests
Assert.Equal("Temperature", deserialized.Configuration.Attributes[0].CanonicalName);
}
/// <summary>Verifies that a deployment package carrying a configuration diff round-trips through JSON serialization.</summary>
/// <returns>The result.</returns>
[Fact]
public void DeploymentPackage_WithDiff_Serializable()
{
@@ -105,6 +109,8 @@ public class DeploymentPackageTests
Assert.Equal("sha256:old", deserialized.PreviousRevisionHash);
}
/// <summary>Verifies that a new flattened configuration has empty default values.</summary>
/// <returns>The result.</returns>
[Fact]
public void FlattenedConfiguration_DefaultValues()
{
@@ -7,6 +7,7 @@ public class DiffServiceTests
{
private readonly DiffService _sut = new();
/// <summary>Verifies that a null old configuration reports everything as added.</summary>
[Fact]
public void ComputeDiff_NullOldConfig_AllAdded()
{
@@ -37,6 +38,7 @@ public class DiffServiceTests
Assert.Single(diff.ScriptChanges);
}
/// <summary>Verifies that identical configurations produce no changes.</summary>
[Fact]
public void ComputeDiff_IdenticalConfigs_NoChanges()
{
@@ -54,6 +56,7 @@ public class DiffServiceTests
Assert.Empty(diff.AttributeChanges);
}
/// <summary>Verifies that a removed attribute is detected as removed.</summary>
[Fact]
public void ComputeDiff_AttributeRemoved_DetectedAsRemoved()
{
@@ -80,6 +83,7 @@ public class DiffServiceTests
Assert.Equal("Removed", diff.AttributeChanges[0].CanonicalName);
}
/// <summary>Verifies that a changed attribute is detected as changed.</summary>
[Fact]
public void ComputeDiff_AttributeChanged_DetectedAsChanged()
{
@@ -103,6 +107,7 @@ public class DiffServiceTests
Assert.Equal("50", diff.AttributeChanges[0].NewValue?.Value);
}
/// <summary>Verifies that the revision hashes are included in the diff.</summary>
[Fact]
public void ComputeDiff_RevisionHashes_Included()
{
@@ -113,6 +118,7 @@ public class DiffServiceTests
Assert.Equal("sha256:new", diff.NewRevisionHash);
}
/// <summary>Verifies that a script code change is detected.</summary>
[Fact]
public void ComputeDiff_ScriptCodeChange_Detected()
{
@@ -134,10 +140,11 @@ public class DiffServiceTests
Assert.Equal(DiffChangeType.Changed, diff.ScriptChanges[0].ChangeType);
}
/// <summary>Verifies that an attribute description change is detected as changed.</summary>
[Fact]
public void ComputeDiff_AttributeDescriptionChange_DetectedAsChanged()
{
// TemplateEngine-017: AttributesEqual must compare Description.
// AttributesEqual must compare Description.
var oldConfig = new FlattenedConfiguration
{
InstanceUniqueName = "Instance1",
@@ -162,10 +169,11 @@ public class DiffServiceTests
Assert.Equal(DiffChangeType.Changed, diff.AttributeChanges[0].ChangeType);
}
/// <summary>Verifies that an attribute element data type change is detected as changed.</summary>
[Fact]
public void ComputeDiff_AttributeElementDataTypeChange_DetectedAsChanged()
{
// #290: ElementDataType is part of the revision hash, so AttributesEqual
// ElementDataType is part of the revision hash, so AttributesEqual
// must also compare it — a List element-type change (Int32 → Double) with
// identical JSON-encoded values must surface as a Changed attribute,
// keeping the structured diff in parity with the staleness hash.
@@ -193,10 +201,11 @@ public class DiffServiceTests
Assert.Equal(DiffChangeType.Changed, diff.AttributeChanges[0].ChangeType);
}
/// <summary>Verifies that an alarm description change is detected as changed.</summary>
[Fact]
public void ComputeDiff_AlarmDescriptionChange_DetectedAsChanged()
{
// TemplateEngine-017: AlarmsEqual must compare Description.
// AlarmsEqual must compare Description.
var oldConfig = new FlattenedConfiguration
{
InstanceUniqueName = "Instance1",
@@ -221,10 +230,11 @@ public class DiffServiceTests
Assert.Equal(DiffChangeType.Changed, diff.AlarmChanges[0].ChangeType);
}
/// <summary>Verifies that a native alarm source reference change is detected as changed.</summary>
[Fact]
public void ComputeDiff_NativeAlarmSourceReferenceChange_DetectedAsChanged()
{
// PLAN-05 Task 12: native alarm source edits must surface in the diff so
// Native alarm source edits must surface in the diff so
// the Deployments diff view is not blind to native-alarm binding changes.
var oldConfig = new FlattenedConfiguration
{
@@ -251,6 +261,7 @@ public class DiffServiceTests
Assert.Equal("PumpAlarms", diff.NativeAlarmSourceChanges[0].CanonicalName);
}
/// <summary>Verifies that an added native alarm source is detected as added.</summary>
[Fact]
public void ComputeDiff_NativeAlarmSourceAdded_DetectedAsAdded()
{
@@ -271,6 +282,7 @@ public class DiffServiceTests
Assert.Equal(DiffChangeType.Added, diff.NativeAlarmSourceChanges[0].ChangeType);
}
/// <summary>Verifies that a removed native alarm source is detected as removed.</summary>
[Fact]
public void ComputeDiff_NativeAlarmSourceRemoved_DetectedAsRemoved()
{
@@ -291,6 +303,7 @@ public class DiffServiceTests
Assert.Equal(DiffChangeType.Removed, diff.NativeAlarmSourceChanges[0].ChangeType);
}
/// <summary>Verifies that a native alarm source lock change is detected as changed.</summary>
[Fact]
public void ComputeDiff_NativeAlarmSourceLockChange_DetectedAsChanged()
{
@@ -317,6 +330,7 @@ public class DiffServiceTests
Assert.Equal(DiffChangeType.Changed, diff.NativeAlarmSourceChanges[0].ChangeType);
}
/// <summary>Verifies that an unchanged native alarm source produces no diff.</summary>
[Fact]
public void ComputeDiff_NativeAlarmSourceUnchanged_NoDiff()
{
@@ -329,12 +343,12 @@ public class DiffServiceTests
Assert.Empty(diff.NativeAlarmSourceChanges);
}
/// <summary>Verifies that identical connection configurations compare as equal.</summary>
[Fact]
public void ConnectionsEqual_IdenticalConfigs_ReturnsTrue()
{
// TemplateEngine-017: ConnectionsEqual is the comparator callers use
// to detect connection-endpoint drift (the diff-view extension that
// surfaces this in the UI is tracked under TemplateEngine-018).
// ConnectionsEqual is the comparator callers use
// to detect connection-endpoint drift.
var a = new ConnectionConfig
{
Protocol = "OpcUa",
@@ -347,10 +361,11 @@ public class DiffServiceTests
Assert.True(DiffService.ConnectionsEqual(a, b));
}
/// <summary>Verifies that an endpoint edit makes the connection configurations compare as unequal.</summary>
[Fact]
public void ConnectionsEqual_EndpointEdit_ReturnsFalse()
{
// TemplateEngine-017: primary endpoint JSON edit must surface as a
// A primary endpoint JSON edit must surface as a
// change. Without this, deployment redeploys ship a different
// ConnectionConfig with no visible drift signal.
var a = new ConnectionConfig
@@ -364,6 +379,7 @@ public class DiffServiceTests
Assert.False(DiffService.ConnectionsEqual(a, b));
}
/// <summary>Verifies that a backup configuration edit makes the connection configurations compare as unequal.</summary>
[Fact]
public void ConnectionsEqual_BackupConfigurationEdit_ReturnsFalse()
{
@@ -373,6 +389,7 @@ public class DiffServiceTests
Assert.False(DiffService.ConnectionsEqual(a, b));
}
/// <summary>Verifies that a failover retry count edit makes the connection configurations compare as unequal.</summary>
[Fact]
public void ConnectionsEqual_FailoverRetryCountEdit_ReturnsFalse()
{
@@ -382,6 +399,7 @@ public class DiffServiceTests
Assert.False(DiffService.ConnectionsEqual(a, b));
}
/// <summary>Verifies that a protocol edit makes the connection configurations compare as unequal.</summary>
[Fact]
public void ConnectionsEqual_ProtocolEdit_ReturnsFalse()
{
@@ -391,14 +409,15 @@ public class DiffServiceTests
Assert.False(DiffService.ConnectionsEqual(a, b));
}
// ── TemplateEngine-018: ComputeConnectionsDiff produces Added/Removed/Changed entries ──
// ── ComputeConnectionsDiff produces Added/Removed/Changed entries ──
/// <summary>Verifies that a newly added binding is reported as added in the connections diff.</summary>
[Fact]
public void ComputeConnectionsDiff_NewBindingAdded_ReportedAsAdded()
{
// First-time binding (or instance gains its first data-sourced
// attribute) — old config has no Connections map, new config does.
// The pre-018 diff shape silently dropped this so operators saw
// The earlier diff shape silently dropped this so operators saw
// "no changes" when the deployment package was structurally larger.
var oldConfig = new FlattenedConfiguration { InstanceUniqueName = "Instance1" };
var newConfig = new FlattenedConfiguration
@@ -425,6 +444,7 @@ public class DiffServiceTests
Assert.Equal("OpcUa", diff[0].NewValue!.Protocol);
}
/// <summary>Verifies that a null old configuration reports all connections as added.</summary>
[Fact]
public void ComputeConnectionsDiff_NullOldConfig_AllReportedAsAdded()
{
@@ -455,6 +475,7 @@ public class DiffServiceTests
Assert.Equal("OpcUa", diff[0].NewValue!.Protocol);
}
/// <summary>Verifies that a cleared binding is reported as removed in the connections diff.</summary>
[Fact]
public void ComputeConnectionsDiff_BindingCleared_ReportedAsRemoved()
{
@@ -479,6 +500,7 @@ public class DiffServiceTests
Assert.Null(diff[0].NewValue);
}
/// <summary>Verifies that an endpoint edit is reported as changed in the connections diff.</summary>
[Fact]
public void ComputeConnectionsDiff_EndpointEdit_ReportedAsChanged()
{
@@ -521,6 +543,7 @@ public class DiffServiceTests
Assert.Contains("host-b", diff[0].NewValue!.ConfigurationJson);
}
/// <summary>Verifies that identical connections produce no diff entries.</summary>
[Fact]
public void ComputeConnectionsDiff_IdenticalConnections_NoEntries()
{
@@ -539,10 +562,11 @@ public class DiffServiceTests
Assert.Empty(diff);
}
// ── TemplateEngine-018: ComputeDiff wires ComputeConnectionsDiff into the
// ── ComputeDiff wires ComputeConnectionsDiff into the
// public ConfigurationDiff.ConnectionChanges slot so standalone connection
// protocol/endpoint/failover drift surfaces in the deployment diff (#10). ──
// protocol/endpoint/failover drift surfaces in the deployment diff. ──
/// <summary>Verifies that protocol, endpoint and failover changes populate the connection changes of the diff.</summary>
[Fact]
public void ComputeDiff_ConnectionProtocolAndEndpointAndFailoverChange_PopulatesConnectionChanges()
{
@@ -591,6 +615,7 @@ public class DiffServiceTests
Assert.Equal(5, entry.NewValue!.FailoverRetryCount);
}
/// <summary>Verifies that a diff that differs only in its connections reports changes.</summary>
[Fact]
public void ComputeDiff_OnlyConnectionDiffers_HasChangesIsTrue()
{
@@ -12,6 +12,7 @@ public class FlatteningServiceMergeTests
{
// ── MergeHiLoConfig ────────────────────────────────────────────────────
/// <summary>Verifies that merging prefers derived keys while inherited keys that are not overridden survive.</summary>
[Fact]
public void MergeHiLoConfig_DerivedKeysWin_InheritedKeysSurvive()
{
@@ -29,6 +30,7 @@ public class FlatteningServiceMergeTests
Assert.Equal(100, doc.RootElement.GetProperty("hiHi").GetDouble()); // inherited
}
/// <summary>Verifies that a derived config can override the attribute name.</summary>
[Fact]
public void MergeHiLoConfig_DerivedCanOverrideAttribute()
{
@@ -42,6 +44,7 @@ public class FlatteningServiceMergeTests
Assert.Equal(50, doc.RootElement.GetProperty("hi").GetDouble());
}
/// <summary>Verifies that merging returns the inherited config when the derived config is null.</summary>
[Fact]
public void MergeHiLoConfig_DerivedNull_ReturnsInherited()
{
@@ -52,6 +55,7 @@ public class FlatteningServiceMergeTests
Assert.Equal(inherited, result);
}
/// <summary>Verifies that merging returns the derived config when the inherited config is null.</summary>
[Fact]
public void MergeHiLoConfig_InheritedNull_ReturnsDerived()
{
@@ -62,12 +66,14 @@ public class FlatteningServiceMergeTests
Assert.Equal(derived, result);
}
/// <summary>Verifies that merging returns null when both configs are null.</summary>
[Fact]
public void MergeHiLoConfig_BothNull_ReturnsNull()
{
Assert.Null(FlatteningService.MergeHiLoConfig(null, null));
}
/// <summary>Verifies that merging falls back to the derived config when the inherited config is malformed.</summary>
[Fact]
public void MergeHiLoConfig_MalformedInherited_FallsBackToDerived()
{
@@ -79,6 +85,7 @@ public class FlatteningServiceMergeTests
Assert.Equal(derived, result);
}
/// <summary>Verifies that a new derived key is added while the remaining inherited keys are preserved.</summary>
[Fact]
public void MergeHiLoConfig_DerivedAddsNewKey_PreservesInheritedRest()
{
@@ -96,6 +103,7 @@ public class FlatteningServiceMergeTests
// ── DiffHiLoConfig ─────────────────────────────────────────────────────
/// <summary>Verifies that diffing identical configs returns null.</summary>
[Fact]
public void DiffHiLoConfig_NoChanges_ReturnsNull()
{
@@ -103,6 +111,7 @@ public class FlatteningServiceMergeTests
Assert.Null(FlatteningService.DiffHiLoConfig(both, both));
}
/// <summary>Verifies that diffing returns only the key that changed.</summary>
[Fact]
public void DiffHiLoConfig_ChangedKey_ReturnsOnlyChangedKey()
{
@@ -118,6 +127,7 @@ public class FlatteningServiceMergeTests
Assert.Equal(90, prop.Value.GetDouble());
}
/// <summary>Verifies that a newly added key appears in the diff.</summary>
[Fact]
public void DiffHiLoConfig_NewKey_AddedToDiff()
{
@@ -132,6 +142,7 @@ public class FlatteningServiceMergeTests
Assert.False(doc.RootElement.TryGetProperty("hi", out _));
}
/// <summary>Verifies that diffing against a null inherited config returns the edited config verbatim.</summary>
[Fact]
public void DiffHiLoConfig_NullInherited_ReturnsEditedVerbatim()
{
@@ -139,12 +150,14 @@ public class FlatteningServiceMergeTests
Assert.Equal(edited, FlatteningService.DiffHiLoConfig(null, edited));
}
/// <summary>Verifies that diffing a null edited config returns null.</summary>
[Fact]
public void DiffHiLoConfig_NullEdited_ReturnsNull()
{
Assert.Null(FlatteningService.DiffHiLoConfig(@"{""hi"":80}", null));
}
/// <summary>Verifies that diffing ignores differences in string escaping.</summary>
[Fact]
public void DiffHiLoConfig_IgnoresStringEscapeDifferences()
{
@@ -158,6 +171,7 @@ public class FlatteningServiceMergeTests
Assert.Null(diff); // no real change once values are decoded
}
/// <summary>Verifies that diffing ignores differences in numeric formatting.</summary>
[Fact]
public void DiffHiLoConfig_IgnoresNumericFormatDifferences()
{
@@ -167,6 +181,7 @@ public class FlatteningServiceMergeTests
Assert.Null(FlatteningService.DiffHiLoConfig(inherited, edited));
}
/// <summary>Verifies that a diff merged back onto the inherited config reproduces the edited config.</summary>
[Fact]
public void DiffHiLoConfig_RoundTripsThroughMerge()
{
@@ -227,6 +242,7 @@ public class FlatteningServiceMergeTests
return result.Value!;
}
/// <summary>Verifies that an instance alarm override merges its high/low setpoints into the flattened alarm.</summary>
[Fact]
public void Flatten_InstanceAlarmOverride_HiLo_MergesSetpoints()
{
@@ -250,6 +266,7 @@ public class FlatteningServiceMergeTests
Assert.Equal("Override", alarm.Source);
}
/// <summary>Verifies that an instance alarm override replaces the alarm priority.</summary>
[Fact]
public void Flatten_InstanceAlarmOverride_OverridesPriority()
{
@@ -268,6 +285,7 @@ public class FlatteningServiceMergeTests
Assert.Equal("Override", alarm.Source);
}
/// <summary>Verifies that an override targeting a locked alarm is silently ignored.</summary>
[Fact]
public void Flatten_InstanceAlarmOverride_LockedAlarm_OverrideSilentlyIgnored()
{
@@ -291,6 +309,7 @@ public class FlatteningServiceMergeTests
Assert.Equal(80, doc.RootElement.GetProperty("hi").GetDouble()); // not overridden
}
/// <summary>Verifies that an override naming an unknown alarm has no effect.</summary>
[Fact]
public void Flatten_InstanceAlarmOverride_UnknownName_DoesNothing()
{
@@ -312,6 +331,7 @@ public class FlatteningServiceMergeTests
Assert.NotEqual("Override", alarm.Source);
}
/// <summary>Verifies that an override on a binary-trigger alarm replaces the whole trigger config.</summary>
[Fact]
public void Flatten_InstanceAlarmOverride_BinaryTrigger_ReplacesWholeConfig()
{
@@ -19,6 +19,7 @@ public class FlatteningServiceTests
return t;
}
/// <summary>Verifies that flattening an empty template chain returns a failure.</summary>
[Fact]
public void Flatten_EmptyTemplateChain_ReturnsFailure()
{
@@ -34,6 +35,7 @@ public class FlatteningServiceTests
Assert.Contains("empty", result.Error, StringComparison.OrdinalIgnoreCase);
}
/// <summary>Verifies that flattening a single template resolves its attributes.</summary>
[Fact]
public void Flatten_SingleTemplate_ResolvesAttributes()
{
@@ -56,6 +58,7 @@ public class FlatteningServiceTests
Assert.Equal("Status", result.Value.Attributes[0].CanonicalName);
}
/// <summary>Verifies that in an inheritance chain the derived template's value overrides the base.</summary>
[Fact]
public void Flatten_InheritanceChain_DerivedOverridesBase()
{
@@ -83,6 +86,7 @@ public class FlatteningServiceTests
Assert.Equal("200.0", speed.Value); // Child's value wins
}
/// <summary>Verifies that a locked attribute is not overridden by a derived template.</summary>
[Fact]
public void Flatten_LockedAttribute_NotOverriddenByDerived()
{
@@ -105,6 +109,7 @@ public class FlatteningServiceTests
Assert.Equal("locked", locked.Value); // Base locked value preserved
}
/// <summary>Verifies that an instance override is applied to an unlocked attribute.</summary>
[Fact]
public void Flatten_InstanceOverride_AppliedToUnlockedAttribute()
{
@@ -127,6 +132,7 @@ public class FlatteningServiceTests
Assert.Equal("Override", attr.Source);
}
/// <summary>Verifies that an instance override is skipped for a locked attribute.</summary>
[Fact]
public void Flatten_InstanceOverride_SkippedForLockedAttribute()
{
@@ -148,6 +154,7 @@ public class FlatteningServiceTests
Assert.Equal("original", attr.Value); // Lock honored
}
/// <summary>Verifies that members of a composed module receive path-qualified names.</summary>
[Fact]
public void Flatten_ComposedModule_PathQualifiedNames()
{
@@ -185,6 +192,7 @@ public class FlatteningServiceTests
Assert.Contains(result.Value.Scripts, s => s.CanonicalName == "MainPump.Start");
}
/// <summary>Verifies that connection bindings are resolved correctly.</summary>
[Fact]
public void Flatten_ConnectionBindings_ResolvedCorrectly()
{
@@ -217,6 +225,7 @@ public class FlatteningServiceTests
Assert.Equal("OpcUa", attr.BoundDataConnectionProtocol);
}
/// <summary>Verifies that alarms are resolved from the template chain.</summary>
[Fact]
public void Flatten_Alarms_ResolvedFromChain()
{
@@ -242,6 +251,7 @@ public class FlatteningServiceTests
Assert.Equal("RangeViolation", result.Value.Alarms[0].TriggerType);
}
/// <summary>Verifies that instance metadata is set correctly on the flattened configuration.</summary>
[Fact]
public void Flatten_InstanceMetadata_SetCorrectly()
{
@@ -263,6 +273,7 @@ public class FlatteningServiceTests
Assert.Equal(3, result.Value.AreaId);
}
/// <summary>Verifies that an inherited attribute on a derived template keeps the base value.</summary>
[Fact]
public void Flatten_InheritedAttributeOnDerived_BaseValueWins()
{
@@ -290,6 +301,7 @@ public class FlatteningServiceTests
Assert.Equal("100.0", setPoint.Value);
}
/// <summary>Verifies that an attribute overridden on a derived template takes the derived value.</summary>
[Fact]
public void Flatten_OverriddenAttributeOnDerived_DerivedValueWins()
{
@@ -317,6 +329,7 @@ public class FlatteningServiceTests
Assert.Equal("150.0", setPoint.Value);
}
/// <summary>Verifies that overriding an attribute locked in the derived template fails.</summary>
[Fact]
public void Flatten_LockedInDerivedOverride_Fails()
{
@@ -349,6 +362,7 @@ public class FlatteningServiceTests
Assert.Contains("SetPoint", result.Error);
}
/// <summary>Verifies that an inherited script on a derived template keeps the base code.</summary>
[Fact]
public void Flatten_InheritedScriptOnDerived_BaseCodeWins()
{
@@ -371,8 +385,9 @@ public class FlatteningServiceTests
Assert.Equal("return base;", script.Code);
}
// ── M2.5 (#9): per-script execution timeout threads to ResolvedScript ───
// ── Per-script execution timeout threads to ResolvedScript ───
/// <summary>Verifies that a script's execution timeout carries through to the resolved script.</summary>
[Fact]
public void Flatten_SingleTemplate_ScriptExecutionTimeoutSecondsThreadsThrough()
{
@@ -395,6 +410,7 @@ public class FlatteningServiceTests
Assert.Null(dflt.ExecutionTimeoutSeconds);
}
/// <summary>Verifies that a derived script override's execution timeout follows the winning row.</summary>
[Fact]
public void Flatten_DerivedScriptOverride_ExecutionTimeoutFollowsWinningRow()
{
@@ -427,6 +443,7 @@ public class FlatteningServiceTests
Assert.Equal(3, script.ExecutionTimeoutSeconds);
}
/// <summary>Verifies that an inherited script's execution timeout follows the base row.</summary>
[Fact]
public void Flatten_InheritedScriptOnDerived_ExecutionTimeoutFollowsBaseRow()
{
@@ -459,8 +476,9 @@ public class FlatteningServiceTests
Assert.Equal(10, script.ExecutionTimeoutSeconds);
}
// ── TemplateEngine-002: per-slot alarm override ────────────────────────
// ── Per-slot alarm override ────────────────────────
/// <summary>Verifies that an inherited alarm on a derived template keeps the base value.</summary>
[Fact]
public void Flatten_InheritedAlarmOnDerived_BaseValueWins()
{
@@ -495,6 +513,7 @@ public class FlatteningServiceTests
Assert.Equal("{\"attributeName\":\"Temp\",\"high\":100}", alarm.TriggerConfiguration);
}
/// <summary>Verifies that the MES-reportable flag keeps the base value through a derived chain.</summary>
[Fact]
public void Flatten_MesReportable_DerivedChain_BaseValueWins()
{
@@ -532,6 +551,7 @@ public class FlatteningServiceTests
Assert.True(alarm.IsMesReportable);
}
/// <summary>Verifies that an alarm overridden on a derived template takes the derived value.</summary>
[Fact]
public void Flatten_OverriddenAlarmOnDerived_DerivedValueWins()
{
@@ -566,6 +586,7 @@ public class FlatteningServiceTests
Assert.Equal("{\"attributeName\":\"Temp\",\"high\":120}", alarm.TriggerConfiguration);
}
/// <summary>Verifies that overriding an alarm locked in the derived template fails.</summary>
[Fact]
public void Flatten_LockedInDerivedAlarmOverride_Fails()
{
@@ -602,6 +623,7 @@ public class FlatteningServiceTests
// ── MES reportable flag: template value + instance override ────────────
/// <summary>Verifies that the template's MES-reportable value is carried to the resolved alarm.</summary>
[Fact]
public void Flatten_MesReportable_TemplateValueCarried()
{
@@ -633,6 +655,7 @@ public class FlatteningServiceTests
Assert.False(result.Value.Alarms.First(a => a.CanonicalName == "LowTemp").IsMesReportable);
}
/// <summary>Verifies that an instance override of false wins for the MES-reportable flag.</summary>
[Fact]
public void Flatten_MesReportable_OverrideFalseWins()
{
@@ -664,6 +687,7 @@ public class FlatteningServiceTests
Assert.Equal("Override", alarm.Source);
}
/// <summary>Verifies that a null instance override inherits the template's MES-reportable value.</summary>
[Fact]
public void Flatten_MesReportable_OverrideNullInherits()
{
@@ -695,8 +719,9 @@ public class FlatteningServiceTests
Assert.True(alarm.IsMesReportable);
}
// ── TemplateEngine-001: deep composition nesting ───────────────────────
// ── Deep composition nesting ───────────────────────
/// <summary>Verifies that attributes, alarms and scripts all resolve through a three-level composition.</summary>
[Fact]
public void Flatten_ThreeLevelComposition_AttributesAlarmsScriptsAllResolved()
{
@@ -749,6 +774,7 @@ public class FlatteningServiceTests
s => s.CanonicalName == "MainPump.DriveMotor.FrontBearing.MonitorBearing");
}
/// <summary>Verifies that a nested composed alarm's trigger attribute is prefixed with the module path.</summary>
[Fact]
public void Flatten_NestedComposedAlarm_TriggerAttributePrefixed()
{
@@ -786,8 +812,9 @@ public class FlatteningServiceTests
Assert.Contains("MainPump.DriveMotor.FrontBearing.Vibration", alarm.TriggerConfiguration);
}
// ── TemplateEngine-004: alarm on-trigger script resolution ─────────────
// ── Alarm on-trigger script resolution ─────────────
/// <summary>Verifies that an alarm's on-trigger script resolves to its canonical name.</summary>
[Fact]
public void Flatten_AlarmOnTriggerScript_ResolvedToCanonicalName()
{
@@ -812,6 +839,7 @@ public class FlatteningServiceTests
Assert.Equal("HandleAlarm", alarm.OnTriggerScriptCanonicalName);
}
/// <summary>Verifies that a composed alarm's on-trigger script resolves with the module prefix.</summary>
[Fact]
public void Flatten_ComposedAlarmOnTriggerScript_ResolvedWithPrefix()
{
@@ -845,14 +873,15 @@ public class FlatteningServiceTests
Assert.Equal("MainPump.PumpAlarmHandler", alarm.OnTriggerScriptCanonicalName);
}
// ── TemplateEngine-016: composed-script ScriptScope.ParentPath ─────────
// ── Composed-script ScriptScope.ParentPath ─────────
/// <summary>Verifies that a nested composed script's scope carries the correct parent path.</summary>
[Fact]
public void Flatten_NestedComposedScript_ScopeCarriesCorrectParentPath()
{
// Station composes Pump (level 1); Pump composes Motor (level 2).
// The depth-1 script's parent is the root template (ParentPath "");
// the depth-2 script's parent is the Pump module (ParentPath "MainPump").
// The first-level script's parent is the root template (ParentPath "");
// the second-level script's parent is the Pump module (ParentPath "MainPump").
var motor = CreateTemplate(3, "Motor");
motor.Scripts.Add(new TemplateScript("MonitorMotor", "// m") { Id = 70 });
@@ -883,10 +912,11 @@ public class FlatteningServiceTests
var depth2 = result.Value.Scripts.First(s => s.CanonicalName == "MainPump.DriveMotor.MonitorMotor");
Assert.Equal("MainPump.DriveMotor", depth2.Scope.SelfPath);
// Parent module of a depth-2 script is the enclosing Pump module.
// Parent module of a second-level script is the enclosing Pump module.
Assert.Equal("MainPump", depth2.Scope.ParentPath);
}
/// <summary>Verifies that native alarm sources are resolved from the template.</summary>
[Fact]
public void Flatten_ResolvesNativeAlarmSources_FromTemplate()
{
@@ -903,6 +933,7 @@ public class FlatteningServiceTests
Assert.Equal("ns=2;s=P1", result.Value.NativeAlarmSources[0].SourceReference);
}
/// <summary>Verifies that an instance override replaces a native alarm source reference.</summary>
[Fact]
public void Flatten_InstanceOverride_ReplacesNativeAlarmSourceReference()
{
@@ -921,10 +952,11 @@ public class FlatteningServiceTests
Assert.Equal("Override", result.Value.NativeAlarmSources[0].Source);
}
/// <summary>Verifies that a locked native alarm source ignores an instance override.</summary>
[Fact]
public void Flatten_LockedNativeAlarmSource_IgnoresInstanceOverride()
{
// #05-T13: a native alarm source locked at the template level must not be
// A native alarm source locked at the template level must not be
// repointed by an instance override — the resolved source keeps the template
// values and its Source stays "Template" (mirrors attribute/alarm lock rules).
var template = CreateTemplate(1, "Base");
@@ -947,8 +979,9 @@ public class FlatteningServiceTests
Assert.True(src.IsLocked);
}
// ── MV-4: ElementDataType carried through flatten/override ────────────
// ── ElementDataType carried through flatten/override ────────────
/// <summary>Verifies that a list attribute's element data type is carried to the resolved attribute.</summary>
[Fact]
public void Flatten_ListAttribute_ElementDataTypeCarriedToResolvedAttribute()
{
@@ -976,6 +1009,7 @@ public class FlatteningServiceTests
Assert.Equal("String", attr.ElementDataType);
}
/// <summary>Verifies that a list attribute's element data type is preserved through an instance override.</summary>
[Fact]
public void Flatten_ListAttributeWithInstanceOverride_ElementDataTypePreservedThroughOverride()
{
@@ -1012,6 +1046,7 @@ public class FlatteningServiceTests
// ── Repeated composition: same template under multiple slots ──────────
/// <summary>Verifies that a template composed twice resolves its nested members under both slots.</summary>
[Fact]
public void Flatten_SameTemplateComposedTwice_ResolvesNestedMembersUnderBothSlots()
{
@@ -1076,6 +1111,7 @@ public class FlatteningServiceTests
Assert.Contains(result.Value.NativeAlarmSources, s => s.CanonicalName == "x.y2.z.Src");
}
/// <summary>Verifies that flattening a composition cycle still terminates.</summary>
[Fact]
public void Flatten_CompositionCycle_StillTerminates()
{
@@ -7,6 +7,7 @@ public class RevisionHashServiceTests
{
private readonly RevisionHashService _sut = new();
/// <summary>Verifies that identical content produces the same hash.</summary>
[Fact]
public void ComputeHash_SameContent_SameHash()
{
@@ -19,6 +20,7 @@ public class RevisionHashServiceTests
Assert.Equal(hash1, hash2);
}
/// <summary>Verifies that different content produces a different hash.</summary>
[Fact]
public void ComputeHash_DifferentContent_DifferentHash()
{
@@ -31,6 +33,7 @@ public class RevisionHashServiceTests
Assert.NotEqual(hash1, hash2);
}
/// <summary>Verifies that the computed hash starts with the sha256 prefix.</summary>
[Fact]
public void ComputeHash_StartsWithSha256Prefix()
{
@@ -40,6 +43,7 @@ public class RevisionHashServiceTests
Assert.StartsWith("sha256:", hash);
}
/// <summary>Verifies that the hash is deterministic across runs.</summary>
[Fact]
public void ComputeHash_DeterministicAcrossRuns()
{
@@ -64,12 +68,14 @@ public class RevisionHashServiceTests
Assert.Equal(_sut.ComputeHash(config1), _sut.ComputeHash(config2));
}
/// <summary>Verifies that a null configuration throws an argument-null exception.</summary>
[Fact]
public void ComputeHash_NullConfig_ThrowsArgumentNull()
{
Assert.Throws<ArgumentNullException>(() => _sut.ComputeHash(null!));
}
/// <summary>Verifies that attribute order does not affect the hash.</summary>
[Fact]
public void ComputeHash_AttributeOrder_DoesNotAffectHash()
{
@@ -99,10 +105,11 @@ public class RevisionHashServiceTests
Assert.Equal(_sut.ComputeHash(config1), _sut.ComputeHash(config2));
}
/// <summary>Verifies that the hashable records declare their properties in alphabetical order.</summary>
[Fact]
public void HashableRecords_PropertiesDeclaredAlphabetically()
{
// TemplateEngine-011: revision-hash determinism depends entirely on the
// Revision-hash determinism depends entirely on the
// private Hashable* records declaring their properties in alphabetical
// order (System.Text.Json emits properties in CLR declaration order and
// does not sort). This guards against a contributor silently changing
@@ -131,10 +138,11 @@ public class RevisionHashServiceTests
}
}
/// <summary>Verifies that editing an attribute description changes the hash.</summary>
[Fact]
public void ComputeHash_AttributeDescriptionEdit_ChangesHash()
{
// TemplateEngine-017: Description must be folded into the hash so that
// Description must be folded into the hash so that
// edits to authoring-time documentation (which still travels in the
// deployed payload) flow through the staleness indicator.
var baseAttr = new ResolvedAttribute
@@ -158,10 +166,11 @@ public class RevisionHashServiceTests
Assert.NotEqual(_sut.ComputeHash(configBefore), _sut.ComputeHash(configAfter));
}
/// <summary>Verifies that editing an alarm description changes the hash.</summary>
[Fact]
public void ComputeHash_AlarmDescriptionEdit_ChangesHash()
{
// TemplateEngine-017: same Description contract applies to alarms.
// Same Description contract applies to alarms.
var baseAlarm = new ResolvedAlarm
{
CanonicalName = "HighTemp",
@@ -182,10 +191,11 @@ public class RevisionHashServiceTests
Assert.NotEqual(_sut.ComputeHash(configBefore), _sut.ComputeHash(configAfter));
}
/// <summary>Verifies that editing a connection endpoint changes the hash.</summary>
[Fact]
public void ComputeHash_ConnectionEndpointEdit_ChangesHash()
{
// TemplateEngine-017: a Deployment user editing the primary endpoint
// A Deployment user editing the primary endpoint
// JSON of a data connection bound to an instance must produce a
// different revision hash. The connection's protocol, primary/backup
// configuration JSON, and failover retry count are all part of the
@@ -220,10 +230,11 @@ public class RevisionHashServiceTests
Assert.NotEqual(_sut.ComputeHash(configBefore), _sut.ComputeHash(configAfter));
}
/// <summary>Verifies that editing a connection protocol changes the hash.</summary>
[Fact]
public void ComputeHash_ConnectionProtocolEdit_ChangesHash()
{
// TemplateEngine-017: changing protocol must change the hash.
// Changing protocol must change the hash.
var connectionsBefore = new Dictionary<string, ConnectionConfig>
{
["plc1"] = new ConnectionConfig
@@ -250,10 +261,11 @@ public class RevisionHashServiceTests
Assert.NotEqual(_sut.ComputeHash(configBefore), _sut.ComputeHash(configAfter));
}
/// <summary>Verifies that connections with the same content produce the same hash.</summary>
[Fact]
public void ComputeHash_ConnectionsSameContent_SameHash()
{
// TemplateEngine-017: equal Connections maps must yield the same hash,
// Equal Connections maps must yield the same hash,
// regardless of dictionary iteration order (the SortedDictionary
// projection guards this).
var connections1 = new Dictionary<string, ConnectionConfig>
@@ -279,10 +291,11 @@ public class RevisionHashServiceTests
Assert.Equal(_sut.ComputeHash(config1), _sut.ComputeHash(config2));
}
/// <summary>Verifies that changing a list element data type changes the hash.</summary>
[Fact]
public void ComputeHash_ListElementDataTypeChange_ChangesHash()
{
// #290: a List attribute whose element type changes (Int32 → Double)
// A List attribute whose element type changes (Int32 → Double)
// while the JSON-encoded values stay identical MUST produce a different
// revision hash, so the deployment is flagged stale. ElementDataType is
// folded into the hashable projection precisely for this case.
@@ -307,10 +320,11 @@ public class RevisionHashServiceTests
Assert.NotEqual(_sut.ComputeHash(configBefore), _sut.ComputeHash(configAfter));
}
/// <summary>Verifies that an unchanged list element data type produces the same hash.</summary>
[Fact]
public void ComputeHash_SameListElementDataType_SameHash()
{
// #290: two List attributes identical in every field — including
// Two List attributes identical in every field — including
// ElementDataType — must hash the same.
var attr = new ResolvedAttribute
{
@@ -332,10 +346,11 @@ public class RevisionHashServiceTests
Assert.Equal(_sut.ComputeHash(config1), _sut.ComputeHash(config2));
}
/// <summary>Verifies that a scalar attribute hashes the same whether its element data type is null or unset.</summary>
[Fact]
public void ComputeHash_ScalarAttribute_NullVsUnsetElementDataType_SameHash()
{
// #290 no-op guard: a scalar attribute has a null ElementDataType, and
// No-op guard: a scalar attribute has a null ElementDataType, and
// the canonical serializer omits null properties
// (DefaultIgnoreCondition = WhenWritingNull). Explicitly setting
// ElementDataType = null must therefore hash identically to leaving it
@@ -367,10 +382,11 @@ public class RevisionHashServiceTests
Assert.Equal(_sut.ComputeHash(config1), _sut.ComputeHash(config2));
}
/// <summary>Verifies that changing a native alarm source changes the hash.</summary>
[Fact]
public void ComputeHash_NativeAlarmSourceChange_ChangesHash()
{
// TemplateEngine-011: native alarm source bindings are part of the
// Native alarm source bindings are part of the
// deployment package, so an edit to a source's binding (here the
// SourceReference) MUST change the revision hash and flag the instance
// stale. Before this migration native sources were absent from the hash
@@ -397,10 +413,11 @@ public class RevisionHashServiceTests
Assert.NotEqual(_sut.ComputeHash(configBefore), _sut.ComputeHash(configAfter));
}
/// <summary>Verifies that changing a native alarm source's locked flag changes the hash.</summary>
[Fact]
public void ComputeHash_NativeAlarmSourceIsLockedChange_ChangesHash()
{
// TemplateEngine-011: IsLocked participates in the hash too.
// IsLocked participates in the hash too.
var baseSource = new ResolvedNativeAlarmSource
{
CanonicalName = "Boiler.Alarms",
@@ -422,10 +439,11 @@ public class RevisionHashServiceTests
Assert.NotEqual(_sut.ComputeHash(configBefore), _sut.ComputeHash(configAfter));
}
/// <summary>Verifies that native alarm source order does not affect the hash.</summary>
[Fact]
public void ComputeHash_NativeAlarmSourceOrder_DoesNotAffectHash()
{
// TemplateEngine-011: sources are sorted by CanonicalName before hashing,
// Sources are sorted by CanonicalName before hashing,
// mirroring attributes/alarms/scripts — authoring order is irrelevant.
var a = new ResolvedNativeAlarmSource
{
@@ -448,10 +466,11 @@ public class RevisionHashServiceTests
Assert.Equal(_sut.ComputeHash(config1), _sut.ComputeHash(config2));
}
/// <summary>Verifies that a configuration with no native alarm sources keeps its baseline hash.</summary>
[Fact]
public void ComputeHash_NoNativeAlarmSources_HashUnchangedFromBaseline()
{
// TemplateEngine-011 migration surgicality: adding the (null-when-empty)
// Migration surgicality: adding the (null-when-empty)
// NativeAlarmSources property to the hashable projection must NOT change
// the hash of any native-source-free configuration. The literal below is
// the pre-migration hash of CreateConfig("Instance1", "25.0"); pinning it