Browse Source

Deleting a resource now clears it's child's 'runson' property

Tim Jones 2 months ago
parent
commit
127ceb7e25

+ 12 - 2
RackPeek.Domain/Resources/Hardware/AccessPoints/DeleteAccessPointUseCase.cs

@@ -1,17 +1,27 @@
 using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
+using RackPeek.Domain.Resources.SystemResources;
 
 namespace RackPeek.Domain.Resources.Hardware.AccessPoints;
 
-public class DeleteAccessPointUseCase(IHardwareRepository repository) : IUseCase
+public class DeleteAccessPointUseCase(IHardwareRepository repository, ISystemRepository systemsRepo) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
         ThrowIfInvalid.ResourceName(name);
 
-        if (await repository.GetByNameAsync(name) is not AccessPoint ap)
+        var ap = await repository.GetByNameAsync(name);
+        if (ap is not AccessPoint)
             throw new NotFoundException($"Access point '{name}' not found.");
 
+        // Break link to dependants
+        var dependants = await systemsRepo.GetByPhysicalHostAsync(name);
+        foreach (var systemResource in dependants)
+        {
+            systemResource.RunsOn = null;
+            await systemsRepo.UpdateAsync(systemResource);
+        }
+        
         await repository.DeleteAsync(name);
     }
 }

+ 10 - 1
RackPeek.Domain/Resources/Hardware/Desktops/DeleteDesktopUseCase.cs

@@ -1,8 +1,9 @@
 using RackPeek.Domain.Helpers;
+using RackPeek.Domain.Resources.SystemResources;
 
 namespace RackPeek.Domain.Resources.Hardware.Desktops;
 
-public class DeleteDesktopUseCase(IHardwareRepository repository) : IUseCase
+public class DeleteDesktopUseCase(IHardwareRepository repository, ISystemRepository systemsRepo) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
@@ -12,6 +13,14 @@ public class DeleteDesktopUseCase(IHardwareRepository repository) : IUseCase
         if (hardware == null)
             throw new NotFoundException($"Desktop '{name}' not found.");
 
+        // Break link to dependants
+        var dependants = await systemsRepo.GetByPhysicalHostAsync(name);
+        foreach (var systemResource in dependants)
+        {
+            systemResource.RunsOn = null;
+            await systemsRepo.UpdateAsync(systemResource);
+        }
+        
         await repository.DeleteAsync(name);
     }
 }

+ 10 - 1
RackPeek.Domain/Resources/Hardware/Firewalls/DeleteFirewallUseCase.cs

@@ -1,9 +1,10 @@
 using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
+using RackPeek.Domain.Resources.SystemResources;
 
 namespace RackPeek.Domain.Resources.Hardware.Firewalls;
 
-public class DeleteFirewallUseCase(IHardwareRepository repository) : IUseCase
+public class DeleteFirewallUseCase(IHardwareRepository repository, ISystemRepository systemsRepo) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
@@ -12,6 +13,14 @@ public class DeleteFirewallUseCase(IHardwareRepository repository) : IUseCase
         if (await repository.GetByNameAsync(name) is not Firewall hardware)
             throw new NotFoundException($"Firewall '{name}' not found.");
 
+        // Break link to dependants
+        var dependants = await systemsRepo.GetByPhysicalHostAsync(name);
+        foreach (var systemResource in dependants)
+        {
+            systemResource.RunsOn = null;
+            await systemsRepo.UpdateAsync(systemResource);
+        }
+        
         await repository.DeleteAsync(name);
     }
 }

+ 11 - 2
RackPeek.Domain/Resources/Hardware/Laptops/DeleteLaptopUseCase.cs

@@ -1,8 +1,9 @@
 using RackPeek.Domain.Helpers;
+using RackPeek.Domain.Resources.SystemResources;
 
 namespace RackPeek.Domain.Resources.Hardware.Laptops;
 
-public class DeleteLaptopUseCase(IHardwareRepository repository) : IUseCase
+public class DeleteLaptopUseCase(IHardwareRepository repository, ISystemRepository systemsRepo) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
@@ -10,8 +11,16 @@ public class DeleteLaptopUseCase(IHardwareRepository repository) : IUseCase
 
         var hardware = await repository.GetByNameAsync(name);
         if (hardware == null)
-            throw new InvalidOperationException($"Laptop '{name}' not found.");
+            throw new NotFoundException($"Laptop '{name}' not found.");
 
