Explorar el Código

Merge pull request #350 from VirSanctus/bug/328-case-d

Allow renaming a resource to a different case of its own name (#328)
Tim Jones hace 14 horas
padre
commit
1f4c9fa7ea

+ 8 - 2
RackPeek.Domain/UseCases/RenameResourceUseCase.cs

@@ -18,14 +18,20 @@ public class RenameResourceUseCase<T>(IResourceCollection repo) : IRenameResourc
         newName = Normalize.HardwareName(newName);
         ThrowIfInvalid.ResourceName(newName);
 
-        Resource? existingResource = await repo.GetByNameAsync(newName);
+        Resource? original = await repo.GetByNameAsync(originalName);
+
+        IReadOnlyList<Resource> resources = await repo.GetAllOfTypeAsync<Resource>();
+        Resource? existingResource = resources.FirstOrDefault(r =>
+            !ReferenceEquals(r, original) && r.Name.Equals(newName, StringComparison.OrdinalIgnoreCase));
         if (existingResource != null)
             throw new ConflictException($"{existingResource.Kind} resource '{newName}' already exists.");
 
-        Resource? original = await repo.GetByNameAsync(originalName);
         if (original == null)
             throw new NotFoundException($"Resource '{originalName}' not found.");
 
+        if (original.Name == newName)
+            return;
+
         original.Name = newName;
 
         // A person has now chosen this name, so discovery must stop improving on it.

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

@@ -208,4 +208,110 @@ public class RenameResourceTests(TempYamlCliFixture fs, ITestOutputHelper output
         Assert.Contains("srv-prod-app-01", yaml);
         Assert.Contains("app-backend-link", yaml);
     }
+
+    [Fact]
+    public async Task rename_that_only_changes_case_updates_the_name() {
+        await ExecuteAsync("servers", "add", "CaseOnly21");
+
+        (var output, var yaml) = await ExecuteAsync("servers", "rename", "CaseOnly21", "caseonly21");
+
+        Assert.Contains("Server 'CaseOnly21' renamed to 'caseonly21'.", output);
+        Assert.Contains("name: caseonly21", yaml);
+        Assert.DoesNotContain("name: CaseOnly21", yaml);
+    }
+
+    [Fact]
+    public async Task rename_that_only_changes_case_updates_runs_on_and_connections() {
+        await ExecuteAsync("servers", "add", "CaseOnly31");
+        await ExecuteAsync("servers", "add", "CaseOnly32");
+
+        await ExecuteAsync("servers", "nic", "add", "CaseOnly31",
+            "--type", "RJ45", "--speed", "10", "--ports", "2");
+
+        await ExecuteAsync("servers", "nic", "add", "CaseOnly32",
+            "--type", "RJ45", "--speed", "10", "--ports", "2");
+
+        await ExecuteAsync("connections", "add",
+            "CaseOnly31", "0", "0",
+            "CaseOnly32", "0", "0",
+            "--label", "case-only-link");
+
+        await ExecuteAsync("systems", "add", "sys-case-only-31");
+        await ExecuteAsync("systems", "set", "sys-case-only-31", "--runs-on", "CaseOnly31");
+
+        (var output, var yaml) = await ExecuteAsync("servers", "rename", "CaseOnly31", "CASEONLY31");
+
+        Assert.Contains("Server 'CaseOnly31' renamed to 'CASEONLY31'.", output);
+        Assert.Contains("name: CASEONLY31", yaml);
+        Assert.Contains("resource: CASEONLY31", yaml);
+        Assert.Contains("- CASEONLY31", yaml);
+        Assert.DoesNotContain("CaseOnly31", yaml);
+    }
+
+    [Fact]
+    public async Task rename_onto_another_resources_case_variant_is_still_refused() {
+        await ExecuteAsync("servers", "add", "Clash41");
+        await ExecuteAsync("servers", "add", "Clash42");
+        await ExecuteAsync("systems", "add", "clash43");
+
+        (var sameKind, _) = await ExecuteAsync("servers", "rename", "Clash41", "CLASH42");
+        (var otherKind, var yaml) = await ExecuteAsync("servers", "rename", "Clash41", "CLASH43");
+
+        Assert.Contains("Conflict: Server resource 'CLASH42' already exists.", sameKind);
+        Assert.Contains("Conflict: System resource 'CLASH43' already exists.", otherKind);
+        Assert.Contains("name: Clash41", yaml);
+        Assert.Contains("name: Clash42", yaml);
+        Assert.Contains("name: clash43", yaml);
+        Assert.DoesNotContain("CLASH4", yaml);
+    }
+
+    [Fact]
+    public async Task rename_to_the_identical_name_leaves_the_file_unchanged() {
+        await ExecuteAsync("servers", "add", "Same51");
+        await ExecuteAsync("servers", "add", "Same52");
+        await ExecuteAsync("servers", "add", "Same53");
+
+        foreach (var s in new[] { "Same51", "Same52", "Same53" }) {
+            await ExecuteAsync("servers", "nic", "add", s,
+                "--type", "RJ45", "--speed", "10", "--ports", "2");
+        }
+
+        await ExecuteAsync("connections", "add",
+            "Same51", "0", "0",
+            "Same52", "0", "0",
+            "--label", "same-link");
+
+        await ExecuteAsync("connections", "add",
+            "Same52", "0", "1",
+            "Same53", "0", "0",
+            "--label", "other-link");
+
+        await ExecuteAsync("systems", "add", "sys-same-51");
+        (_, var before) = await ExecuteAsync("systems", "set", "sys-same-51", "--runs-on", "same51");
+
+        (var output, var after) = await ExecuteAsync("servers", "rename", "Same51", "Same51");
+
+        Assert.Contains("Server 'Same51' renamed to 'Same51'.", output);
+        Assert.Equal(before, after);
+    }
+
+    [Fact]
+    public async Task rename_is_refused_when_the_config_already_holds_a_case_variant() {
+        await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), """
+            version: 4
+            resources:
+            - kind: Server
+              name: Dup61
+            - kind: Server
+              name: dup61
+            connections: []
+            """);
+
+        (var output, var yaml) = await ExecuteAsync("servers", "rename", "Dup61", "DUP61");
+
+        Assert.Contains("Conflict: Server resource 'DUP61' already exists.", output);
+        Assert.Contains("name: Dup61", yaml);
+        Assert.Contains("name: dup61", yaml);
+        Assert.DoesNotContain("DUP61", yaml);
+    }
 }