Bläddra i källkod

Delete / Rename updates connections

Tim Jones 3 timmar sedan
förälder
incheckning
1dbf918ec7
25 ändrade filer med 866 tillägg och 9 borttagningar
  1. 9 1
      RackPeek.Domain/UseCases/DeleteResourceUseCase.cs
  2. 35 8
      RackPeek.Domain/UseCases/RenameResourceUseCase.cs
  3. 45 0
      Shared.Rcl/CliBootstrap.cs
  4. 29 0
      Shared.Rcl/Commands/AccessPoints/Rename/AccessPointRenameCommand.cs
  5. 29 0
      Shared.Rcl/Commands/Desktops/Rename/DesktopRenameCommand.cs
  6. 29 0
      Shared.Rcl/Commands/Firewalls/Rename/FirewallRenameCommand.cs
  7. 29 0
      Shared.Rcl/Commands/Laptops/Rename/LaptopRenameCommand.cs
  8. 29 0
      Shared.Rcl/Commands/Routers/Rename/RouterRenameCommand.cs
  9. 29 0
      Shared.Rcl/Commands/Servers/Rename/ServerRenameCommand.cs
  10. 29 0
      Shared.Rcl/Commands/Services/Rename/ServiceRenameCommand.cs
  11. 29 0
      Shared.Rcl/Commands/Switches/Rename/SwitchRenameCommand.cs
  12. 29 0
      Shared.Rcl/Commands/Systems/Rename/SystemRenameCommand.cs
  13. 30 0
      Shared.Rcl/Commands/Ups/Rename/UpsRenameCommand.cs
  14. 12 0
      Tests/EndToEnd/AccessPointTests/AccessPointCommandTests.cs
  15. 170 0
      Tests/EndToEnd/ConnectionTests/RenameResourceTests.cs
  16. 190 0
      Tests/EndToEnd/DeleteResourceTests.cs
  17. 11 0
      Tests/EndToEnd/DesktopTests/DesktopCommandTests.cs
  18. 11 0
      Tests/EndToEnd/FirewallTests/FirewallCommandTests.cs
  19. 11 0
      Tests/EndToEnd/LaptopTests/LaptopCommandTests.cs
  20. 11 0
      Tests/EndToEnd/RouterTests/RouterCommandTests.cs
  21. 25 0
      Tests/EndToEnd/ServerTests/ServerCommandTests.cs
  22. 11 0
      Tests/EndToEnd/ServiceTests/ServiceCommandTests.cs
  23. 11 0
      Tests/EndToEnd/SwitchTests/SwitchCommandTests.cs
  24. 11 0
      Tests/EndToEnd/SystemTests/SystemCommandTests.cs
  25. 12 0
      Tests/EndToEnd/UpsTests/UpsCommandTests.cs

+ 9 - 1
RackPeek.Domain/UseCases/DeleteResourceUseCase.cs

@@ -1,6 +1,7 @@
 using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Persistence;
 using RackPeek.Domain.Resources;
+using RackPeek.Domain.Resources.Connections;
 
 namespace RackPeek.Domain.UseCases;
 
@@ -24,6 +25,13 @@ public class DeleteResourceUseCase<T>(IResourceCollection repo) : IDeleteResourc
             await repo.UpdateAsync(resource);
         }
 
+        IReadOnlyList<Connection> connections = await repo.GetConnectionsAsync();
+        foreach (Connection connection in connections) {
+            if (connection.A.Resource == name || connection.B.Resource == name) {
+                await repo.RemoveConnectionAsync(connection);
+            }
+        }
+
         await repo.DeleteAsync(name);
     }
-}
+}

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

@@ -1,20 +1,21 @@
 using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Persistence;
 using RackPeek.Domain.Resources;
+using RackPeek.Domain.Resources.Connections;
 
 namespace RackPeek.Domain.UseCases;
 
 public interface IRenameResourceUseCase<T> : IResourceUseCase<T>
     where T : Resource {
-    public Task ExecuteAsync(string originalName, string newName);
+    Task ExecuteAsync(string originalName, string newName);
 }
 
 public class RenameResourceUseCase<T>(IResourceCollection repo) : IRenameResourceUseCase<T> where T : Resource {
     public async Task ExecuteAsync(string originalName, string newName) {
-        originalName = Normalize.SystemName(originalName);
+        originalName = Normalize.HardwareName(originalName);
         ThrowIfInvalid.ResourceName(originalName);
 
-        newName = Normalize.SystemName(newName);
+        newName = Normalize.HardwareName(newName);
         ThrowIfInvalid.ResourceName(newName);
 
         Resource? existingResource = await repo.GetByNameAsync(newName);
@@ -22,15 +23,41 @@ public class RenameResourceUseCase<T>(IResourceCollection repo) : IRenameResourc
             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 == null)
+            throw new NotFoundException($"Resource '{originalName}' not found.");
 
         original.Name = newName;
         await repo.UpdateAsync(original);
 
-        IReadOnlyList<Resource> children = await repo.GetDependantsAsync(originalName);
-        foreach (Resource child in children) {
-            child.RunsOn = child.RunsOn.ConvertAll<string>(p => p == originalName ? newName : p);
-            await repo.UpdateAsync(child);
+        IReadOnlyList<Resource> allResources = await repo.GetAllOfTypeAsync<Resource>();
+
+        foreach (Resource resource in allResources) {
+            if (resource.RunsOn.Contains(originalName)) {
+                resource.RunsOn = resource.RunsOn
+                    .ConvertAll(p => p == originalName ? newName : p);
+
+                await repo.UpdateAsync(resource);
+            }
+        }
+
+        IReadOnlyList<Connection> connections = await repo.GetConnectionsAsync();
+        foreach (Connection connection in connections) {
+            var updated = false;
+
+            if (connection.A.Resource == originalName) {
+                connection.A.Resource = newName;
+                updated = true;
+            }
+
+            if (connection.B.Resource == originalName) {
+                connection.B.Resource = newName;
+                updated = true;
+            }
+
+            if (updated) {
+                await repo.RemoveConnectionAsync(connection);
+                await repo.AddConnectionAsync(connection);
+            }
         }
     }
 }