+        // Break link to dependants
+        var dependants = await systemsRepo.GetByPhysicalHostAsync(name);
+        foreach (var systemResource in dependants)
+        {
+            systemResource.RunsOn = null;
+            await systemsRepo.UpdateAsync(systemResource);
+        }
+        
         await repository.DeleteAsync(name);
     }
 }

+ 10 - 1
RackPeek.Domain/Resources/Hardware/Routers/DeleteRouterUseCase.cs

@@ -1,9 +1,10 @@
 using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
+using RackPeek.Domain.Resources.SystemResources;
 
 namespace RackPeek.Domain.Resources.Hardware.Routers;
 
-public class DeleteRouterUseCase(IHardwareRepository repository) : IUseCase
+public class DeleteRouterUseCase(IHardwareRepository repository, ISystemRepository systemsRepo) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
@@ -12,6 +13,14 @@ public class DeleteRouterUseCase(IHardwareRepository repository) : IUseCase
         if (await repository.GetByNameAsync(name) is not Router hardware)
             throw new NotFoundException($"Router '{name}' not found.");
 
+        // Break link to dependants
+        var dependants = await systemsRepo.GetByPhysicalHostAsync(name);
+        foreach (var systemResource in dependants)
+        {
+            systemResource.RunsOn = null;
+            await systemsRepo.UpdateAsync(systemResource);
+        }
+        
         await repository.DeleteAsync(name);
     }
 }

+ 10 - 1
RackPeek.Domain/Resources/Hardware/Servers/DeleteServerUseCase.cs

@@ -1,9 +1,10 @@
 using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
+using RackPeek.Domain.Resources.SystemResources;
 
 namespace RackPeek.Domain.Resources.Hardware.Servers;
 
-public class DeleteServerUseCase(IHardwareRepository repository) : IUseCase
+public class DeleteServerUseCase(IHardwareRepository repository, ISystemRepository systemsRepo) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
@@ -13,6 +14,14 @@ public class DeleteServerUseCase(IHardwareRepository repository) : IUseCase
         if (hardware == null)
             throw new NotFoundException($"Server '{name}' not found.");
 
+        // Break link to dependants
+        var dependants = await systemsRepo.GetByPhysicalHostAsync(name);
+        foreach (var systemResource in dependants)
+        {
+            systemResource.RunsOn = null;
+            await systemsRepo.UpdateAsync(systemResource);
+        }
+        
         await repository.DeleteAsync(name);
     }
 }

+ 11 - 2
RackPeek.Domain/Resources/Hardware/Switches/DeleteSwitchUseCase.cs

@@ -1,17 +1,26 @@
 using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
+using RackPeek.Domain.Resources.SystemResources;
 
 namespace RackPeek.Domain.Resources.Hardware.Switches;
 
-public class DeleteSwitchUseCase(IHardwareRepository repository) : IUseCase
+public class DeleteSwitchUseCase(IHardwareRepository repository, ISystemRepository systemsRepo) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
         ThrowIfInvalid.ResourceName(name);
 
         if (await repository.GetByNameAsync(name) is not Switch hardware)
-            throw new InvalidOperationException($"Switch '{name}' not found.");
+            throw new NotFoundException($"Switch '{name}' not found.");
 
+        // Break link to dependants
+        var dependants = await systemsRepo.GetByPhysicalHostAsync(name);
+        foreach (var systemResource in dependants)
+        {
+            systemResource.RunsOn = null;
+            await systemsRepo.UpdateAsync(systemResource);
+        }
+        
         await repository.DeleteAsync(name);
     }
 }

+ 11 - 2
RackPeek.Domain/Resources/Hardware/UpsUnits/DeleteUpsUseCase.cs

@@ -1,17 +1,26 @@
 using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
+using RackPeek.Domain.Resources.SystemResources;
 
 namespace RackPeek.Domain.Resources.Hardware.UpsUnits;
 
-public class DeleteUpsUseCase(IHardwareRepository repository) : IUseCase
+public class DeleteUpsUseCase(IHardwareRepository repository, ISystemRepository systemsRepo) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
         ThrowIfInvalid.ResourceName(name);
 
         if (await repository.GetByNameAsync(name) is not Ups ups)
-            throw new InvalidOperationException($"UPS '{name}' not found.");
+            throw new NotFoundException($"UPS '{name}' not found.");
 
+        // Break link to dependants
+        var dependants = await systemsRepo.GetByPhysicalHostAsync(name);
+        foreach (var systemResource in dependants)
+        {
+            systemResource.RunsOn = null;
+            await systemsRepo.UpdateAsync(systemResource);
+        }
+        
         await repository.DeleteAsync(name);
     }
 }

