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:
@@ -14,6 +14,7 @@ public class TemplateServiceTests
|
||||
private readonly Mock<IAuditService> _auditMock;
|
||||
private readonly TemplateService _service;
|
||||
|
||||
/// <summary>Initializes the test with mocked repositories and the service under test.</summary>
|
||||
public TemplateServiceTests()
|
||||
{
|
||||
_repoMock = new Mock<ITemplateEngineRepository>();
|
||||
@@ -23,9 +24,11 @@ public class TemplateServiceTests
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// WP-1: Template CRUD with Inheritance
|
||||
// Template CRUD with Inheritance
|
||||
// ========================================================================
|
||||
|
||||
/// <summary>Verifies that create template, success.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateTemplate_Success()
|
||||
{
|
||||
@@ -34,7 +37,7 @@ public class TemplateServiceTests
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.Equal("Pump", result.Value.Name);
|
||||
_repoMock.Verify(r => r.AddTemplateAsync(It.IsAny<Template>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
// TemplateEngine-020: SaveChangesAsync runs twice — once after the
|
||||
// SaveChangesAsync runs twice — once after the
|
||||
// AddTemplateAsync to populate the auto-generated key (so the audit
|
||||
// row can carry the real Id), and once after LogAsync to persist the
|
||||
// audit row itself.
|
||||
@@ -42,6 +45,8 @@ public class TemplateServiceTests
|
||||
_auditMock.Verify(a => a.LogAsync("admin", "Create", "Template", It.IsAny<string>(), "Pump", It.IsAny<object?>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that create template, empty name, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateTemplate_EmptyName_Fails()
|
||||
{
|
||||
@@ -50,6 +55,8 @@ public class TemplateServiceTests
|
||||
Assert.Contains("required", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that create template, with parent, success.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateTemplate_WithParent_Success()
|
||||
{
|
||||
@@ -66,6 +73,8 @@ public class TemplateServiceTests
|
||||
Assert.Equal(1, result.Value.ParentTemplateId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that create template, with parent, does not run dead collision query.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateTemplate_WithParent_DoesNotRunDeadCollisionQuery()
|
||||
{
|
||||
@@ -87,6 +96,8 @@ public class TemplateServiceTests
|
||||
_repoMock.Verify(r => r.GetAllTemplatesForAnalysisAsync(It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that create template, nonexistent parent, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateTemplate_NonexistentParent_Fails()
|
||||
{
|
||||
@@ -97,6 +108,8 @@ public class TemplateServiceTests
|
||||
Assert.Contains("not found", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that create template, with folder id, sets folder id.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateTemplate_WithFolderId_SetsFolderId()
|
||||
{
|
||||
@@ -109,6 +122,8 @@ public class TemplateServiceTests
|
||||
Assert.Equal(7, result.Value.FolderId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that delete template, success.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteTemplate_Success()
|
||||
{
|
||||
@@ -127,6 +142,8 @@ public class TemplateServiceTests
|
||||
_repoMock.Verify(r => r.DeleteTemplateAsync(1, It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that delete template, referenced by instances, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteTemplate_ReferencedByInstances_Fails()
|
||||
{
|
||||
@@ -145,6 +162,8 @@ public class TemplateServiceTests
|
||||
Assert.Contains("instance(s) reference it", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that delete template, has children, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteTemplate_HasChildren_Fails()
|
||||
{
|
||||
@@ -164,6 +183,8 @@ public class TemplateServiceTests
|
||||
Assert.Contains("child template(s) inherit from it", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that delete template, composed by other, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteTemplate_ComposedByOther_Fails()
|
||||
{
|
||||
@@ -185,10 +206,12 @@ public class TemplateServiceTests
|
||||
Assert.Contains("template(s) compose it", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that delete template, multiple constraints, reports all not just first.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteTemplate_MultipleConstraints_ReportsAllNotJustFirst()
|
||||
{
|
||||
// TemplateEngine-014: DeleteTemplateAsync delegates its constraint check
|
||||
// DeleteTemplateAsync delegates its constraint check
|
||||
// to the single TemplateDeletionService implementation, which accumulates
|
||||
// every blocking reason instead of returning on the first failing category.
|
||||
var template = new Template("Busy") { Id = 1 };
|
||||
@@ -222,9 +245,11 @@ public class TemplateServiceTests
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// WP-2: Attribute Definitions with Lock Flags
|
||||
// Attribute Definitions with Lock Flags
|
||||
// ========================================================================
|
||||
|
||||
/// <summary>Verifies that add attribute, success.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddAttribute_Success()
|
||||
{
|
||||
@@ -243,10 +268,12 @@ public class TemplateServiceTests
|
||||
Assert.Equal(1, result.Value.TemplateId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that add attribute, to derived template with inherited placeholders, succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddAttribute_ToDerivedTemplateWithInheritedPlaceholders_Succeeds()
|
||||
{
|
||||
// Follow-up #3 (end-to-end). Adding a member to a derived template used to
|
||||
// End-to-end: adding a member to a derived template used to
|
||||
// fail: CloneTemplateWithNewAttribute carries the child's IsInherited
|
||||
// placeholder rows into DetectCollisions, which counted each one twice (child
|
||||
// origin + the parent the inheritance walk re-adds) and reported a spurious
|
||||
@@ -276,6 +303,8 @@ public class TemplateServiceTests
|
||||
Assert.Equal(8, result.Value.TemplateId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that add attribute, duplicate name, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddAttribute_DuplicateName_Fails()
|
||||
{
|
||||
@@ -290,10 +319,12 @@ public class TemplateServiceTests
|
||||
Assert.Contains("already exists", result.Error);
|
||||
}
|
||||
|
||||
// ── #92: (DataType, ElementDataType, Value) validation hoisted into the
|
||||
// ── (DataType, ElementDataType, Value) validation hoisted into the
|
||||
// service so the Central UI's direct write path shares the same
|
||||
// server-side guard as ManagementActor.ValidateAttributeTypes. ──
|
||||
|
||||
/// <summary>Verifies that add attribute, invalid data type, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddAttribute_InvalidDataType_Fails()
|
||||
{
|
||||
@@ -312,6 +343,8 @@ public class TemplateServiceTests
|
||||
_repoMock.Verify(r => r.AddTemplateAttributeAsync(It.IsAny<TemplateAttribute>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that add attribute, list missing element type, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddAttribute_ListMissingElementType_Fails()
|
||||
{
|
||||
@@ -326,6 +359,8 @@ public class TemplateServiceTests
|
||||
_repoMock.Verify(r => r.AddTemplateAttributeAsync(It.IsAny<TemplateAttribute>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that add attribute, list invalid element type, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddAttribute_ListInvalidElementType_Fails()
|
||||
{
|
||||
@@ -341,6 +376,8 @@ public class TemplateServiceTests
|
||||
_repoMock.Verify(r => r.AddTemplateAttributeAsync(It.IsAny<TemplateAttribute>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that add attribute, scalar with element type, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddAttribute_ScalarWithElementType_Fails()
|
||||
{
|
||||
@@ -355,6 +392,8 @@ public class TemplateServiceTests
|
||||
_repoMock.Verify(r => r.AddTemplateAttributeAsync(It.IsAny<TemplateAttribute>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that add attribute, list value does not decode, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddAttribute_ListValueDoesNotDecode_Fails()
|
||||
{
|
||||
@@ -373,6 +412,8 @@ public class TemplateServiceTests
|
||||
_repoMock.Verify(r => r.AddTemplateAttributeAsync(It.IsAny<TemplateAttribute>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that add attribute, scalar value does not parse, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddAttribute_ScalarValueDoesNotParse_Fails()
|
||||
{
|
||||
@@ -396,6 +437,8 @@ public class TemplateServiceTests
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that add attribute, valid typed list, succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddAttribute_ValidTypedList_Succeeds()
|
||||
{
|
||||
@@ -418,6 +461,8 @@ public class TemplateServiceTests
|
||||
Assert.Equal(DataType.Int32, result.Value.ElementDataType);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that update attribute, invalid list value, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateAttribute_InvalidListValue_Fails()
|
||||
{
|
||||
@@ -444,6 +489,8 @@ public class TemplateServiceTests
|
||||
_repoMock.Verify(r => r.UpdateTemplateAttributeAsync(It.IsAny<TemplateAttribute>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that update attribute, valid typed list value, succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateAttribute_ValidTypedListValue_Succeeds()
|
||||
{
|
||||
@@ -466,9 +513,11 @@ public class TemplateServiceTests
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// WP-3: Alarm Definitions
|
||||
// Alarm Definitions
|
||||
// ========================================================================
|
||||
|
||||
/// <summary>Verifies that add alarm, success.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddAlarm_Success()
|
||||
{
|
||||
@@ -491,6 +540,8 @@ public class TemplateServiceTests
|
||||
Assert.Equal("HighTemp", result.Value.Name);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that add alarm, invalid priority, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddAlarm_InvalidPriority_Fails()
|
||||
{
|
||||
@@ -504,6 +555,8 @@ public class TemplateServiceTests
|
||||
Assert.Contains("priority", result.Error, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that update alarm, trigger type fixed, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateAlarm_TriggerTypeFixed_Fails()
|
||||
{
|
||||
@@ -530,6 +583,8 @@ public class TemplateServiceTests
|
||||
Assert.Contains("TriggerType", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that update alarm async, copies is mes reportable.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateAlarmAsync_CopiesIsMesReportable()
|
||||
{
|
||||
@@ -561,6 +616,8 @@ public class TemplateServiceTests
|
||||
It.Is<TemplateAlarm>(a => a.IsMesReportable), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that update alarm async, clears is mes reportable.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateAlarmAsync_ClearsIsMesReportable()
|
||||
{
|
||||
@@ -590,9 +647,11 @@ public class TemplateServiceTests
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// WP-4: Script Definitions
|
||||
// Script Definitions
|
||||
// ========================================================================
|
||||
|
||||
/// <summary>Verifies that add script, success.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddScript_Success()
|
||||
{
|
||||
@@ -610,6 +669,8 @@ public class TemplateServiceTests
|
||||
Assert.Equal("OnStart", result.Value.Name);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that update script, name fixed, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateScript_NameFixed_Fails()
|
||||
{
|
||||
@@ -625,10 +686,12 @@ public class TemplateServiceTests
|
||||
Assert.Contains("Name", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that update script, ui edit path, preserves existing execution timeout seconds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateScript_UiEditPath_PreservesExistingExecutionTimeoutSeconds()
|
||||
{
|
||||
// #54: the UI now has an ExecutionTimeoutSeconds input, but it still loads
|
||||
// The UI now has an ExecutionTimeoutSeconds input, but it still loads
|
||||
// (round-trips) the existing value into that input on edit, so leaving it
|
||||
// untouched re-sends the same value rather than null. This test proves the
|
||||
// service preserves the timeout end-to-end when the loaded value is re-sent
|
||||
@@ -655,10 +718,12 @@ public class TemplateServiceTests
|
||||
Assert.Equal(30, result.Value.ExecutionTimeoutSeconds);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that update script, explicit null timeout, clears execution timeout seconds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateScript_ExplicitNullTimeout_ClearsExecutionTimeoutSeconds()
|
||||
{
|
||||
// M2.5 (#9): a deliberate clear (e.g. via Transport or CLI setting null
|
||||
// A deliberate clear (e.g. via Transport or CLI setting null
|
||||
// explicitly) must still work — the service must not guard against null.
|
||||
var existing = new TemplateScript("OnStart", "return true;")
|
||||
{
|
||||
@@ -683,13 +748,15 @@ public class TemplateServiceTests
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// WP-5: Shared Script CRUD (see SharedScriptServiceTests)
|
||||
// Shared Script CRUD (see SharedScriptServiceTests)
|
||||
// ========================================================================
|
||||
|
||||
// ========================================================================
|
||||
// WP-6: Composition with Recursive Nesting
|
||||
// Composition with Recursive Nesting
|
||||
// ========================================================================
|
||||
|
||||
/// <summary>Verifies that add composition, success, derives template.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddComposition_Success_DerivesTemplate()
|
||||
{
|
||||
@@ -726,6 +793,8 @@ public class TemplateServiceTests
|
||||
_repoMock.Verify(r => r.AddTemplateCompositionAsync(It.IsAny<TemplateComposition>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that add composition, cascades child compositions.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddComposition_CascadesChildCompositions()
|
||||
{
|
||||
@@ -771,6 +840,8 @@ public class TemplateServiceTests
|
||||
Assert.Equal("Probe1", capturedCompositions[1].InstanceName);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that add composition, base is derived, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddComposition_BaseIsDerived_Fails()
|
||||
{
|
||||
@@ -786,6 +857,8 @@ public class TemplateServiceTests
|
||||
Assert.Contains("derived template", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that add composition, derived contained name, need not be globally unique.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddComposition_DerivedContainedName_NeedNotBeGloballyUnique()
|
||||
{
|
||||
@@ -816,6 +889,8 @@ public class TemplateServiceTests
|
||||
Assert.Equal("myModule", captured[0].Name);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that rename composition, renames slot and derived template.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RenameComposition_RenamesSlotAndDerivedTemplate()
|
||||
{
|
||||
@@ -837,6 +912,8 @@ public class TemplateServiceTests
|
||||
Assert.Equal("NewSlot", derived.Name);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that rename composition, renames only the slots own derived template.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RenameComposition_RenamesOnlyTheSlotsOwnDerivedTemplate()
|
||||
{
|
||||
@@ -867,6 +944,8 @@ public class TemplateServiceTests
|
||||
It.Is<Template>(t => t.Id == 78), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that rename composition, duplicate name, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RenameComposition_DuplicateName_Fails()
|
||||
{
|
||||
@@ -885,6 +964,8 @@ public class TemplateServiceTests
|
||||
Assert.Contains("already exists", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that delete composition, cascades nested derived templates.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteComposition_CascadesNestedDerivedTemplates()
|
||||
{
|
||||
@@ -912,6 +993,8 @@ public class TemplateServiceTests
|
||||
_repoMock.Verify(r => r.DeleteTemplateAsync(78, It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that delete composition, cascades derived template.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteComposition_CascadesDerivedTemplate()
|
||||
{
|
||||
@@ -928,6 +1011,8 @@ public class TemplateServiceTests
|
||||
_repoMock.Verify(r => r.DeleteTemplateAsync(77, It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that delete composition, legacy direct base, does not cascade.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteComposition_LegacyDirectBase_DoesNotCascade()
|
||||
{
|
||||
@@ -944,6 +1029,8 @@ public class TemplateServiceTests
|
||||
_repoMock.Verify(r => r.DeleteTemplateAsync(5, It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that delete template, derived template, direct delete blocked.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteTemplate_DerivedTemplate_DirectDeleteBlocked()
|
||||
{
|
||||
@@ -956,6 +1043,8 @@ public class TemplateServiceTests
|
||||
Assert.Contains("derived template", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that update attribute, locked in derived base, rejects on derived.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateAttribute_LockedInDerivedBase_RejectsOnDerived()
|
||||
{
|
||||
@@ -979,6 +1068,8 @@ public class TemplateServiceTests
|
||||
Assert.Contains("locked by base template 'Sensor'", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that update script, locked in derived base, rejects on derived.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateScript_LockedInDerivedBase_RejectsOnDerived()
|
||||
{
|
||||
@@ -1002,6 +1093,8 @@ public class TemplateServiceTests
|
||||
Assert.Contains("locked by base template 'Sensor'", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that add composition, copies alarms as inherited.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddComposition_CopiesAlarmsAsInherited()
|
||||
{
|
||||
@@ -1043,6 +1136,8 @@ public class TemplateServiceTests
|
||||
Assert.Equal("Too hot", copied.Description);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that update alarm, locked in derived base, rejects on derived.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateAlarm_LockedInDerivedBase_RejectsOnDerived()
|
||||
{
|
||||
@@ -1084,6 +1179,8 @@ public class TemplateServiceTests
|
||||
Assert.Contains("locked by base template 'Sensor'", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that update alarm, derived override, persists is inherited false.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateAlarm_DerivedOverride_PersistsIsInheritedFalse()
|
||||
{
|
||||
@@ -1125,6 +1222,8 @@ public class TemplateServiceTests
|
||||
Assert.Equal(99, result.Value.PriorityLevel);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that update attribute, derived override, persists is inherited false.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateAttribute_DerivedOverride_PersistsIsInheritedFalse()
|
||||
{
|
||||
@@ -1149,6 +1248,8 @@ public class TemplateServiceTests
|
||||
Assert.Equal("99", result.Value.Value);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that delete template, base with derivatives, blocked with slot names.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteTemplate_BaseWithDerivatives_BlockedWithSlotNames()
|
||||
{
|
||||
@@ -1172,6 +1273,8 @@ public class TemplateServiceTests
|
||||
Assert.Contains("'Pump' (as 'TempSensor')", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that add composition, duplicate instance name, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddComposition_DuplicateInstanceName_Fails()
|
||||
{
|
||||
@@ -1188,6 +1291,8 @@ public class TemplateServiceTests
|
||||
Assert.Contains("already exists", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that add composition, self composition, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddComposition_SelfComposition_Fails()
|
||||
{
|
||||
@@ -1205,9 +1310,11 @@ public class TemplateServiceTests
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// WP-9: Locking Rules
|
||||
// Locking Rules
|
||||
// ========================================================================
|
||||
|
||||
/// <summary>Verifies that update attribute, locked member, cannot unlock.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateAttribute_LockedMember_CannotUnlock()
|
||||
{
|
||||
@@ -1229,6 +1336,8 @@ public class TemplateServiceTests
|
||||
Assert.Contains("cannot be unlocked", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that update attribute, lock unlocked member, succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateAttribute_LockUnlockedMember_Succeeds()
|
||||
{
|
||||
@@ -1250,6 +1359,8 @@ public class TemplateServiceTests
|
||||
Assert.True(result.Value.IsLocked);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that update attribute, unlocked attribute, data type change rejected.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateAttribute_UnlockedAttribute_DataTypeChangeRejected()
|
||||
{
|
||||
@@ -1274,6 +1385,8 @@ public class TemplateServiceTests
|
||||
Assert.Equal(DataType.Float, existing.DataType);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that update attribute, unlocked attribute, data source reference change rejected.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateAttribute_UnlockedAttribute_DataSourceReferenceChangeRejected()
|
||||
{
|
||||
@@ -1298,6 +1411,8 @@ public class TemplateServiceTests
|
||||
Assert.Equal("/Motor/Temp", existing.DataSourceReference);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that update attribute, parent locked, cannot override.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateAttribute_ParentLocked_CannotOverride()
|
||||
{
|
||||
@@ -1334,9 +1449,11 @@ public class TemplateServiceTests
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// WP-10: Inheritance Override Scope — Cannot remove parent members
|
||||
// Inheritance Override Scope — Cannot remove parent members
|
||||
// ========================================================================
|
||||
|
||||
/// <summary>Verifies that delete attribute, inherited from parent, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteAttribute_InheritedFromParent_Fails()
|
||||
{
|
||||
@@ -1366,6 +1483,8 @@ public class TemplateServiceTests
|
||||
Assert.Contains("inherited from parent", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that delete attribute, own member, succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteAttribute_OwnMember_Succeeds()
|
||||
{
|
||||
@@ -1385,9 +1504,11 @@ public class TemplateServiceTests
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// WP-13: Graph Acyclicity
|
||||
// Graph Acyclicity
|
||||
// ========================================================================
|
||||
|
||||
/// <summary>Verifies that update template, change parent, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateTemplate_ChangeParent_Fails()
|
||||
{
|
||||
@@ -1403,10 +1524,12 @@ public class TemplateServiceTests
|
||||
_repoMock.Verify(r => r.UpdateTemplateAsync(It.IsAny<Template>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that update template, omitted parent and description, leaves unchanged.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateTemplate_OmittedParentAndDescription_LeavesUnchanged()
|
||||
{
|
||||
// Followup #5: a null parent/description means "leave unchanged" (an omitted
|
||||
// A null parent/description means "leave unchanged" (an omitted
|
||||
// CLI/API field), not an attempt to clear. Renaming without re-passing the
|
||||
// description must not wipe it, and must not trip the parent-immutability guard.
|
||||
var child = new Template("Child") { Id = 2, ParentTemplateId = 1, Description = "keep me" };
|
||||
@@ -1425,6 +1548,8 @@ public class TemplateServiceTests
|
||||
_repoMock.Verify(r => r.UpdateTemplateAsync(It.IsAny<Template>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that update template, empty description, clears it.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateTemplate_EmptyDescription_ClearsIt()
|
||||
{
|
||||
@@ -1443,6 +1568,8 @@ public class TemplateServiceTests
|
||||
_repoMock.Verify(r => r.UpdateTemplateAsync(It.IsAny<Template>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that update template, same parent, succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateTemplate_SameParent_Succeeds()
|
||||
{
|
||||
@@ -1463,6 +1590,8 @@ public class TemplateServiceTests
|
||||
_repoMock.Verify(r => r.UpdateTemplateAsync(It.IsAny<Template>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that add composition, circular chain, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddComposition_CircularChain_Fails()
|
||||
{
|
||||
@@ -1490,6 +1619,8 @@ public class TemplateServiceTests
|
||||
// Move template between folders
|
||||
// ========================================================================
|
||||
|
||||
/// <summary>Verifies that move template, to folder, returns success.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MoveTemplate_ToFolder_ReturnsSuccess()
|
||||
{
|
||||
@@ -1508,6 +1639,8 @@ public class TemplateServiceTests
|
||||
Assert.Equal(7, result.Value.FolderId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that move template, to root, returns success.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MoveTemplate_ToRoot_ReturnsSuccess()
|
||||
{
|
||||
@@ -1524,6 +1657,8 @@ public class TemplateServiceTests
|
||||
Assert.Null(result.Value.FolderId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that move template, target folder missing, returns failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MoveTemplate_TargetFolderMissing_ReturnsFailure()
|
||||
{
|
||||
@@ -1537,10 +1672,12 @@ public class TemplateServiceTests
|
||||
Assert.Contains("not found", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that move template, name collision at destination, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MoveTemplate_NameCollisionAtDestination_Fails()
|
||||
{
|
||||
// TemplateEngine-021: moving "Pump" into a folder that already contains a
|
||||
// Moving "Pump" into a folder that already contains a
|
||||
// template named "Pump" (case-insensitive) must be rejected before the
|
||||
// FolderId is persisted. Mirrors TemplateFolderService.MoveFolderAsync
|
||||
// sibling-name uniqueness, and pins the design rule that naming
|
||||
@@ -1564,10 +1701,12 @@ public class TemplateServiceTests
|
||||
_repoMock.Verify(r => r.UpdateTemplateAsync(It.IsAny<Template>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that move template, no collision at destination, succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MoveTemplate_NoCollisionAtDestination_Succeeds()
|
||||
{
|
||||
// TemplateEngine-021 companion: a sibling with the same name in a
|
||||
// Companion case: a sibling with the same name in a
|
||||
// *different* folder is not a collision. The move must succeed.
|
||||
var moving = new Template("Pump") { Id = 1, FolderId = null };
|
||||
var unrelated = new Template("Pump") { Id = 2, FolderId = 8 }; // different folder
|
||||
@@ -1585,10 +1724,12 @@ public class TemplateServiceTests
|
||||
Assert.Equal(7, result.Value.FolderId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that update attribute, locked in derived downgrade, on base, rejected.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateAttribute_LockedInDerivedDowngrade_OnBase_Rejected()
|
||||
{
|
||||
// TemplateEngine-022: LockedInDerived is a one-way ratchet on base
|
||||
// LockedInDerived is a one-way ratchet on base
|
||||
// templates. Once true, it cannot be cleared — otherwise existing
|
||||
// derived overrides that were previously blocked would become
|
||||
// retroactively legal.
|
||||
@@ -1616,20 +1757,22 @@ public class TemplateServiceTests
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// TemplateEngine-020: Create* audit rows must carry the real entity Id,
|
||||
// Create* audit rows must carry the real entity Id,
|
||||
// not the literal "0" that EF Core leaves on the entity until
|
||||
// SaveChangesAsync populates the auto-generated key.
|
||||
// ========================================================================
|
||||
|
||||
/// <summary>Verifies that create template, audit row carries real template id not literal zero.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateTemplate_AuditRowCarriesRealTemplateIdNotLiteralZero()
|
||||
{
|
||||
// Simulate EF Core's identity-key population: the repository's
|
||||
// AddTemplateAsync stamps Id during SaveChangesAsync. The pre-020
|
||||
// AddTemplateAsync stamps Id during SaveChangesAsync. The previous
|
||||
// order (Add → LogAsync → Save) queued the audit row before the Id
|
||||
// existed, so every Create audit entry persisted with EntityId = "0"
|
||||
// and the audit trail lost its link back to the created template.
|
||||
// Post-020: save first, then log with the real Id, then save the
|
||||
// Current order: save first, then log with the real Id, then save the
|
||||
// audit row.
|
||||
Template? added = null;
|
||||
_repoMock.Setup(r => r.AddTemplateAsync(It.IsAny<Template>(), It.IsAny<CancellationToken>()))
|
||||
@@ -1663,6 +1806,8 @@ public class TemplateServiceTests
|
||||
Assert.NotEqual("0", auditedEntityId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that add attribute, audit row carries real attribute id not literal zero.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddAttribute_AuditRowCarriesRealAttributeIdNotLiteralZero()
|
||||
{
|
||||
@@ -1705,6 +1850,8 @@ public class TemplateServiceTests
|
||||
Assert.NotEqual("0", auditedEntityId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that add alarm, audit row carries real alarm id not literal zero.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddAlarm_AuditRowCarriesRealAlarmIdNotLiteralZero()
|
||||
{
|
||||
@@ -1752,6 +1899,8 @@ public class TemplateServiceTests
|
||||
Assert.NotEqual("0", auditedEntityId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that add script, audit row carries real script id not literal zero.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddScript_AuditRowCarriesRealScriptIdNotLiteralZero()
|
||||
{
|
||||
@@ -1795,17 +1944,19 @@ public class TemplateServiceTests
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Follow-up #1/#2: inherited-member propagation & resync
|
||||
// Inherited-member propagation & resync
|
||||
// ========================================================================
|
||||
|
||||
private static Template BaseTemplate(int id, string name) => new(name) { Id = id };
|
||||
private static Template DerivedTemplate(int id, string name, int parentId) =>
|
||||
new(name) { Id = id, ParentTemplateId = parentId, IsDerived = true };
|
||||
|
||||
/// <summary>Verifies that resync, materializes missing inherited attribute.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Resync_MaterializesMissingInheritedAttribute()
|
||||
{
|
||||
// The reported #1/#2 shape: a base member added after the child was derived
|
||||
// The reported shape: a base member added after the child was derived
|
||||
// never reached the child's stored rows. Resync materializes it.
|
||||
var baseT = BaseTemplate(7, "Base");
|
||||
baseT.Attributes.Add(new TemplateAttribute("A") { Id = 70, TemplateId = 7, DataType = DataType.Float, Value = "1" });
|
||||
@@ -1836,6 +1987,8 @@ public class TemplateServiceTests
|
||||
Assert.Equal(DataType.String, b.DataType);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that resync, removes orphaned inherited row.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Resync_RemovesOrphanedInheritedRow()
|
||||
{
|
||||
@@ -1863,6 +2016,8 @@ public class TemplateServiceTests
|
||||
Assert.Equal(81, Assert.Single(deleted));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that resync, updates drifted inherited row.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Resync_UpdatesDriftedInheritedRow()
|
||||
{
|
||||
@@ -1890,6 +2045,8 @@ public class TemplateServiceTests
|
||||
Assert.Equal("NEW", childA.Value); // re-synced in place to the live base value
|
||||
}
|
||||
|
||||
/// <summary>Verifies that resync, leaves authored overrides untouched.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Resync_LeavesAuthoredOverridesUntouched()
|
||||
{
|
||||
@@ -1915,6 +2072,8 @@ public class TemplateServiceTests
|
||||
_repoMock.Verify(r => r.DeleteTemplateAttributeAsync(It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that resync, on base, cascades to all derived children.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Resync_OnBase_CascadesToAllDerivedChildren()
|
||||
{
|
||||
@@ -1946,6 +2105,8 @@ public class TemplateServiceTests
|
||||
Assert.Contains(added, a => a.TemplateId == 9 && a.Name == "B" && a.IsInherited);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that resync, materializes missing inherited script and native source.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Resync_MaterializesMissingInheritedScriptAndNativeSource()
|
||||
{
|
||||
@@ -1984,6 +2145,8 @@ public class TemplateServiceTests
|
||||
Assert.Equal(8, src.TemplateId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that reconcile descendants, propagates new base member to child.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReconcileDescendants_PropagatesNewBaseMemberToChild()
|
||||
{
|
||||
@@ -2012,11 +2175,13 @@ public class TemplateServiceTests
|
||||
Assert.True(c.IsInherited);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that add attribute, propagates new member to derived child.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddAttribute_PropagatesNewMemberToDerivedChild()
|
||||
{
|
||||
// End-to-end: adding a member to a base auto-propagates an inherited
|
||||
// placeholder to its derived children (#1/#2).
|
||||
// placeholder to its derived children.
|
||||
var baseT = BaseTemplate(7, "Base");
|
||||
baseT.Attributes.Add(new TemplateAttribute("A") { Id = 70, TemplateId = 7, DataType = DataType.Float, Value = "1" });
|
||||
var child = DerivedTemplate(8, "Child", 7);
|
||||
@@ -2047,6 +2212,8 @@ public class TemplateServiceTests
|
||||
Assert.Contains(added, a => a.TemplateId == 8 && a.Name == "C" && a.IsInherited); // child placeholder
|
||||
}
|
||||
|
||||
/// <summary>Verifies that create derived template async, copies is mes reportable onto placeholder.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateDerivedTemplateAsync_CopiesIsMesReportableOntoPlaceholder()
|
||||
{
|
||||
@@ -2088,6 +2255,8 @@ public class TemplateServiceTests
|
||||
Assert.True(copied.IsMesReportable);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that reconcile descendants, propagates is mes reportable change.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReconcileDescendants_PropagatesIsMesReportableChange()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user