+ 45 - 0
Shared.Rcl/CliBootstrap.cs

@@ -8,6 +8,7 @@ using RackPeek.Domain.Persistence.Yaml;
 using Shared.Rcl.Commands;
 using Shared.Rcl.Commands.AccessPoints;
 using Shared.Rcl.Commands.AccessPoints.Labels;
+using Shared.Rcl.Commands.AccessPoints.Rename;
 using Shared.Rcl.Commands.Connections;
 using Shared.Rcl.Commands.Desktops;
 using Shared.Rcl.Commands.Desktops.Cpus;
@@ -15,33 +16,42 @@ using Shared.Rcl.Commands.Desktops.Drive;
 using Shared.Rcl.Commands.Desktops.Gpus;
 using Shared.Rcl.Commands.Desktops.Labels;
 using Shared.Rcl.Commands.Desktops.Nics;
+using Shared.Rcl.Commands.Desktops.Rename;
 using Shared.Rcl.Commands.Exporters;
 using Shared.Rcl.Commands.Firewalls;
 using Shared.Rcl.Commands.Firewalls.Labels;
 using Shared.Rcl.Commands.Firewalls.Ports;
+using Shared.Rcl.Commands.Firewalls.Rename;
 using Shared.Rcl.Commands.Laptops;
 using Shared.Rcl.Commands.Laptops.Cpus;
 using Shared.Rcl.Commands.Laptops.Drive;
 using Shared.Rcl.Commands.Laptops.Gpus;
 using Shared.Rcl.Commands.Laptops.Labels;
+using Shared.Rcl.Commands.Laptops.Rename;
 using Shared.Rcl.Commands.Routers;
 using Shared.Rcl.Commands.Routers.Labels;
 using Shared.Rcl.Commands.Routers.Ports;
+using Shared.Rcl.Commands.Routers.Rename;
 using Shared.Rcl.Commands.Servers;
 using Shared.Rcl.Commands.Servers.Cpus;
 using Shared.Rcl.Commands.Servers.Drives;
 using Shared.Rcl.Commands.Servers.Gpus;
 using Shared.Rcl.Commands.Servers.Labels;
 using Shared.Rcl.Commands.Servers.Nics;
+using Shared.Rcl.Commands.Servers.Rename;
 using Shared.Rcl.Commands.Services;
 using Shared.Rcl.Commands.Services.Labels;
+using Shared.Rcl.Commands.Services.Rename;
 using Shared.Rcl.Commands.Switches;
 using Shared.Rcl.Commands.Switches.Labels;
 using Shared.Rcl.Commands.Switches.Ports;
+using Shared.Rcl.Commands.Switches.Rename;
 using Shared.Rcl.Commands.Systems;
 using Shared.Rcl.Commands.Systems.Labels;
+using Shared.Rcl.Commands.Systems.Rename;
 using Shared.Rcl.Commands.Ups;
 using Shared.Rcl.Commands.Ups.Labels;
+using Shared.Rcl.Commands.Ups.Rename;
 using Spectre.Console;
 using Spectre.Console.Cli;
 