+ 13 - 3
RackPeek.Domain/Resources/SystemResources/UseCases/DeleteSystemUseCase.cs

@@ -1,15 +1,25 @@
 using RackPeek.Domain.Helpers;
+using RackPeek.Domain.Resources.Services;
 
 namespace RackPeek.Domain.Resources.SystemResources.UseCases;
 
-public class DeleteSystemUseCase(ISystemRepository repository) : IUseCase
+public class DeleteSystemUseCase(ISystemRepository repository, IServiceRepository serviceRepo) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
         ThrowIfInvalid.ResourceName(name);
+        
         if (await repository.GetByNameAsync(name) is not SystemResource)
-            throw new InvalidOperationException($"System '{name}' not found.");
-
+            throw new NotFoundException($"System '{name}' not found.");
+        
+        // Break link to dependants
+        var dependants = await serviceRepo.GetBySystemHostAsync(name);
+        foreach (var serviceResource in dependants)
+        {
+            serviceResource.RunsOn = null;
+            await serviceRepo.UpdateAsync(serviceResource);
+        }
+        
         await repository.DeleteAsync(name);
     }
 }

+ 2 - 2
Tests/HardwareResources/AccessPoints/AddAccessPointUseCaseTests.cs

@@ -1,4 +1,5 @@
 using NSubstitute;
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware;
 using RackPeek.Domain.Resources.Hardware.AccessPoints;
 using RackPeek.Domain.Resources.Hardware.Models;
@@ -35,12 +36,11 @@ public class AddAccessPointUseCaseTests
         var sut = new AddAccessPointUseCase(repo);
 
         // Act
-        var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
+        var ex = await Assert.ThrowsAsync<ConflictException>(async () =>
             await sut.ExecuteAsync("ap01")
         );
 
         // Assert
-        Assert.Equal("Access point 'ap01' already exists.", ex.Message);
         await repo.DidNotReceive().AddAsync(Arg.Any<AccessPoint>());
     }
 }

+ 8 - 5
Tests/HardwareResources/AccessPoints/DeleteAccessPointUseCaseTests.cs

@@ -1,4 +1,5 @@
 using NSubstitute;
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware;
 using RackPeek.Domain.Resources.Hardware.AccessPoints;
 using RackPeek.Domain.Resources.Hardware.Models;
