Kaynağa Gözat

Compare resource names case-insensitively on delete and rename (#328)

Deleting or renaming a resource finds it case-insensitively, but the
cleanup of runsOn references and connections compared names with
case-sensitive operators. Typing a name in a different case from the
stored one removed or renamed the resource and left every reference to
it behind, which is the #258 symptom returning whenever the casing does
not match.

Use OrdinalIgnoreCase for those comparisons in DeleteResourceUseCase and
RenameResourceUseCase, matching the repository lookups.

Add CLI tests covering delete and rename with a mismatched case for both
runsOn references and connections.
VirSanctus 5 saat önce
ebeveyn
işleme
0be51e6e07

+ 3 - 2
RackPeek.Domain/UseCases/DeleteResourceUseCase.cs

@@ -21,13 +21,14 @@ public class DeleteResourceUseCase<T>(IResourceCollection repo) : IDeleteResourc
 
         IReadOnlyList<Resource> dependants = await repo.GetDependantsAsync(name);
         foreach (Resource resource in dependants) {
-            resource.RunsOn.Remove(name);
+            resource.RunsOn.RemoveAll(p => p.Equals(name, StringComparison.OrdinalIgnoreCase));
             await repo.UpdateAsync(resource);
         }
 
         IReadOnlyList<Connection> connections = await repo.GetConnectionsAsync();
         foreach (Connection connection in connections) {
-            if (connection.A.Resource == name || connection.B.Resource == name) {
+            if (connection.A.Resource.Equals(name, StringComparison.OrdinalIgnoreCase)
+                || connection.B.Resource.Equals(name, StringComparison.OrdinalIgnoreCase)) {
                 await repo.RemoveConnectionAsync(connection);
             }
         }

+ 4 - 4
RackPeek.Domain/UseCases/RenameResourceUseCase.cs

@@ -32,9 +32,9 @@ public class RenameResourceUseCase<T>(IResourceCollection repo) : IRenameResourc
         IReadOnlyList<Resource> allResources = await repo.GetAllOfTypeAsync<Resource>();
 
         foreach (Resource resource in allResources) {
-            if (resource.RunsOn.Contains(originalName)) {
+            if (resource.RunsOn.Contains(originalName, StringComparer.OrdinalIgnoreCase)) {
                 resource.RunsOn = resource.RunsOn
-                    .ConvertAll(p => p == originalName ? newName : p);
+                    .ConvertAll(p => p.Equals(originalName, StringComparison.OrdinalIgnoreCase) ? newName : p);
 
                 await repo.UpdateAsync(resource);
             }
@@ -44,12 +44,12 @@ public class RenameResourceUseCase<T>(IResourceCollection repo) : IRenameResourc
         foreach (Connection connection in connections) {
             var updated = false;
 
-            if (connection.A.Resource == originalName) {
+            if (connection.A.Resource.Equals(originalName, StringComparison.OrdinalIgnoreCase)) {
                 connection.A.Resource = newName;
                 updated = true;
             }
 
-            if (connection.B.Resource == originalName) {
+            if (connection.B.Resource.Equals(originalName, StringComparison.OrdinalIgnoreCase)) {
                 connection.B.Resource = newName;
                 updated = true;
             }

+ 41 - 0
Tests/EndToEnd/ConnectionTests/RenameResourceTests.cs

@@ -142,6 +142,47 @@ public class RenameResourceTests(TempYamlCliFixture fs, ITestOutputHelper output
         Assert.Contains("switch-uplink", yaml);
     }
 
+    [Fact]
+    public async Task rename_typed_in_different_case_preserves_connection() {
+        await ExecuteAsync("servers", "add", "Case01");
+        await ExecuteAsync("servers", "add", "Case02");
+
+        await ExecuteAsync("servers", "nic", "add", "Case01",
+            "--type", "RJ45", "--speed", "10", "--ports", "2");
+
+        await ExecuteAsync("servers", "nic", "add", "Case02",
+            "--type", "RJ45", "--speed", "10", "--ports", "2");
+
+        await ExecuteAsync("connections", "add",
+            "Case01", "0", "0",
+            "Case02", "0", "0",
+            "--label", "mixed-case-link");
+
+        await ExecuteAsync("servers", "rename", "case01", "Case01-renamed");
+
+        (_, var yaml) = await ExecuteAsync("servers", "get", "Case01-renamed");
+
+        Assert.Contains("name: Case01-renamed", yaml);
+        Assert.Contains("mixed-case-link", yaml);
+        // The connection endpoint should follow the rename, not keep pointing at the old name
+        Assert.Contains("resource: Case01-renamed", yaml);
+    }
+
+    [Fact]
+    public async Task rename_typed_in_different_case_updates_runs_on() {
+        await ExecuteAsync("servers", "add", "Case11");
+        await ExecuteAsync("systems", "add", "sys-case-11");
+        await ExecuteAsync("systems", "set", "sys-case-11", "--runs-on", "Case11");
+
+        await ExecuteAsync("servers", "rename", "case11", "Case12");
+
+        (_, var yaml) = await ExecuteAsync("servers", "get", "Case12");
+
+        Assert.Contains("name: Case12", yaml);
+        // The dependant system should follow the rename
+        Assert.Contains("- Case12", yaml);
+    }
+
     [Fact]
     public async Task rename_with_special_naming_preserves_connections() {
         await ExecuteAsync("servers", "add", "srv-prod-web-01");

+ 47 - 0
Tests/EndToEnd/DeleteResourceTests.cs

@@ -105,6 +105,53 @@ public class DeleteResourceTests(TempYamlCliFixture fs, ITestOutputHelper output
         Assert.DoesNotContain("srv01", yaml);
     }
 
+    [Fact]
+    public async Task deleting_resource_typed_in_different_case_removes_dependant_runs_on() {
+        await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), "");
+        await ExecuteAsync("servers", "add", "Srv01");
+        await ExecuteAsync("systems", "add", "sys01");
+        await ExecuteAsync("systems", "set", "sys01", "--runs-on", "Srv01");
+
+        (var output, var yaml) = await ExecuteAsync("servers", "del", "srv01");
+
+        Assert.Contains("Server 'srv01' deleted.", output);
+        Assert.Contains("sys01", yaml);
+        // The runs-on reference should be removed even though the name was typed in a different case
+        Assert.DoesNotContain("Srv01", yaml, StringComparison.OrdinalIgnoreCase);
+    }
+
+    [Fact]
+    public async Task deleting_resource_typed_in_different_case_removes_connections() {
+        await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), "");
+        await ExecuteAsync("servers", "add", "Srv01");
+        await ExecuteAsync("servers", "add", "Srv02");
+
+        await ExecuteAsync(
+            "servers", "nic", "add", "Srv01",
+            "--type", "RJ45",
+            "--speed", "10",
+            "--ports", "2");
+
+        await ExecuteAsync(
+            "servers", "nic", "add", "Srv02",
+            "--type", "RJ45",
+            "--speed", "10",
+            "--ports", "2");
+
+        await ExecuteAsync(
+            "connections", "add",
+            "Srv01", "0", "0",
+            "Srv02", "0", "0",
+            "--label", "mixed-case-connection");
+
+        (var output, var yaml) = await ExecuteAsync("servers", "del", "srv01");
+
+        Assert.Contains("Server 'srv01' deleted.", output);
+        Assert.Contains("Srv02", yaml);
+        Assert.DoesNotContain("mixed-case-connection", yaml);
+        Assert.DoesNotContain("Srv01", yaml, StringComparison.OrdinalIgnoreCase);
+    }
+
     [Fact]
     public async Task deleting_resource_with_multiple_connections_removes_all() {
         await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), "");