@@ -134,6 +144,9 @@ public static class CliBootstrap {
 
                 server.AddCommand<ServerDeleteCommand>("del").WithDescription("Delete a server from the inventory.");
 
+                server.AddCommand<ServerRenameCommand>("rename")
+                    .WithDescription("Rename a server to a new name.");
+
                 server.AddCommand<ServerTreeCommand>("tree")
                     .WithDescription("Display the dependency tree of a server.");
 
@@ -216,6 +229,10 @@ public static class CliBootstrap {
                 switches.AddCommand<SwitchSetCommand>("set").WithDescription("Update properties of a switch.");
 
                 switches.AddCommand<SwitchDeleteCommand>("del").WithDescription("Delete a switch from the inventory.");
+
+                switches.AddCommand<SwitchRenameCommand>("rename")
+                    .WithDescription("Rename a switch to a new name.");
+
                 switches.AddBranch("port", port => {
                     port.SetDescription("Manage ports on a network switch.");
 
@@ -257,6 +274,10 @@ public static class CliBootstrap {
                 routers.AddCommand<RouterSetCommand>("set").WithDescription("Update properties of a router.");
 
                 routers.AddCommand<RouterDeleteCommand>("del").WithDescription("Delete a router from the inventory.");
+
+                routers.AddCommand<RouterRenameCommand>("rename")
+                    .WithDescription("Rename a router to a new name.");
+
                 routers.AddBranch("port", port => {
                     port.SetDescription("Manage ports on a router.");
 
@@ -298,6 +319,10 @@ public static class CliBootstrap {
 
                 firewalls.AddCommand<FirewallDeleteCommand>("del")
                     .WithDescription("Delete a firewall from the inventory.");
+
+                firewalls.AddCommand<FirewallRenameCommand>("rename")
+                    .WithDescription("Rename a firewall to a new name.");
+
                 firewalls.AddBranch("port", port => {
                     port.SetDescription("Manage ports on a firewall.");
 
@@ -338,6 +363,9 @@ public static class CliBootstrap {
 
                 system.AddCommand<SystemDeleteCommand>("del").WithDescription("Delete a system from the inventory.");
 
+                system.AddCommand<SystemRenameCommand>("rename")
+                    .WithDescription("Rename a system to a new name.");
+
                 system.AddCommand<SystemTreeCommand>("tree")
                     .WithDescription("Display the dependency tree for a system.");
 
@@ -371,6 +399,9 @@ public static class CliBootstrap {
 
                 ap.AddCommand<AccessPointDeleteCommand>("del").WithDescription("Delete an access point.");
 
+                ap.AddCommand<AccessPointRenameCommand>("rename")
+                    .WithDescription("Rename an access point to a new name.");
+
                 ap.AddBranch("label", label => {
                     label.SetDescription("Manage labels on an access point.");
                     label.AddCommand<AccessPointLabelAddCommand>("add")
@@ -402,6 +433,9 @@ public static class CliBootstrap {
 
                 ups.AddCommand<UpsDeleteCommand>("del").WithDescription("Delete a UPS unit.");
 
+                ups.AddCommand<UpsRenameCommand>("rename")
+                    .WithDescription("Rename a UPS unit to a new name.");
+
                 ups.AddBranch("label", label => {
                     label.SetDescription("Manage labels on a UPS unit.");
                     label.AddCommand<UpsLabelAddCommand>("add").WithDescription("Add a label to a UPS unit.");
@@ -425,6 +459,10 @@ public static class CliBootstrap {
                 desktops.AddCommand<DesktopSetCommand>("set").WithDescription("Update properties of a desktop.");
                 desktops.AddCommand<DesktopDeleteCommand>("del")
                     .WithDescription("Delete a desktop from the inventory.");
+
+                desktops.AddCommand<DesktopRenameCommand>("rename")
+                    .WithDescription("Rename a desktop to a new name.");
+
                 desktops.AddCommand<DesktopReportCommand>("summary")
                     .WithDescription("Show a summarized hardware report for all desktops.");
                 desktops.AddCommand<DesktopTreeCommand>("tree")
@@ -485,6 +523,10 @@ public static class CliBootstrap {
                     .WithDescription("Show detailed information about a Laptop.");
                 laptops.AddCommand<LaptopSetCommand>("set").WithDescription("Update properties of a laptop.");
                 laptops.AddCommand<LaptopDeleteCommand>("del").WithDescription("Delete a Laptop from the inventory.");
+
+                laptops.AddCommand<LaptopRenameCommand>("rename")
+                    .WithDescription("Rename a Laptop to a new name.");
+
                 laptops.AddCommand<LaptopReportCommand>("summary")
                     .WithDescription("Show a summarized hardware report for all Laptops.");
                 laptops.AddCommand<LaptopTreeCommand>("tree")
@@ -544,6 +586,9 @@ public static class CliBootstrap {
 
                 service.AddCommand<ServiceDeleteCommand>("del").WithDescription("Delete a service.");
 
+                service.AddCommand<ServiceRenameCommand>("rename")
+                    .WithDescription("Rename a service to a new name.");
+
                 service.AddCommand<ServiceSubnetsCommand>("subnets")
                     .WithDescription("List subnets associated with a service, optionally filtered by CIDR.");
 

+ 29 - 0
Shared.Rcl/Commands/AccessPoints/Rename/AccessPointRenameCommand.cs

@@ -0,0 +1,29 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.AccessPoints;
+using RackPeek.Domain.UseCases;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace Shared.Rcl.Commands.AccessPoints.Rename;
+
+public class AccessPointRenameSettings : AccessPointNameSettings {
+    [CommandArgument(1, "<new-name>")]
+    public string NewName { get; set; } = default!;
+}
+
+public class AccessPointRenameCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<AccessPointRenameSettings> {
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        AccessPointRenameSettings settings,
+        CancellationToken cancellationToken) {
+        using IServiceScope scope = serviceProvider.CreateScope();
+        IRenameResourceUseCase<AccessPoint> renameUseCase = scope.ServiceProvider.GetRequiredService<IRenameResourceUseCase<AccessPoint>>();
+
+        await renameUseCase.ExecuteAsync(settings.Name, settings.NewName);
+
+        AnsiConsole.MarkupLine($"[green]AccessPoint '{settings.Name}' renamed to '{settings.NewName}'.[/]");
+        return 0;
+    }
+}

+ 29 - 0
Shared.Rcl/Commands/Desktops/Rename/DesktopRenameCommand.cs

@@ -0,0 +1,29 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Desktops;
+using RackPeek.Domain.UseCases;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace Shared.Rcl.Commands.Desktops.Rename;
+
+public class DesktopRenameSettings : DesktopNameSettings {
+    [CommandArgument(1, "<new-name>")]
+    public string NewName { get; set; } = default!;
+}
+
+public class DesktopRenameCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<DesktopRenameSettings> {
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        DesktopRenameSettings settings,
+        CancellationToken cancellationToken) {
+        using IServiceScope scope = serviceProvider.CreateScope();
+        IRenameResourceUseCase<Desktop> renameUseCase = scope.ServiceProvider.GetRequiredService<IRenameResourceUseCase<Desktop>>();
+
+        await renameUseCase.ExecuteAsync(settings.Name, settings.NewName);
+
+        AnsiConsole.MarkupLine($"[green]Desktop '{settings.Name}' renamed to '{settings.NewName}'.[/]");
+        return 0;
+    }
+}

+ 29 - 0
Shared.Rcl/Commands/Firewalls/Rename/FirewallRenameCommand.cs

@@ -0,0 +1,29 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Firewalls;
+using RackPeek.Domain.UseCases;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace Shared.Rcl.Commands.Firewalls.Rename;
+
+public class FirewallRenameSettings : FirewallNameSettings {
+    [CommandArgument(1, "<new-name>")]
+    public string NewName { get; set; } = default!;
+}
+
+public class FirewallRenameCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<FirewallRenameSettings> {
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        FirewallRenameSettings settings,
+        CancellationToken cancellationToken) {
+        using IServiceScope scope = serviceProvider.CreateScope();
+        IRenameResourceUseCase<Firewall> renameUseCase = scope.ServiceProvider.GetRequiredService<IRenameResourceUseCase<Firewall>>();
+
+        await renameUseCase.ExecuteAsync(settings.Name, settings.NewName);
+
+        AnsiConsole.MarkupLine($"[green]Firewall '{settings.Name}' renamed to '{settings.NewName}'.[/]");
+        return 0;
+    }
+}

+ 29 - 0
Shared.Rcl/Commands/Laptops/Rename/LaptopRenameCommand.cs

@@ -0,0 +1,29 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Laptops;
+using RackPeek.Domain.UseCases;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace Shared.Rcl.Commands.Laptops.Rename;
+
+public class LaptopRenameSettings : LaptopNameSettings {
+    [CommandArgument(1, "<new-name>")]
+    public string NewName { get; set; } = default!;
+}
+
+public class LaptopRenameCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<LaptopRenameSettings> {
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        LaptopRenameSettings settings,
+        CancellationToken cancellationToken) {
+        using IServiceScope scope = serviceProvider.CreateScope();
+        IRenameResourceUseCase<Laptop> renameUseCase = scope.ServiceProvider.GetRequiredService<IRenameResourceUseCase<Laptop>>();
+
+        await renameUseCase.ExecuteAsync(settings.Name, settings.NewName);
+
+        AnsiConsole.MarkupLine($"[green]Laptop '{settings.Name}' renamed to '{settings.NewName}'.[/]");
+        return 0;
+    }
+}

+ 29 - 0
Shared.Rcl/Commands/Routers/Rename/RouterRenameCommand.cs

@@ -0,0 +1,29 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Routers;
+using RackPeek.Domain.UseCases;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace Shared.Rcl.Commands.Routers.Rename;
+
+public class RouterRenameSettings : RouterNameSettings {
+    [CommandArgument(1, "<new-name>")]
+    public string NewName { get; set; } = default!;
+}
+
+public class RouterRenameCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<RouterRenameSettings> {
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        RouterRenameSettings settings,
+        CancellationToken cancellationToken) {
+        using IServiceScope scope = serviceProvider.CreateScope();
+        IRenameResourceUseCase<Router> renameUseCase = scope.ServiceProvider.GetRequiredService<IRenameResourceUseCase<Router>>();
+
+        await renameUseCase.ExecuteAsync(settings.Name, settings.NewName);
+
+        AnsiConsole.MarkupLine($"[green]Router '{settings.Name}' renamed to '{settings.NewName}'.[/]");
+        return 0;
+    }
+}

+ 29 - 0
Shared.Rcl/Commands/Servers/Rename/ServerRenameCommand.cs

@@ -0,0 +1,29 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Servers;
+using RackPeek.Domain.UseCases;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace Shared.Rcl.Commands.Servers.Rename;
+
+public class ServerRenameSettings : ServerNameSettings {
+    [CommandArgument(1, "<new-name>")]
+    public string NewName { get; set; } = default!;
+}
+
+public class ServerRenameCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<ServerRenameSettings> {
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        ServerRenameSettings settings,
+        CancellationToken cancellationToken) {
+        using IServiceScope scope = serviceProvider.CreateScope();
+        IRenameResourceUseCase<Server> renameUseCase = scope.ServiceProvider.GetRequiredService<IRenameResourceUseCase<Server>>();
+
+        await renameUseCase.ExecuteAsync(settings.Name, settings.NewName);
+
+        AnsiConsole.MarkupLine($"[green]Server '{settings.Name}' renamed to '{settings.NewName}'.[/]");
+        return 0;
+    }
+}

+ 29 - 0
Shared.Rcl/Commands/Services/Rename/ServiceRenameCommand.cs

@@ -0,0 +1,29 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Services;
+using RackPeek.Domain.UseCases;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace Shared.Rcl.Commands.Services.Rename;
+
+public class ServiceRenameSettings : ServiceNameSettings {
+    [CommandArgument(1, "<new-name>")]
+    public string NewName { get; set; } = default!;
+}
+
+public class ServiceRenameCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<ServiceRenameSettings> {
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        ServiceRenameSettings settings,
+        CancellationToken cancellationToken) {
+        using IServiceScope scope = serviceProvider.CreateScope();
+        IRenameResourceUseCase<Service> renameUseCase = scope.ServiceProvider.GetRequiredService<IRenameResourceUseCase<Service>>();
+
+        await renameUseCase.ExecuteAsync(settings.Name, settings.NewName);
+
+        AnsiConsole.MarkupLine($"[green]Service '{settings.Name}' renamed to '{settings.NewName}'.[/]");
+        return 0;
+    }
+}

+ 29 - 0
Shared.Rcl/Commands/Switches/Rename/SwitchRenameCommand.cs

@@ -0,0 +1,29 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.Switches;
+using RackPeek.Domain.UseCases;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace Shared.Rcl.Commands.Switches.Rename;
+
+public class SwitchRenameSettings : SwitchNameSettings {
+    [CommandArgument(1, "<new-name>")]
+    public string NewName { get; set; } = default!;
+}
+
+public class SwitchRenameCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<SwitchRenameSettings> {
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        SwitchRenameSettings settings,
+        CancellationToken cancellationToken) {
+        using IServiceScope scope = serviceProvider.CreateScope();
+        IRenameResourceUseCase<Switch> renameUseCase = scope.ServiceProvider.GetRequiredService<IRenameResourceUseCase<Switch>>();
+
+        await renameUseCase.ExecuteAsync(settings.Name, settings.NewName);
+
+        AnsiConsole.MarkupLine($"[green]Switch '{settings.Name}' renamed to '{settings.NewName}'.[/]");
+        return 0;
+    }
+}

+ 29 - 0
Shared.Rcl/Commands/Systems/Rename/SystemRenameCommand.cs

@@ -0,0 +1,29 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.SystemResources;
+using RackPeek.Domain.UseCases;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace Shared.Rcl.Commands.Systems.Rename;
+
+public class SystemRenameSettings : SystemNameSettings {
+    [CommandArgument(1, "<new-name>")]
+    public string NewName { get; set; } = default!;
+}
+
+public class SystemRenameCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<SystemRenameSettings> {
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        SystemRenameSettings settings,
+        CancellationToken cancellationToken) {
+        using IServiceScope scope = serviceProvider.CreateScope();
+        IRenameResourceUseCase<SystemResource> renameUseCase = scope.ServiceProvider.GetRequiredService<IRenameResourceUseCase<SystemResource>>();
+
+        await renameUseCase.ExecuteAsync(settings.Name, settings.NewName);
+
+        AnsiConsole.MarkupLine($"[green]System '{settings.Name}' renamed to '{settings.NewName}'.[/]");
+        return 0;
+    }
+}

+ 30 - 0
Shared.Rcl/Commands/Ups/Rename/UpsRenameCommand.cs

@@ -0,0 +1,30 @@
+using Microsoft.Extensions.DependencyInjection;
+using RackPeek.Domain.Resources.UpsUnits;
+using RackPeek.Domain.UseCases;
+using Spectre.Console;
+using Spectre.Console.Cli;
+using UpsResource = RackPeek.Domain.Resources.UpsUnits.Ups;
+
+namespace Shared.Rcl.Commands.Ups.Rename;
+
+public class UpsRenameSettings : UpsNameSettings {
+    [CommandArgument(1, "<new-name>")]
+    public string NewName { get; set; } = default!;
+}
+
+public class UpsRenameCommand(
+    IServiceProvider serviceProvider
+) : AsyncCommand<UpsRenameSettings> {
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        UpsRenameSettings settings,
+        CancellationToken cancellationToken) {
+        using IServiceScope scope = serviceProvider.CreateScope();
+        IRenameResourceUseCase<UpsResource> renameUseCase = scope.ServiceProvider.GetRequiredService<IRenameResourceUseCase<UpsResource>>();
+
+        await renameUseCase.ExecuteAsync(settings.Name, settings.NewName);
+
+        AnsiConsole.MarkupLine($"[green]UPS '{settings.Name}' renamed to '{settings.NewName}'.[/]");
+        return 0;
+    }
+}

+ 12 - 0
Tests/EndToEnd/AccessPointTests/AccessPointCommandTests.cs

@@ -44,5 +44,17 @@ public class AccessPointCommandTests(TempYamlCliFixture fs, ITestOutputHelper ou
 
         (var describeHelp, var _) = await ExecuteAsync("accesspoints", "describe", "--help");
         Assert.Contains("Show detailed information", describeHelp);
+        (var renameHelp, var _) = await ExecuteAsync("accesspoints", "rename", "--help");
+        Assert.Contains("Rename an access point", renameHelp);
+    }
+
+    [Fact]
+    public async Task rename_successfully_updates_name() {
+        await ExecuteAsync("accesspoints", "add", "ap01");
+
+        (var output, var yaml) = await ExecuteAsync("accesspoints", "rename", "ap01", "ap01-new");
+
+        Assert.Equal("AccessPoint 'ap01' renamed to 'ap01-new'.\n", output);
+        Assert.Contains("name: ap01-new", yaml);
     }
 }

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

@@ -0,0 +1,170 @@
+using Tests.EndToEnd.Infra;
+using Xunit.Abstractions;
+
+namespace Tests.EndToEnd.ConnectionTests;
+
+[Collection("Yaml CLI tests")]
+public class RenameResourceTests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)
+    : IClassFixture<TempYamlCliFixture> {
+
+    private async Task<(string output, string yaml)> ExecuteAsync(params string[] args) {
+        outputHelper.WriteLine($"rpk {string.Join(" ", args)}");
+
+        var output = await YamlCliTestHost.RunAsync(
+            args,
+            fs.Root,
+            outputHelper,
+            "config.yaml");
+
+        outputHelper.WriteLine(output);
+
+        var yaml = await File.ReadAllTextAsync(Path.Combine(fs.Root, "config.yaml"));
+        return (output, yaml);
+    }
+
+    [Fact]
+    public async Task rename_server_with_single_connection_preserves_connection() {
+        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", "uplink-test");
+
+        await ExecuteAsync("servers", "rename", "srv01", "srv01-renamed");
+
+        (_, var yaml) = await ExecuteAsync("servers", "get", "srv01-renamed");
+
+        Assert.Contains("name: srv01-renamed", yaml);
+        Assert.Contains("srv01-renamed", yaml);
+        Assert.Contains("srv02", yaml);
+        Assert.Contains("uplink-test", yaml);
+    }
+
+    [Fact]
+    public async Task rename_server_with_multiple_connections_preserves_all() {
+        await ExecuteAsync("servers", "add", "srv01");
+        await ExecuteAsync("servers", "add", "srv02");
+        await ExecuteAsync("servers", "add", "srv03");
+        await ExecuteAsync("servers", "add", "srv04");
+
+        foreach (var s in new[] { "srv01", "srv02", "srv03", "srv04" }) {
+            await ExecuteAsync("servers", "nic", "add", s,
+                "--type", "RJ45", "--speed", "10", "--ports", "2");
+        }
+
+        await ExecuteAsync("connections", "add",
+            "srv01", "0", "0",
+            "srv02", "0", "0",
+            "--label", "conn-to-srv02");
+
+        await ExecuteAsync("connections", "add",
+            "srv01", "0", "1",
+            "srv03", "0", "0",
+            "--label", "conn-to-srv03");
+
+        await ExecuteAsync("connections", "add",
+            "srv02", "0", "1",   // changed
+            "srv04", "0", "1",
+            "--label", "conn-to-srv04");
+
+        await ExecuteAsync("servers", "rename", "srv01", "srv01-updated");
+
+        var yaml = await File.ReadAllTextAsync(Path.Combine(fs.Root, "config.yaml"));
+
+        Assert.Contains("name: srv01-updated", yaml);
+        Assert.Contains("srv01-updated", yaml);
+        Assert.Contains("srv02", yaml);
+        Assert.Contains("srv03", yaml);
+        Assert.Contains("srv04", yaml);
+        Assert.Contains("conn-to-srv02", yaml);
+        Assert.Contains("conn-to-srv03", yaml);
+        Assert.Contains("conn-to-srv04", yaml);
+    }
+
+    [Fact]
+    public async Task rename_both_connection_endpoints_preserves_connection() {
+        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", "bi-directional-link");
+
+        await ExecuteAsync("servers", "rename", "srv01", "new-srv01");
+        await ExecuteAsync("servers", "rename", "srv02", "new-srv02");
+
+        (_, var yaml) = await ExecuteAsync("servers", "get", "new-srv01");
+
+        Assert.Contains("name: new-srv01", yaml);
+        Assert.Contains("new-srv01", yaml);
+        Assert.Contains("new-srv02", yaml);
+        Assert.Contains("bi-directional-link", yaml);
+    }
+
+    [Fact]
+    public async Task rename_switch_with_connections_preserves_connections() {
+        await ExecuteAsync("switches", "add", "sw01");
+        await ExecuteAsync("switches", "add", "sw02");
+
+        await ExecuteAsync("switches", "port", "add", "sw01",
+            "--type", "SFP+", "--speed", "25", "--count", "2");
+
+        await ExecuteAsync("switches", "port", "add", "sw02",
+            "--type", "SFP+", "--speed", "25", "--count", "2");
+
+        await ExecuteAsync("connections", "add",
+            "sw01", "0", "0",
+            "sw02", "0", "0",
+            "--label", "switch-uplink");
+
+        await ExecuteAsync("switches", "rename", "sw01", "sw01-core");
+
+        (_, var yaml) = await ExecuteAsync("switches", "get", "sw01-core");
+
+        Assert.Contains("name: sw01-core", yaml);
+        Assert.Contains("sw01-core", yaml);
+        Assert.Contains("sw02", yaml);
+        Assert.Contains("switch-uplink", yaml);
+    }
+
+    [Fact]
+    public async Task rename_with_special_naming_preserves_connections() {
+        await ExecuteAsync("servers", "add", "srv-prod-web-01");
+        await ExecuteAsync("servers", "add", "srv-prod-app-01");
+
+        await ExecuteAsync("servers", "nic", "add", "srv-prod-web-01",
+            "--type", "RJ45", "--speed", "10", "--ports", "2");
+
+        await ExecuteAsync("servers", "nic", "add", "srv-prod-app-01",
+            "--type", "RJ45", "--speed", "10", "--ports", "2");
+
+        await ExecuteAsync("connections", "add",
+            "srv-prod-web-01", "0", "0",
+            "srv-prod-app-01", "0", "0",
+            "--label", "app-backend-link");
+
+        await ExecuteAsync("servers", "rename", "srv-prod-web-01", "srv_prod_web_01");
+
+        (_, var yaml) = await ExecuteAsync("servers", "get", "srv_prod_web_01");
+
+        Assert.Contains("name: srv_prod_web_01", yaml);
+        Assert.Contains("srv_prod_web_01", yaml);
+        Assert.Contains("srv-prod-app-01", yaml);
+        Assert.Contains("app-backend-link", yaml);
+    }
+}

+ 190 - 0
Tests/EndToEnd/DeleteResourceTests.cs

@@ -0,0 +1,190 @@
+using Tests.EndToEnd.Infra;
+using Xunit.Abstractions;
+
+namespace Tests.EndToEnd;
+
+[Collection("Yaml CLI tests")]
+public class DeleteResourceTests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)
+    : IClassFixture<TempYamlCliFixture> {
+    private async Task<(string output, string yaml)> ExecuteAsync(params string[] args) {
+        outputHelper.WriteLine($"rpk {string.Join(" ", args)}");
+
+        var output = await YamlCliTestHost.RunAsync(
+            args,
+            fs.Root,
+            outputHelper,
+            "config.yaml");
+
+        outputHelper.WriteLine(output);
+
+        var yaml = await File.ReadAllTextAsync(Path.Combine(fs.Root, "config.yaml"));
+        return (output, yaml);
+    }
+
+    [Fact]
+    public async Task deleting_resource_removes_connections_from_endpoint_a() {
+        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", "test-connection");
+
+        (var output, var yaml) = await ExecuteAsync("servers", "del", "srv01");
+
+        Assert.Contains("Server 'srv01' deleted.", output);
+        // Connection referencing deleted resource is removed
+        Assert.DoesNotContain("test-connection", yaml);
+        // srv02 should still exist
+        Assert.Contains("srv02", yaml);
+        Assert.DoesNotContain("srv01", yaml);
+    }
+
+    [Fact]
+    public async Task deleting_resource_removes_connections_from_endpoint_b() {
+        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", "test-connection");
+
+        (var output, var yaml) = await ExecuteAsync("servers", "del", "srv02");
+
+        Assert.Contains("Server 'srv02' deleted.", output);
+        // Connection referencing deleted resource is removed
+        Assert.DoesNotContain("test-connection", yaml);
+        // srv01 should still exist
+        Assert.Contains("srv01", yaml);
+        Assert.DoesNotContain("srv02", yaml);
+    }
+
+    [Fact]
+    public async Task deleting_resource_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);
+        // System should still exist but without runs-on reference
+        Assert.Contains("sys01", yaml);
+        // The runs-on reference should be removed from the system
+        Assert.DoesNotContain("srv01", yaml);
+    }
+
+    [Fact]
+    public async Task deleting_resource_with_multiple_connections_removes_all() {
+        await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), "");
+        await ExecuteAsync("switches", "add", "sw01");
+        await ExecuteAsync("switches", "add", "sw02");
+        await ExecuteAsync("switches", "add", "sw03");
+
+        await ExecuteAsync(
+            "switches", "port", "add", "sw01",
+            "--type", "SFP+",
+            "--speed", "25",
+            "--count", "3");
+
+        await ExecuteAsync(
+            "switches", "port", "add", "sw02",
+            "--type", "SFP+",
+            "--speed", "25",
+            "--count", "2");
+
+        await ExecuteAsync(
+            "switches", "port", "add", "sw03",
+            "--type", "SFP+",
+            "--speed", "25",
+            "--count", "2");
+
+        await ExecuteAsync(
+            "connections", "add",
+            "sw01", "0", "0",
+            "sw02", "0", "0",
+            "--label", "sw01-to-sw02");
+
+        await ExecuteAsync(
+            "connections", "add",
+            "sw01", "0", "1",
+            "sw03", "0", "0",
+            "--label", "sw01-to-sw03");
+
+        (var output, var yaml) = await ExecuteAsync("switches", "del", "sw01");
+
+        Assert.Contains("Switch 'sw01' deleted.", output);
+        Assert.Contains("sw02", yaml);
+        Assert.Contains("sw03", yaml);
+        // Both connections referencing sw01 should be removed
+        Assert.DoesNotContain("sw01-to-sw02", yaml);
+        Assert.DoesNotContain("sw01-to-sw03", yaml);
+    }
+
+    [Fact]
+    public async Task deleting_resource_removes_connection_when_both_endpoints_referenced() {
+        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", "bi-directional-link");
+
+        await ExecuteAsync("servers", "del", "srv01");
+
+        var yaml = await File.ReadAllTextAsync(Path.Combine(fs.Root, "config.yaml"));
+        // srv02 should remain
+        Assert.Contains("srv02", yaml);
+        // Connection referencing deleted resource should be removed
+        Assert.DoesNotContain("srv01", yaml);
+        // Connection label should be gone since the connection is removed
+        Assert.DoesNotContain("bi-directional-link", yaml);
+    }
+}

+ 11 - 0
Tests/EndToEnd/DesktopTests/DesktopCommandTests.cs

@@ -58,5 +58,16 @@ public class DesktopCommandTests(TempYamlCliFixture fs, ITestOutputHelper output
 
         Assert.Contains("Manage network interface cards", (await ExecuteAsync("desktops", "nic", "--help")).Item1);
         Assert.Contains("Add a NIC", (await ExecuteAsync("desktops", "nic", "add", "--help")).Item1);
+        Assert.Contains("Rename a desktop", (await ExecuteAsync("desktops", "rename", "--help")).Item1);
+    }
+
+    [Fact]
+    public async Task rename_successfully_updates_name() {
+        await ExecuteAsync("desktops", "add", "workstation01");
+
+        (var output, var yaml) = await ExecuteAsync("desktops", "rename", "workstation01", "workstation01-new");
+
+        Assert.Equal("Desktop 'workstation01' renamed to 'workstation01-new'.\n", output);
+        Assert.Contains("name: workstation01-new", yaml);
     }
 }

+ 11 - 0
Tests/EndToEnd/FirewallTests/FirewallCommandTests.cs

@@ -47,5 +47,16 @@ public class FirewallCommandTests(TempYamlCliFixture fs, ITestOutputHelper outpu
         Assert.Contains("Add a port", (await ExecuteAsync("firewalls", "port", "add", "--help")).Item1);
         Assert.Contains("Update a firewall port", (await ExecuteAsync("firewalls", "port", "set", "--help")).Item1);
         Assert.Contains("Remove a port", (await ExecuteAsync("firewalls", "port", "del", "--help")).Item1);
+        Assert.Contains("Rename a firewall", (await ExecuteAsync("firewalls", "rename", "--help")).Item1);
+    }
+
+    [Fact]
+    public async Task rename_successfully_updates_name() {
+        await ExecuteAsync("firewalls", "add", "fw01");
+
+        (var output, var yaml) = await ExecuteAsync("firewalls", "rename", "fw01", "fw01-new");
+
+        Assert.Equal("Firewall 'fw01' renamed to 'fw01-new'.\n", output);
+        Assert.Contains("name: fw01-new", yaml);
     }
 }

+ 11 - 0
Tests/EndToEnd/LaptopTests/LaptopCommandTests.cs

@@ -49,5 +49,16 @@ public class LaptopCommandTests(TempYamlCliFixture fs, ITestOutputHelper outputH
 
         // GPU help
         Assert.Contains("Manage GPUs", (await ExecuteAsync("laptops", "gpu", "--help")).Item1);
+        Assert.Contains("Rename a Laptop", (await ExecuteAsync("laptops", "rename", "--help")).Item1);
+    }
+
+    [Fact]
+    public async Task rename_successfully_updates_name() {
+        await ExecuteAsync("laptops", "add", "lap01");
+
+        (var output, var yaml) = await ExecuteAsync("laptops", "rename", "lap01", "lap01-new");
+
+        Assert.Equal("Laptop 'lap01' renamed to 'lap01-new'.\n", output);
+        Assert.Contains("name: lap01-new", yaml);
     }
 }

