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:
@@ -12,11 +12,14 @@ public class AreaServiceTests
|
||||
private readonly Mock<IAuditService> _auditMock = new();
|
||||
private readonly AreaService _sut;
|
||||
|
||||
/// <summary>Initializes the test class with a mocked repository and the service under test.</summary>
|
||||
public AreaServiceTests()
|
||||
{
|
||||
_sut = new AreaService(_repoMock.Object, _auditMock.Object);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that creating an area with valid input succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateArea_ValidInput_ReturnsSuccess()
|
||||
{
|
||||
@@ -32,6 +35,8 @@ public class AreaServiceTests
|
||||
Assert.Equal(1, result.Value.SiteId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that creating an area with a duplicate name fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateArea_DuplicateName_ReturnsFailure()
|
||||
{
|
||||
@@ -47,6 +52,8 @@ public class AreaServiceTests
|
||||
Assert.Contains("already exists", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that creating an area with a parent validates that the parent belongs to the site.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateArea_WithParent_ValidatesParentBelongsToSite()
|
||||
{
|
||||
@@ -59,6 +66,8 @@ public class AreaServiceTests
|
||||
Assert.Contains("does not belong", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deleting an area with assigned instances fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteArea_WithAssignedInstances_ReturnsFailure()
|
||||
{
|
||||
@@ -78,6 +87,8 @@ public class AreaServiceTests
|
||||
Assert.Contains("instance(s) are assigned", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deleting an area with child areas fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteArea_WithChildAreas_ReturnsFailure()
|
||||
{
|
||||
@@ -98,6 +109,8 @@ public class AreaServiceTests
|
||||
Assert.Contains("child areas", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deleting an unconstrained area succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteArea_NoConstraints_Success()
|
||||
{
|
||||
@@ -116,6 +129,8 @@ public class AreaServiceTests
|
||||
_repoMock.Verify(r => r.DeleteAreaAsync(1, It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that moving an area under another area succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MoveArea_ToOtherArea_Succeeds()
|
||||
{
|
||||
@@ -141,6 +156,8 @@ public class AreaServiceTests
|
||||
_auditMock.Verify(a => a.LogAsync("admin", "Move", "Area", "3", "Leaf", It.IsAny<object>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that moving an area to the site root succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MoveArea_ToSiteRoot_Succeeds()
|
||||
{
|
||||
@@ -161,6 +178,8 @@ public class AreaServiceTests
|
||||
Assert.Null(result.Value.ParentAreaId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that moving an area under itself fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MoveArea_ToSelf_Fails()
|
||||
{
|
||||
@@ -173,6 +192,8 @@ public class AreaServiceTests
|
||||
Assert.Contains("its own parent", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that moving an area under one of its descendants fails with a cycle error.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MoveArea_ToDescendant_FailsWithCycleError()
|
||||
{
|
||||
@@ -195,6 +216,8 @@ public class AreaServiceTests
|
||||
Assert.Contains("descendants", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that moving an area to a different site fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MoveArea_DifferentSite_Fails()
|
||||
{
|
||||
@@ -209,6 +232,8 @@ public class AreaServiceTests
|
||||
Assert.Contains("same site", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that moving an area fails when its name collides at the new parent.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MoveArea_NameCollidesAtNewParent_Fails()
|
||||
{
|
||||
@@ -232,6 +257,8 @@ public class AreaServiceTests
|
||||
Assert.Contains("already exists", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that moving an area to its current parent succeeds as a no-op.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MoveArea_SameParent_NoOpSuccess()
|
||||
{
|
||||
@@ -253,6 +280,8 @@ public class AreaServiceTests
|
||||
_auditMock.Verify(a => a.LogAsync(It.IsAny<string>(), "Move", It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<object>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that moving an area fails when the target parent does not exist.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MoveArea_TargetParentMissing_Fails()
|
||||
{
|
||||
@@ -267,6 +296,8 @@ public class AreaServiceTests
|
||||
Assert.Contains("not found", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deleting an area is blocked when its descendants have assigned instances.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteArea_InstancesInDescendants_Blocked()
|
||||
{
|
||||
|
||||
@@ -15,11 +15,14 @@ public class InstanceServiceTests
|
||||
private readonly Mock<IAuditService> _auditMock = new();
|
||||
private readonly InstanceService _sut;
|
||||
|
||||
/// <summary>Initializes the test with a mocked repository and audit service and the service under test.</summary>
|
||||
public InstanceServiceTests()
|
||||
{
|
||||
_sut = new InstanceService(_repoMock.Object, _auditMock.Object);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that creating an instance with valid input returns success.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateInstance_ValidInput_ReturnsSuccess()
|
||||
{
|
||||
@@ -38,6 +41,8 @@ public class InstanceServiceTests
|
||||
_repoMock.Verify(r => r.AddInstanceAsync(It.IsAny<Instance>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that creating an instance with a duplicate name returns a failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateInstance_DuplicateName_ReturnsFailure()
|
||||
{
|
||||
@@ -52,6 +57,8 @@ public class InstanceServiceTests
|
||||
Assert.Contains("already exists", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that creating an instance from a missing template returns a failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateInstance_MissingTemplate_ReturnsFailure()
|
||||
{
|
||||
@@ -64,6 +71,8 @@ public class InstanceServiceTests
|
||||
Assert.Contains("not found", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that overriding a locked attribute returns a failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SetAttributeOverride_LockedAttribute_ReturnsFailure()
|
||||
{
|
||||
@@ -82,6 +91,8 @@ public class InstanceServiceTests
|
||||
Assert.Contains("locked", result.Error, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that overriding a non-existent attribute returns a failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SetAttributeOverride_NonExistentAttribute_ReturnsFailure()
|
||||
{
|
||||
@@ -97,6 +108,8 @@ public class InstanceServiceTests
|
||||
Assert.Contains("does not exist", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that overriding an unlocked attribute returns success.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SetAttributeOverride_UnlockedAttribute_ReturnsSuccess()
|
||||
{
|
||||
@@ -120,7 +133,7 @@ public class InstanceServiceTests
|
||||
Assert.Equal("99", result.Value.OverrideValue);
|
||||
}
|
||||
|
||||
// --- TemplateEngine-008 regression: SetAlarmOverrideAsync validation ---
|
||||
// --- SetAlarmOverrideAsync validation regression ---
|
||||
|
||||
private static Template TemplateWithAlarms(int id, params TemplateAlarm[] alarms)
|
||||
{
|
||||
@@ -133,6 +146,8 @@ public class InstanceServiceTests
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Verifies that overriding a non-existent alarm returns a failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SetAlarmOverride_NonExistentAlarm_ReturnsFailure()
|
||||
{
|
||||
@@ -153,6 +168,8 @@ public class InstanceServiceTests
|
||||
It.IsAny<InstanceAlarmOverride>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that overriding a locked alarm on a composed template returns a failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SetAlarmOverride_ComposedLockedAlarm_ReturnsFailure()
|
||||
{
|
||||
@@ -177,6 +194,8 @@ public class InstanceServiceTests
|
||||
It.IsAny<InstanceAlarmOverride>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that overriding an unlocked alarm on a composed template returns success.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SetAlarmOverride_ComposedUnlockedAlarm_ReturnsSuccess()
|
||||
{
|
||||
@@ -202,6 +221,8 @@ public class InstanceServiceTests
|
||||
It.IsAny<InstanceAlarmOverride>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that overriding a locked alarm declared directly on the template returns a failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SetAlarmOverride_DirectLockedAlarm_ReturnsFailure()
|
||||
{
|
||||
@@ -220,6 +241,8 @@ public class InstanceServiceTests
|
||||
Assert.Contains("locked", result.Error, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that setting an alarm override stores the MES-reportable override.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SetAlarmOverrideAsync_StoresIsMesReportableOverride()
|
||||
{
|
||||
@@ -250,6 +273,8 @@ public class InstanceServiceTests
|
||||
Assert.True(result.Value.IsMesReportableOverride);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a null MES-reportable override clears the existing override.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SetAlarmOverrideAsync_NullIsMesReportableOverride_ClearsExistingOverride()
|
||||
{
|
||||
@@ -284,6 +309,8 @@ public class InstanceServiceTests
|
||||
It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that setting an alarm override updates the MES-reportable override on an existing override.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SetAlarmOverrideAsync_UpdatesIsMesReportableOverrideOnExisting()
|
||||
{
|
||||
@@ -315,6 +342,8 @@ public class InstanceServiceTests
|
||||
Assert.Equal(3, existingOverride.PriorityLevelOverride);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that enabling an existing instance sets its state to enabled.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Enable_ExistingInstance_SetsEnabled()
|
||||
{
|
||||
@@ -330,6 +359,8 @@ public class InstanceServiceTests
|
||||
Assert.Equal(InstanceState.Enabled, result.Value.State);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that disabling an existing instance sets its state to disabled.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Disable_ExistingInstance_SetsDisabled()
|
||||
{
|
||||
@@ -345,6 +376,8 @@ public class InstanceServiceTests
|
||||
Assert.Equal(InstanceState.Disabled, result.Value.State);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a bulk connection-binding assignment succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SetConnectionBindings_BulkAssignment_Success()
|
||||
{
|
||||
@@ -364,8 +397,10 @@ public class InstanceServiceTests
|
||||
_repoMock.Verify(r => r.AddInstanceConnectionBindingAsync(It.IsAny<InstanceConnectionBinding>(), It.IsAny<CancellationToken>()), Times.Exactly(2));
|
||||
}
|
||||
|
||||
// --- NJ-2: ElementDataType stamping on SetAttributeOverrideAsync ---
|
||||
// --- ElementDataType stamping on SetAttributeOverrideAsync ---
|
||||
|
||||
/// <summary>Verifies that creating an override for a list attribute stamps the element data type.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SetAttributeOverride_CreatePath_ListAttribute_StampsElementDataType()
|
||||
{
|
||||
@@ -398,6 +433,8 @@ public class InstanceServiceTests
|
||||
Assert.Equal(DataType.String, captured.ElementDataType);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that updating an override for a list attribute stamps the element data type.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SetAttributeOverride_UpdatePath_ListAttribute_StampsElementDataType()
|
||||
{
|
||||
@@ -432,6 +469,8 @@ public class InstanceServiceTests
|
||||
Assert.Equal(DataType.String, captured.ElementDataType);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that creating an override for a scalar attribute leaves the element data type null.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SetAttributeOverride_CreatePath_ScalarAttribute_ElementDataTypeIsNull()
|
||||
{
|
||||
@@ -464,6 +503,8 @@ public class InstanceServiceTests
|
||||
Assert.Null(captured.ElementDataType);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that assigning an instance to an area in a different site returns a failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AssignToArea_AreaInDifferentSite_ReturnsFailure()
|
||||
{
|
||||
|
||||
@@ -13,11 +13,14 @@ public class SiteServiceTests
|
||||
private readonly Mock<IAuditService> _auditMock = new();
|
||||
private readonly SiteService _sut;
|
||||
|
||||
/// <summary>Initializes the test class with a mocked repository and the service under test.</summary>
|
||||
public SiteServiceTests()
|
||||
{
|
||||
_sut = new SiteService(_repoMock.Object, _auditMock.Object);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that creating a site with valid input succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateSite_ValidInput_ReturnsSuccess()
|
||||
{
|
||||
@@ -33,6 +36,8 @@ public class SiteServiceTests
|
||||
Assert.Equal("SITE-001", result.Value.SiteIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that creating a site with a duplicate identifier fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateSite_DuplicateIdentifier_ReturnsFailure()
|
||||
{
|
||||
@@ -45,6 +50,8 @@ public class SiteServiceTests
|
||||
Assert.Contains("already exists", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that creating a site with an empty name fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateSite_EmptyName_ReturnsFailure()
|
||||
{
|
||||
@@ -54,6 +61,8 @@ public class SiteServiceTests
|
||||
Assert.Contains("required", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deleting a site that has instances fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteSite_WithInstances_ReturnsFailure()
|
||||
{
|
||||
@@ -72,6 +81,8 @@ public class SiteServiceTests
|
||||
Assert.Contains("2 instance(s)", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deleting a site without instances succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteSite_NoInstances_Success()
|
||||
{
|
||||
@@ -87,6 +98,8 @@ public class SiteServiceTests
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that creating a data connection with valid input succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateDataConnection_ValidInput_Success()
|
||||
{
|
||||
@@ -101,6 +114,8 @@ public class SiteServiceTests
|
||||
Assert.Equal(1, result.Value.SiteId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that updating a site with valid input succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UpdateSite_ValidInput_Success()
|
||||
{
|
||||
|
||||
+18
-1
@@ -11,11 +11,14 @@ public class TemplateDeletionServiceTests
|
||||
private readonly Mock<ITemplateEngineRepository> _repoMock = new();
|
||||
private readonly TemplateDeletionService _sut;
|
||||
|
||||
/// <summary>Initializes the test class with a mocked repository and the deletion service under test.</summary>
|
||||
public TemplateDeletionServiceTests()
|
||||
{
|
||||
_sut = new TemplateDeletionService(_repoMock.Object);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a template with no references can be deleted.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CanDeleteTemplate_NoReferences_ReturnsSuccess()
|
||||
{
|
||||
@@ -33,6 +36,8 @@ public class TemplateDeletionServiceTests
|
||||
Assert.True(result.IsSuccess);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a template with instances cannot be deleted.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CanDeleteTemplate_WithInstances_ReturnsFailure()
|
||||
{
|
||||
@@ -56,6 +61,8 @@ public class TemplateDeletionServiceTests
|
||||
Assert.Contains("Inst1", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a template with child templates cannot be deleted.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CanDeleteTemplate_WithChildTemplates_ReturnsFailure()
|
||||
{
|
||||
@@ -79,6 +86,8 @@ public class TemplateDeletionServiceTests
|
||||
Assert.Contains("Child", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a template composed by other templates cannot be deleted.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CanDeleteTemplate_ComposedByOthers_ReturnsFailure()
|
||||
{
|
||||
@@ -103,10 +112,12 @@ public class TemplateDeletionServiceTests
|
||||
Assert.Contains("Composer", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that checking deletability does not issue a composition query per template.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CanDeleteTemplate_DoesNotIssuePerTemplateCompositionQuery()
|
||||
{
|
||||
// TemplateEngine-009: Check 3 must read the Compositions navigation
|
||||
// The composition check must read the Compositions navigation
|
||||
// already loaded by GetAllTemplatesAsync rather than issuing one
|
||||
// GetCompositionsByTemplateIdAsync round-trip per template.
|
||||
var templates = new List<Template>
|
||||
@@ -131,6 +142,8 @@ public class TemplateDeletionServiceTests
|
||||
It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that checking a missing template returns a failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CanDeleteTemplate_NotFound_ReturnsFailure()
|
||||
{
|
||||
@@ -143,6 +156,8 @@ public class TemplateDeletionServiceTests
|
||||
Assert.Contains("not found", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a template is deleted when all constraints are met.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteTemplate_AllConstraintsMet_Deletes()
|
||||
{
|
||||
@@ -163,6 +178,8 @@ public class TemplateDeletionServiceTests
|
||||
_repoMock.Verify(r => r.DeleteTemplateAsync(1, It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that all errors are reported when multiple constraints are violated.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CanDeleteTemplate_MultipleConstraints_AllErrorsReported()
|
||||
{
|
||||
|
||||
+69
-4
@@ -13,11 +13,14 @@ public class TemplateFolderServiceTests
|
||||
private readonly Mock<IAuditService> _auditMock = new();
|
||||
private readonly TemplateFolderService _sut;
|
||||
|
||||
/// <summary>Initializes the test class with a mocked repository and the service under test.</summary>
|
||||
public TemplateFolderServiceTests()
|
||||
{
|
||||
_sut = new TemplateFolderService(_repoMock.Object, _auditMock.Object);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that creating a folder with valid input succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateFolder_ValidInput_ReturnsSuccess()
|
||||
{
|
||||
@@ -30,10 +33,12 @@ public class TemplateFolderServiceTests
|
||||
Assert.Equal("Dev", result.Value.Name);
|
||||
Assert.Null(result.Value.ParentFolderId);
|
||||
_repoMock.Verify(r => r.AddFolderAsync(It.IsAny<TemplateFolder>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
// Two saves: the folder entity, then the staged audit row (TemplateEngine-024).
|
||||
// Two saves: the folder entity, then the staged audit row.
|
||||
_repoMock.Verify(r => r.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Exactly(2));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that creating a folder with an empty name fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateFolder_EmptyName_ReturnsFailure()
|
||||
{
|
||||
@@ -43,6 +48,8 @@ public class TemplateFolderServiceTests
|
||||
Assert.Contains("required", result.Error, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that creating a folder whose name duplicates a sibling's, ignoring case, fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateFolder_DuplicateSiblingName_CaseInsensitive_ReturnsFailure()
|
||||
{
|
||||
@@ -58,6 +65,8 @@ public class TemplateFolderServiceTests
|
||||
Assert.Contains("already exists", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that creating a folder under a missing parent fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateFolder_ParentNotFound_ReturnsFailure()
|
||||
{
|
||||
@@ -70,6 +79,8 @@ public class TemplateFolderServiceTests
|
||||
Assert.Contains("not found", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that renaming a folder with valid input succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RenameFolder_ValidInput_ReturnsSuccess()
|
||||
{
|
||||
@@ -84,6 +95,8 @@ public class TemplateFolderServiceTests
|
||||
Assert.Equal("New", result.Value.Name);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that renaming a missing folder fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RenameFolder_NotFound_ReturnsFailure()
|
||||
{
|
||||
@@ -95,6 +108,8 @@ public class TemplateFolderServiceTests
|
||||
Assert.True(result.IsFailure);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that renaming a folder to a sibling's name fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RenameFolder_DuplicateSibling_ReturnsFailure()
|
||||
{
|
||||
@@ -110,6 +125,8 @@ public class TemplateFolderServiceTests
|
||||
Assert.Contains("already exists", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that moving a folder under a valid parent succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MoveFolder_ValidParent_ReturnsSuccess()
|
||||
{
|
||||
@@ -126,6 +143,8 @@ public class TemplateFolderServiceTests
|
||||
Assert.Equal(2, result.Value.ParentFolderId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that moving a folder onto itself fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MoveFolder_OntoSelf_ReturnsFailure()
|
||||
{
|
||||
@@ -138,6 +157,8 @@ public class TemplateFolderServiceTests
|
||||
Assert.Contains("cycle", result.Error, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that moving a folder onto one of its descendants fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MoveFolder_OntoDescendant_ReturnsFailure()
|
||||
{
|
||||
@@ -156,6 +177,8 @@ public class TemplateFolderServiceTests
|
||||
Assert.Contains("cycle", result.Error, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that moving a folder to the root succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MoveFolder_ToRoot_ReturnsSuccess()
|
||||
{
|
||||
@@ -170,6 +193,8 @@ public class TemplateFolderServiceTests
|
||||
Assert.Null(result.Value.ParentFolderId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that moving a folder in a graph with a pre-existing cycle fails without looping forever.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MoveFolder_PreExistingCycleInGraph_ReturnsFailure_DoesNotInfiniteLoop()
|
||||
{
|
||||
@@ -189,6 +214,8 @@ public class TemplateFolderServiceTests
|
||||
Assert.Contains("cycle", result.Error, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deleting an empty folder succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteFolder_Empty_ReturnsSuccess()
|
||||
{
|
||||
@@ -205,6 +232,8 @@ public class TemplateFolderServiceTests
|
||||
_repoMock.Verify(r => r.DeleteFolderAsync(1, It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deleting a folder with child folders fails and reports the counts.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteFolder_HasChildFolders_ReturnsFailure_WithCounts()
|
||||
{
|
||||
@@ -222,6 +251,8 @@ public class TemplateFolderServiceTests
|
||||
Assert.Contains("1 subfolder", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deleting a folder that contains templates fails and reports the counts.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteFolder_HasTemplates_ReturnsFailure_WithCounts()
|
||||
{
|
||||
@@ -243,6 +274,8 @@ public class TemplateFolderServiceTests
|
||||
// ReorderFolderAsync — sibling reorder by SortOrder (Up = lower, Down = higher)
|
||||
// ========================================================================
|
||||
|
||||
/// <summary>Verifies that reordering a folder up swaps its sort order with the previous sibling.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReorderFolder_Up_SwapsSortOrderWithPreviousSibling()
|
||||
{
|
||||
@@ -262,6 +295,8 @@ public class TemplateFolderServiceTests
|
||||
Assert.Equal(2, c.SortOrder);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that reordering a folder down swaps its sort order with the next sibling.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReorderFolder_Down_SwapsSortOrderWithNextSibling()
|
||||
{
|
||||
@@ -281,6 +316,8 @@ public class TemplateFolderServiceTests
|
||||
Assert.Equal(1, c.SortOrder);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that reordering the top folder up is a no-op.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReorderFolder_UpAtTop_IsNoOp()
|
||||
{
|
||||
@@ -302,6 +339,8 @@ public class TemplateFolderServiceTests
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<object?>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that reordering the bottom folder down is a no-op.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReorderFolder_DownAtBottom_IsNoOp()
|
||||
{
|
||||
@@ -320,6 +359,8 @@ public class TemplateFolderServiceTests
|
||||
_repoMock.Verify(r => r.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that reordering only affects folders within the same parent.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReorderFolder_OnlyReordersWithinSameParent()
|
||||
{
|
||||
@@ -343,6 +384,8 @@ public class TemplateFolderServiceTests
|
||||
Assert.Equal(1, b.SortOrder);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that moving one of three siblings down and then up yields the expected order.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReorderFolder_ThreeSiblings_DownThenUp_YieldsExpectedOrder()
|
||||
{
|
||||
@@ -367,6 +410,8 @@ public class TemplateFolderServiceTests
|
||||
Assert.Equal(new[] { "B", "C", "A" }, ordered2);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that reordering a missing folder fails.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReorderFolder_NotFound_ReturnsFailure()
|
||||
{
|
||||
@@ -383,6 +428,8 @@ public class TemplateFolderServiceTests
|
||||
// CreateFolderAsync — distinct SortOrder assignment (max+1 among siblings)
|
||||
// ========================================================================
|
||||
|
||||
/// <summary>Verifies that the first sibling folder gets sort order zero.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateFolder_FirstSibling_GetsSortOrderZero()
|
||||
{
|
||||
@@ -401,6 +448,8 @@ public class TemplateFolderServiceTests
|
||||
Assert.Equal(0, captured!.SortOrder);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a second sibling folder under the same parent gets sort order one.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateFolder_SecondSiblingUnderSameParent_GetsSortOrderOne()
|
||||
{
|
||||
@@ -420,6 +469,8 @@ public class TemplateFolderServiceTests
|
||||
Assert.Equal(1, captured!.SortOrder);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that folder sort orders do not interfere across different parents.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateFolder_SortOrdersDoNotInterferAcrossParents()
|
||||
{
|
||||
@@ -442,6 +493,8 @@ public class TemplateFolderServiceTests
|
||||
Assert.Equal(0, captured!.SortOrder);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that reordering two sibling folders visibly changes their order.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateFolder_TwoSiblingsReorderIsVisiblyEffective()
|
||||
{
|
||||
@@ -471,6 +524,8 @@ public class TemplateFolderServiceTests
|
||||
_repoMock.Verify(r => r.UpdateFolderAsync(second, It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that reordering a folder persists the change and writes an audit entry.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReorderFolder_Persists_And_WritesAudit()
|
||||
{
|
||||
@@ -486,7 +541,7 @@ public class TemplateFolderServiceTests
|
||||
// Both swapped siblings persisted.
|
||||
_repoMock.Verify(r => r.UpdateFolderAsync(a, It.IsAny<CancellationToken>()), Times.Once);
|
||||
_repoMock.Verify(r => r.UpdateFolderAsync(b, It.IsAny<CancellationToken>()), Times.Once);
|
||||
// Two saves: the swapped siblings, then the staged audit row (TemplateEngine-024).
|
||||
// Two saves: the swapped siblings, then the staged audit row.
|
||||
_repoMock.Verify(r => r.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Exactly(2));
|
||||
// Audit entry written (mirrors Move/Rename audit assertions).
|
||||
_auditMock.Verify(au => au.LogAsync("admin", "Reorder", "TemplateFolder", "2", "B",
|
||||
@@ -494,11 +549,13 @@ public class TemplateFolderServiceTests
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// TemplateEngine-024 — each folder mutator must SaveChanges *after* LogAsync
|
||||
// Each folder mutator must SaveChanges *after* LogAsync
|
||||
// so the staged audit row is persisted (and not discarded when the scope is
|
||||
// disposed). Verified by tracking call order across the mutators.
|
||||
// ========================================================================
|
||||
|
||||
/// <summary>Verifies that creating a folder persists the audit row by saving after logging.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CreateFolder_PersistsAuditRow_SaveFollowsLog()
|
||||
{
|
||||
@@ -508,6 +565,8 @@ public class TemplateFolderServiceTests
|
||||
.ReturnsAsync(new List<TemplateFolder>())));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that renaming a folder persists the audit row by saving after logging.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RenameFolder_PersistsAuditRow_SaveFollowsLog()
|
||||
{
|
||||
@@ -522,6 +581,8 @@ public class TemplateFolderServiceTests
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that moving a folder persists the audit row by saving after logging.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MoveFolder_PersistsAuditRow_SaveFollowsLog()
|
||||
{
|
||||
@@ -538,6 +599,8 @@ public class TemplateFolderServiceTests
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that reordering a folder persists the audit row by saving after logging.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReorderFolder_PersistsAuditRow_SaveFollowsLog()
|
||||
{
|
||||
@@ -553,6 +616,8 @@ public class TemplateFolderServiceTests
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deleting a folder persists the audit row by saving after logging.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteFolder_PersistsAuditRow_SaveFollowsLog()
|
||||
{
|
||||
@@ -593,7 +658,7 @@ public class TemplateFolderServiceTests
|
||||
/// <summary>
|
||||
/// Asserts the mutator logged an audit entry and then issued a
|
||||
/// <c>SaveChangesAsync</c> after it — proving the staged audit row is
|
||||
/// persisted rather than discarded (TemplateEngine-024).
|
||||
/// persisted rather than discarded.
|
||||
/// </summary>
|
||||
private static void AssertAuditRowPersisted(List<string> calls)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user