@@ -11,10 +12,11 @@ public class DeleteAccessPointUseCaseTests
     public async Task ExecuteAsync_Deletes_ap_when_exists()
     {
         // Arrange
-        var repo = Substitute.For<IHardwareRepository>();
+        var host = new UsecaseTestHost();
+        var repo = host.HardwareRepo;
         repo.GetByNameAsync("ap01").Returns(new AccessPoint { Name = "ap01" });
 
-        var sut = new DeleteAccessPointUseCase(repo);
+        var sut = host.Get<DeleteAccessPointUseCase>();
 
         // Act
         await sut.ExecuteAsync("ap01");
@@ -27,13 +29,14 @@ public class DeleteAccessPointUseCaseTests
     public async Task ExecuteAsync_Throws_if_ap_not_found()
     {
         // Arrange
-        var repo = Substitute.For<IHardwareRepository>();
+        var host = new UsecaseTestHost();
+        var repo = host.HardwareRepo;
         repo.GetByNameAsync("ap01").Returns((Hardware?)null);
 
-        var sut = new DeleteAccessPointUseCase(repo);
+        var sut = host.Get<DeleteAccessPointUseCase>();
 
         // Act
-        var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
+        var ex = await Assert.ThrowsAsync<NotFoundException>(async () =>
             await sut.ExecuteAsync("ap01")
         );
 

+ 2 - 1
Tests/HardwareResources/AccessPoints/UpdateAccessPointUseCaseTests.cs

@@ -1,4 +1,5 @@
 using NSubstitute;
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware;
 using RackPeek.Domain.Resources.Hardware.AccessPoints;
 using RackPeek.Domain.Resources.Hardware.Models;
@@ -17,7 +18,7 @@ public class UpdateAccessPointUseCaseTests
         var sut = new UpdateAccessPointUseCase(repo);
 
         // Act
-        var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
+        var ex = await Assert.ThrowsAsync<NotFoundException>(async () =>
             await sut.ExecuteAsync("ap01")
         );
 

+ 36 - 6
Tests/HardwareResources/DeleteServerUseCaseTests.cs

@@ -1,37 +1,67 @@
+using Microsoft.Extensions.DependencyInjection;
 using NSubstitute;
+using NSubstitute.ClearExtensions;
 using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware;
 using RackPeek.Domain.Resources.Hardware.Models;
 using RackPeek.Domain.Resources.Hardware.Servers;
+using RackPeek.Domain.Resources.Services;
+using RackPeek.Domain.Resources.SystemResources;
 
 namespace Tests.HardwareResources;
 
+public class UsecaseTestHost
+{
+    public IHardwareRepository HardwareRepo { get; set; }
+    public ISystemRepository SystemRepo { get; set; }
+    public IServiceRepository ServiceRepo { get; set; }
+
+    private readonly ServiceCollection _sc;
+    public UsecaseTestHost()
+    {
+        HardwareRepo = Substitute.For<IHardwareRepository>();
+        SystemRepo = Substitute.For<ISystemRepository>();
+        ServiceRepo = Substitute.For<IServiceRepository>();
+        _sc = new ServiceCollection();
+        _sc.AddSingleton<IHardwareRepository>(HardwareRepo);
+        _sc.AddSingleton<ISystemRepository>(SystemRepo);
+        _sc.AddSingleton<IServiceRepository>(ServiceRepo);
+    }
+    public T Get<T>() where T : notnull
+    {
+        _sc.AddSingleton(typeof(T));
+        var sp = _sc.BuildServiceProvider();
+        return sp.GetRequiredService<T>();
+    }
+}
+
 public class DeleteServerUseCaseTests
 {
     [Fact]
     public async Task ExecuteAsync_Deletes_server_when_exists()
     {
         // Arrange
-        var repo = Substitute.For<IHardwareRepository>();
-        repo.GetByNameAsync("node01").Returns(new Server { Name = "node01" });
+        var host = new UsecaseTestHost();
+        host.HardwareRepo.GetByNameAsync("node01").Returns(new Server { Name = "node01" });
 
-        var sut = new DeleteServerUseCase(repo);
+        var sut = host.Get<DeleteServerUseCase>();
 
         // Act
         await sut.ExecuteAsync("node01");
 
         // Assert
-        await repo.Received(1).DeleteAsync("node01");
+        await host.HardwareRepo.Received(1).DeleteAsync("node01");
     }
 
     [Fact]
     public async Task ExecuteAsync_Throws_when_server_not_found()
     {
         // Arrange
-        var repo = Substitute.For<IHardwareRepository>();
+        var host = new UsecaseTestHost();
+        var repo = host.HardwareRepo;
         repo.GetByNameAsync("node01").Returns((Hardware?)null);
 
-        var sut = new DeleteServerUseCase(repo);
+        var sut = host.Get<DeleteServerUseCase>();
 
         // Act
         var ex = await Assert.ThrowsAsync<NotFoundException>(() =>

+ 2 - 1
Tests/HardwareResources/Desktops/AddDesktopUseCaseTests.cs

@@ -1,4 +1,5 @@
 using NSubstitute;
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware;
 using RackPeek.Domain.Resources.Hardware.Desktops;
 using RackPeek.Domain.Resources.Hardware.Models;
@@ -28,6 +29,6 @@ public class AddDesktopUseCaseTests
 
         var useCase = new AddDesktopUseCase(repo);
 
-        await Assert.ThrowsAsync<InvalidOperationException>(() => useCase.ExecuteAsync("desk1"));
+        await Assert.ThrowsAsync<ConflictException>(() => useCase.ExecuteAsync("desk1"));
     }
 }

+ 9 - 6
Tests/HardwareResources/Desktops/DeleteDesktopUseCaseTests.cs

@@ -1,4 +1,5 @@
 using NSubstitute;
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware;
 using RackPeek.Domain.Resources.Hardware.Desktops;
 using RackPeek.Domain.Resources.Hardware.Models;
@@ -10,12 +11,13 @@ public class DeleteDesktopUseCaseTests
     [Fact]
     public async Task Deletes_Desktop()
     {
-        var repo = Substitute.For<IHardwareRepository>();
+        var host = new UsecaseTestHost();
+        var repo = host.HardwareRepo;
         repo.GetByNameAsync("desk1").Returns(new Desktop { Name = "desk1" });
 
-        var useCase = new DeleteDesktopUseCase(repo);
+        var sut = host.Get<DeleteDesktopUseCase>();
 
-        await useCase.ExecuteAsync("desk1");
+        await sut.ExecuteAsync("desk1");
 
         await repo.Received().DeleteAsync("desk1");
     }
@@ -23,11 +25,12 @@ public class DeleteDesktopUseCaseTests
     [Fact]
     public async Task Throws_If_Not_Found()
     {
-        var repo = Substitute.For<IHardwareRepository>();
+        var host = new UsecaseTestHost();
+        var repo = host.HardwareRepo;
         repo.GetByNameAsync("desk1").Returns((Hardware?)null);
 
-        var useCase = new DeleteDesktopUseCase(repo);
+        var sut = host.Get<DeleteDesktopUseCase>();
 
-        await Assert.ThrowsAsync<InvalidOperationException>(() => useCase.ExecuteAsync("desk1"));
+        await Assert.ThrowsAsync<NotFoundException>(() => sut.ExecuteAsync("desk1"));
     }
 }

+ 2 - 1
Tests/HardwareResources/Desktops/UpdateDesktopUseCaseTests.cs

@@ -1,4 +1,5 @@
 using NSubstitute;
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware;
 using RackPeek.Domain.Resources.Hardware.Desktops;
 using RackPeek.Domain.Resources.Hardware.Models;
@@ -30,7 +31,7 @@ public class UpdateDesktopUseCaseTests
 
         var useCase = new UpdateDesktopUseCase(repo);
 
-        await Assert.ThrowsAsync<InvalidOperationException>(() =>
+        await Assert.ThrowsAsync<NotFoundException>(() =>
             useCase.ExecuteAsync("desk1", "Optiplex"));
     }
 }

+ 8 - 5
Tests/HardwareResources/Switches/DeleteSwitchUseCaseTests.cs

@@ -1,4 +1,5 @@
 using NSubstitute;
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware;
 using RackPeek.Domain.Resources.Hardware.Models;
 using RackPeek.Domain.Resources.Hardware.Switches;
@@ -11,10 +12,11 @@ public class DeleteSwitchUseCaseTests
     public async Task ExecuteAsync_Deletes_switch_when_exists()
     {
         // Arrange
-        var repo = Substitute.For<IHardwareRepository>();
+        var host = new UsecaseTestHost();
+        var repo = host.HardwareRepo;
         repo.GetByNameAsync("sw01").Returns(new Switch { Name = "sw01" });
 
-        var sut = new DeleteSwitchUseCase(repo);
+        var sut = host.Get<DeleteSwitchUseCase>();
 
         // Act
         await sut.ExecuteAsync(
@@ -29,13 +31,14 @@ public class DeleteSwitchUseCaseTests
     public async Task ExecuteAsync_Throws_if_switch_not_found()
     {
         // Arrange
-        var repo = Substitute.For<IHardwareRepository>();
+        var host = new UsecaseTestHost();
+        var repo = host.HardwareRepo;
         repo.GetByNameAsync("sw01").Returns((Hardware?)null);
 
-        var sut = new DeleteSwitchUseCase(repo);
+        var sut = host.Get<DeleteSwitchUseCase>();
 
         // Act
-        var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
+        var ex = await Assert.ThrowsAsync<NotFoundException>(async () =>
             await sut.ExecuteAsync(
                 "sw01"
             )

+ 8 - 5
Tests/HardwareResources/Ups/DeleteUpsUseCaseTests.cs

@@ -1,4 +1,5 @@
 using NSubstitute;
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware;
 using RackPeek.Domain.Resources.Hardware.Models;
 using RackPeek.Domain.Resources.Hardware.UpsUnits;
@@ -10,10 +11,11 @@ public class DeleteUpsUseCaseTests
     [Fact]
     public async Task ExecuteAsync_Deletes_ups_when_exists()
     {
-        var repo = Substitute.For<IHardwareRepository>();
+        var host = new UsecaseTestHost();
+        var repo = host.HardwareRepo;
         repo.GetByNameAsync("ups01").Returns(new RackPeek.Domain.Resources.Hardware.Models.Ups { Name = "ups01" });
 
-        var sut = new DeleteUpsUseCase(repo);
+        var sut = host.Get<DeleteUpsUseCase>();
 
         await sut.ExecuteAsync("ups01");
 
@@ -23,12 +25,13 @@ public class DeleteUpsUseCaseTests
     [Fact]
     public async Task ExecuteAsync_Throws_if_ups_not_found()
     {
-        var repo = Substitute.For<IHardwareRepository>();
+        var host = new UsecaseTestHost();
+        var repo = host.HardwareRepo;
         repo.GetByNameAsync("ups01").Returns((Hardware?)null);
 
-        var sut = new DeleteUpsUseCase(repo);
+        var sut = host.Get<DeleteUpsUseCase>();
 
-        var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
+        var ex = await Assert.ThrowsAsync<NotFoundException>(async () =>
             await sut.ExecuteAsync("ups01")
         );