+ 11 - 0
Tests/EndToEnd/RouterTests/RouterCommandTests.cs

@@ -46,5 +46,16 @@ public class RouterCommandTests(TempYamlCliFixture fs, ITestOutputHelper outputH
         Assert.Contains("Add a port", (await ExecuteAsync("routers", "port", "add", "--help")).Item1);
         Assert.Contains("Update a router port", (await ExecuteAsync("routers", "port", "set", "--help")).Item1);
         Assert.Contains("Remove a port", (await ExecuteAsync("routers", "port", "del", "--help")).Item1);
+        Assert.Contains("Rename a router", (await ExecuteAsync("routers", "rename", "--help")).Item1);
+    }
+
+    [Fact]
+    public async Task rename_successfully_updates_name() {
+        await ExecuteAsync("routers", "add", "rt01");
+
+        (var output, var yaml) = await ExecuteAsync("routers", "rename", "rt01", "rt01-new");
+
+        Assert.Equal("Router 'rt01' renamed to 'rt01-new'.\n", output);
+        Assert.Contains("name: rt01-new", yaml);
     }
 }

+ 25 - 0
Tests/EndToEnd/ServerTests/ServerCommandTests.cs

@@ -42,5 +42,30 @@ public class ServerCommandTests(TempYamlCliFixture fs, ITestOutputHelper outputH
         Assert.Contains("Manage drives", (await ExecuteAsync("servers", "drive", "--help")).output);
         Assert.Contains("Manage GPUs", (await ExecuteAsync("servers", "gpu", "--help")).output);
         Assert.Contains("Manage network interface cards", (await ExecuteAsync("servers", "nic", "--help")).output);
+        Assert.Contains("Rename a server", (await ExecuteAsync("servers", "rename", "--help")).output);
+    }
+
+    [Fact]
+    public async Task rename_successfully_updates_name() {
+        await ExecuteAsync("servers", "add", "srv01");
+        await ExecuteAsync("servers", "set", "srv01", "--ram", "64");
+
+        (var output, var yaml) = await ExecuteAsync("servers", "rename", "srv01", "srv01-new");
+
+        Assert.Equal("Server 'srv01' renamed to 'srv01-new'.\n", output);
+        Assert.Contains("name: srv01-new", yaml);
+    }
+
+    [Fact]
+    public async Task rename_updates_dependants() {
+        await ExecuteAsync("servers", "add", "srv01");
+        await ExecuteAsync("systems", "add", "sys01", "--runs-on", "srv01");
+
+        (var output, var yaml) = await ExecuteAsync("servers", "rename", "srv01", "srv01-updated");
+
+        (_,  yaml) = await ExecuteAsync("systems", "get", "sys01");
+
+        Assert.Contains("srv01-updated", yaml);
+        Assert.DoesNotContain("srv01\n", yaml);
     }
 }

+ 11 - 0
Tests/EndToEnd/ServiceTests/ServiceCommandTests.cs

@@ -39,5 +39,16 @@ public class ServiceCommandTests(TempYamlCliFixture fs, ITestOutputHelper output
         Assert.Contains("Update properties", (await ExecuteAsync("services", "set", "--help")).output);
         Assert.Contains("Delete a service", (await ExecuteAsync("services", "del", "--help")).output);
         Assert.Contains("List subnets", (await ExecuteAsync("services", "subnets", "--help")).output);
+        Assert.Contains("Rename a service", (await ExecuteAsync("services", "rename", "--help")).output);
+    }
+
+    [Fact]
+    public async Task rename_successfully_updates_name() {
+        await ExecuteAsync("services", "add", "svc01");
+
+        (var output, var yaml) = await ExecuteAsync("services", "rename", "svc01", "svc01-new");
+
+        Assert.Equal("Service 'svc01' renamed to 'svc01-new'.\n", output);
+        Assert.Contains("name: svc01-new", yaml);
     }
 }

+ 11 - 0
Tests/EndToEnd/SwitchTests/SwitchCommandTests.cs

@@ -46,5 +46,16 @@ public class SwitchCommandTests(TempYamlCliFixture fs, ITestOutputHelper outputH
         Assert.Contains("Add a port", (await ExecuteAsync("switches", "port", "add", "--help")).Item1);
         Assert.Contains("Update a switch port", (await ExecuteAsync("switches", "port", "set", "--help")).Item1);
         Assert.Contains("Remove a port", (await ExecuteAsync("switches", "port", "del", "--help")).Item1);
+        Assert.Contains("Rename a switch", (await ExecuteAsync("switches", "rename", "--help")).Item1);
+    }
+
+    [Fact]
+    public async Task rename_successfully_updates_name() {
+        await ExecuteAsync("switches", "add", "sw01");
+
+        (var output, var yaml) = await ExecuteAsync("switches", "rename", "sw01", "sw01-new");
+
+        Assert.Equal("Switch 'sw01' renamed to 'sw01-new'.\n", output);
+        Assert.Contains("name: sw01-new", yaml);
     }
 }

+ 11 - 0
Tests/EndToEnd/SystemTests/SystemCommandTests.cs

@@ -42,5 +42,16 @@ public class SystemCommandTests(TempYamlCliFixture fs, ITestOutputHelper outputH
         Assert.Contains("Update properties", (await ExecuteAsync("systems", "set", "--help")).Item1);
         Assert.Contains("Delete a system", (await ExecuteAsync("systems", "del", "--help")).Item1);
         Assert.Contains("Display the dependency tree", (await ExecuteAsync("systems", "tree", "--help")).Item1);
+        Assert.Contains("Rename a system", (await ExecuteAsync("systems", "rename", "--help")).Item1);
+    }
+
+    [Fact]
+    public async Task rename_successfully_updates_name() {
+        await ExecuteAsync("systems", "add", "sys01");
+
+        (var output, var yaml) = await ExecuteAsync("systems", "rename", "sys01", "sys01-new");
+
+        Assert.Equal("System 'sys01' renamed to 'sys01-new'.\n", output);
+        Assert.Contains("name: sys01-new", yaml);
     }
 }

+ 12 - 0
Tests/EndToEnd/UpsTests/UpsCommandTests.cs

@@ -53,5 +53,17 @@ public class UpsCommandTests(TempYamlCliFixture fs, ITestOutputHelper outputHelp
 
         (var delHelp, var _) = await ExecuteAsync("ups", "del", "--help");
         Assert.Contains("Delete a UPS unit", delHelp);
+        (var renameHelp, var _) = await ExecuteAsync("ups", "rename", "--help");
+        Assert.Contains("Rename a UPS unit", renameHelp);
+    }
+
+    [Fact]
+    public async Task rename_successfully_updates_name() {
+        await ExecuteAsync("ups", "add", "ups01");
+
+        (var output, var yaml) = await ExecuteAsync("ups", "rename", "ups01", "ups01-new");
+
+        Assert.Equal("UPS 'ups01' renamed to 'ups01-new'.\n", output);
+        Assert.Contains("name: ups01-new", yaml);
     }
 }