Просмотр исходного кода

Merge pull request #133 from Timmoth/Testing-Improvements

Testing improvements
Tim Jones 1 месяц назад
Родитель
Сommit
438426c20e

+ 1 - 1
RackPeek/RackPeek.csproj

@@ -5,7 +5,7 @@
         <TargetFramework>net10.0</TargetFramework>
         <ImplicitUsings>enable</ImplicitUsings>
         <Nullable>enable</Nullable>
-        <AssemblyVersion>v0.0.11</AssemblyVersion>
+        <AssemblyVersion>0.0.11</AssemblyVersion>
     </PropertyGroup>
 
     <ItemGroup>

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

@@ -0,0 +1,52 @@
+using Tests.EndToEnd.Infra;
+using Xunit.Abstractions;
+
+namespace Tests.EndToEnd;
+
+[Collection("Yaml CLI tests")]
+public class AccessPointCommandTests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)
+    : IClassFixture<TempYamlCliFixture>
+{
+    private async Task<(string, string)> ExecuteAsync(params string[] args)
+    {
+        var output = await YamlCliTestHost.RunAsync(args, fs.Root, outputHelper, "config.yaml");
+
+        var yaml = await File.ReadAllTextAsync(Path.Combine(fs.Root, "config.yaml"));
+        return (output, yaml);
+    }
+
+    [Fact]
+    public async Task describe_returns_detailed_information()
+    {
+        // given
+        await ExecuteAsync("accesspoints", "add", "ap01");
+        await ExecuteAsync("accesspoints", "set", "ap01", "--model", "U6-Lite", "--speed", "1");
+
+        // when
+        var (output, _) = await ExecuteAsync("accesspoints", "describe", "ap01");
+
+        // then
+        Assert.Contains("Name:", output);
+        Assert.Contains("ap01", output);
+        Assert.Contains("Model:", output);
+        Assert.Contains("U6-Lite", output);
+        Assert.Contains("Speed (Gbps):", output);
+        Assert.Contains("1", output);
+    }
+
+    [Fact]
+    public async Task help_outputs_do_not_throw()
+    {
+        var (rootHelp, _) = await ExecuteAsync("accesspoints", "--help");
+        Assert.Contains("Manage access points", rootHelp);
+
+        var (addHelp, _) = await ExecuteAsync("accesspoints", "add", "--help");
+        Assert.Contains("Add a new access point", addHelp);
+
+        var (setHelp, _) = await ExecuteAsync("accesspoints", "set", "--help");
+        Assert.Contains("Update properties", setHelp);
+
+        var (describeHelp, _) = await ExecuteAsync("accesspoints", "describe", "--help");
+        Assert.Contains("Show detailed information", describeHelp);
+    }
+}

+ 72 - 0
Tests/EndToEnd/AccessPointTests/AccessPointErrorTests.cs

@@ -0,0 +1,72 @@
+using Tests.EndToEnd.Infra;
+using Xunit.Abstractions;
+
+namespace Tests.EndToEnd;
+
+[Collection("Yaml CLI tests")]
+public class AccessPointErrorTests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)
+    : IClassFixture<TempYamlCliFixture>
+{
+    private async Task<(string, string)> ExecuteAsync(params string[] args)
+    {
+        var output = await YamlCliTestHost.RunAsync(
+            args,
+            fs.Root,
+            outputHelper,
+            "config.yaml");
+
+        var yaml = await File.ReadAllTextAsync(Path.Combine(fs.Root, "config.yaml"));
+        return (output, yaml);
+    }
+
+    [Fact]
+    public async Task adding_duplicate_access_point_returns_error()
+    {
+        await ExecuteAsync("accesspoints", "add", "ap01");
+
+        var (output, _) = await ExecuteAsync("accesspoints", "add", "ap01");
+
+        Assert.Contains("already exists", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    [Fact]
+    public async Task get_missing_access_point_returns_error()
+    {
+        var (output, _) = await ExecuteAsync("accesspoints", "get", "ghost");
+
+        Assert.Contains("not found", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    [Fact]
+    public async Task set_missing_access_point_returns_error()
+    {
+        var (output, _) = await ExecuteAsync(
+            "accesspoints", "set", "ghost",
+            "--model", "X",
+            "--speed", "1"
+        );
+
+        Assert.Contains("not found", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    [Fact]
+    public async Task delete_missing_access_point_returns_error()
+    {
+        var (output, _) = await ExecuteAsync("accesspoints", "del", "ghost");
+
+        Assert.Contains("not found", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    [Fact]
+    public async Task invalid_speed_value_returns_error()
+    {
+        await ExecuteAsync("accesspoints", "add", "ap01");
+
+        var (output, _) = await ExecuteAsync(
+            "accesspoints", "set", "ap01",
+            "--speed", "not-a-number"
+        );
+
+        Assert.Contains("invalid", output, StringComparison.OrdinalIgnoreCase);
+    }
+}

+ 113 - 0
Tests/EndToEnd/AccessPointTests/AccessPointWorkflowTests.cs

@@ -0,0 +1,113 @@
+using Tests.EndToEnd.Infra;
+using Xunit.Abstractions;
+
+namespace Tests.EndToEnd;
+
+[Collection("Yaml CLI tests")]
+public class AccessPointWorkflowTests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)
+    : IClassFixture<TempYamlCliFixture>
+{
+    private async Task<(string, string)> 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 accesspoints_cli_workflow_test()
+    {
+        var (output, yaml) = await ExecuteAsync("accesspoints", "add", "ap01");
+        Assert.Equal("Access Point 'ap01' added.\n", output);
+        Assert.Contains("name: ap01", yaml);
+
+        (output, yaml) = await ExecuteAsync(
+            "accesspoints", "set", "ap01",
+            "--model", "Unifi-U6-Lite",
+            "--speed", "1"
+        );
+        Assert.Equal("Access Point 'ap01' updated.\n", output);
+
+        Assert.Equal("""
+                     resources:
+                     - kind: AccessPoint
+                       model: Unifi-U6-Lite
+                       speed: 1
+                       name: ap01
+
+                     """, yaml);
+
+        (output, yaml) = await ExecuteAsync("accesspoints", "add", "ap02");
+        Assert.Equal("Access Point 'ap02' added.\n", output);
+
+        (output, yaml) = await ExecuteAsync(
+            "accesspoints", "set", "ap02",
+            "--model", "Aruba-AP-515",
+            "--speed", "2.5"
+        );
+        Assert.Equal("Access Point 'ap02' updated.\n", output);
+
+        Assert.Equal("""
+                     resources:
+                     - kind: AccessPoint
+                       model: Unifi-U6-Lite
+                       speed: 1
+                       name: ap01
+                     - kind: AccessPoint
+                       model: Aruba-AP-515
+                       speed: 2.5
+                       name: ap02
+
+                     """, yaml);
+
+        (output, yaml) = await ExecuteAsync("accesspoints", "get", "ap01");
+        Assert.Equal("ap01  Model: Unifi-U6-Lite, Speed: 1Gbps\n", output);
+
+        (output, yaml) = await ExecuteAsync("accesspoints", "list");
+        Assert.Equal("""
+                     ╭──────┬───────────────┬──────────────╮
+                     │ Name │ Model         │ Speed (Gbps) │
+                     ├──────┼───────────────┼──────────────┤
+                     │ ap01 │ Unifi-U6-Lite │ 1            │
+                     │ ap02 │ Aruba-AP-515  │ 2.5          │
+                     ╰──────┴───────────────┴──────────────╯
+
+                     """, output);
+
+        (output, yaml) = await ExecuteAsync("accesspoints", "summary");
+        Assert.Equal("""
+                     ╭──────┬───────────────┬──────────────╮
+                     │ Name │ Model         │ Speed (Gbps) │
+                     ├──────┼───────────────┼──────────────┤
+                     │ ap01 │ Unifi-U6-Lite │ 1            │
+                     │ ap02 │ Aruba-AP-515  │ 2.5          │
+                     ╰──────┴───────────────┴──────────────╯
+
+                     """, output);
+
+        (output, yaml) = await ExecuteAsync("accesspoints", "del", "ap02");
+        Assert.Equal("""
+                     Access Point 'ap02' deleted.
+
+                     """, output);
+
+        (output, yaml) = await ExecuteAsync("accesspoints", "list");
+        Assert.Equal("""
+                     ╭──────┬───────────────┬──────────────╮
+                     │ Name │ Model         │ Speed (Gbps) │
+                     ├──────┼───────────────┼──────────────┤
+                     │ ap01 │ Unifi-U6-Lite │ 1            │
+                     ╰──────┴───────────────┴──────────────╯
+
+                     """, output);
+    }
+}

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

@@ -0,0 +1,66 @@
+using Tests.EndToEnd.Infra;
+using Xunit.Abstractions;
+
+namespace Tests.EndToEnd;
+
+[Collection("Yaml CLI tests")]
+public class DesktopCommandTests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)
+    : IClassFixture<TempYamlCliFixture>
+{
+    private async Task<(string, string)> ExecuteAsync(params string[] args)
+    {
+        var output = await YamlCliTestHost.RunAsync(
+            args,
+            fs.Root,
+            outputHelper,
+            "config.yaml"
+        );
+
+        var yaml = await File.ReadAllTextAsync(Path.Combine(fs.Root, "config.yaml"));
+        return (output, yaml);
+    }
+
+    [Fact]
+    public async Task describe_returns_detailed_information()
+    {
+        // given 
+        await ExecuteAsync("desktops", "add", "workstation01");
+        await ExecuteAsync("desktops", "set", "workstation01", "--model", "Dell Precision 7960");
+
+        // then
+        var (output, _) = await ExecuteAsync("desktops", "describe", "workstation01");
+
+        // when
+        Assert.Contains("Name:", output);
+        Assert.Contains("workstation01", output);
+        Assert.Contains("Model:", output);
+        Assert.Contains("Dell Precision 7960", output);
+    }
+
+    [Fact]
+    public async Task help_outputs_do_not_throw()
+    {
+        Assert.Contains("Manage desktop computers", (await ExecuteAsync("desktops", "--help")).Item1);
+        Assert.Contains("Add a new desktop", (await ExecuteAsync("desktops", "add", "--help")).Item1);
+        Assert.Contains("Retrieve a desktop", (await ExecuteAsync("desktops", "get", "--help")).Item1);
+        Assert.Contains("Show detailed information", (await ExecuteAsync("desktops", "describe", "--help")).Item1);
+        Assert.Contains("Update properties", (await ExecuteAsync("desktops", "set", "--help")).Item1);
+        Assert.Contains("Delete a desktop", (await ExecuteAsync("desktops", "del", "--help")).Item1);
+        Assert.Contains("summarized hardware report", (await ExecuteAsync("desktops", "summary", "--help")).Item1);
+        Assert.Contains("dependency tree", (await ExecuteAsync("desktops", "tree", "--help")).Item1);
+
+        // Component help
+        Assert.Contains("Manage CPUs", (await ExecuteAsync("desktops", "cpu", "--help")).Item1);
+        Assert.Contains("Add a CPU", (await ExecuteAsync("desktops", "cpu", "add", "--help")).Item1);
+        Assert.Contains("Update a desktop CPU", (await ExecuteAsync("desktops", "cpu", "set", "--help")).Item1);
+
+        Assert.Contains("Manage storage drives", (await ExecuteAsync("desktops", "drive", "--help")).Item1);
+        Assert.Contains("Add a drive", (await ExecuteAsync("desktops", "drive", "add", "--help")).Item1);
+
+        Assert.Contains("Manage GPUs", (await ExecuteAsync("desktops", "gpu", "--help")).Item1);
+        Assert.Contains("Add a GPU", (await ExecuteAsync("desktops", "gpu", "add", "--help")).Item1);
+
+        Assert.Contains("Manage network interface cards", (await ExecuteAsync("desktops", "nic", "--help")).Item1);
+        Assert.Contains("Add a NIC", (await ExecuteAsync("desktops", "nic", "add", "--help")).Item1);
+    }
+}

+ 125 - 0
Tests/EndToEnd/DesktopTests/DesktopErrorTests.cs

@@ -0,0 +1,125 @@
+using Tests.EndToEnd.Infra;
+using Xunit.Abstractions;
+
+namespace Tests.EndToEnd;
+
+[Collection("Yaml CLI tests")]
+public class DesktopErrorTests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)
+    : IClassFixture<TempYamlCliFixture>
+{
+    private async Task<(string, string)> ExecuteAsync(params string[] args)
+    {
+        var output = await YamlCliTestHost.RunAsync(
+            args,
+            fs.Root,
+            outputHelper,
+            "config.yaml"
+        );
+
+        var yaml = await File.ReadAllTextAsync(Path.Combine(fs.Root, "config.yaml"));
+        return (output, yaml);
+    }
+
+    [Fact]
+    public async Task adding_duplicate_desktop_returns_error()
+    {
+        await ExecuteAsync("desktops", "add", "workstation01");
+        var (output, _) = await ExecuteAsync("desktops", "add", "workstation01");
+        Assert.Contains("already exists", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    [Fact]
+    public async Task get_missing_desktop_returns_error()
+    {
+        var (output, _) = await ExecuteAsync("desktops", "get", "ghost");
+        Assert.Contains("not found", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    [Fact]
+    public async Task set_missing_desktop_returns_error()
+    {
+        var (output, _) = await ExecuteAsync("desktops", "set", "ghost", "--model", "X");
+        Assert.Contains("not found", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    [Fact]
+    public async Task delete_missing_desktop_returns_error()
+    {
+        var (output, _) = await ExecuteAsync("desktops", "del", "ghost");
+        Assert.Contains("not found", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    [Fact]
+    public async Task tree_missing_desktop_returns_error()
+    {
+        var (output, _) = await ExecuteAsync("desktops", "tree", "ghost");
+        Assert.Contains("not found", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    // CPU errors
+    [Fact]
+    public async Task cpu_add_missing_desktop_returns_error()
+    {
+        var (output, _) = await ExecuteAsync(
+            "desktops", "cpu", "add", "ghost",
+            "--model", "Xeon",
+            "--cores", "8",
+            "--threads", "16"
+        );
+
+        Assert.Contains("not found", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    [Fact]
+    public async Task cpu_set_invalid_index_returns_error()
+    {
+        await ExecuteAsync("desktops", "add", "workstation01");
+
+        var (output, _) = await ExecuteAsync(
+            "desktops", "cpu", "set", "workstation01", "5",
+            "--model", "Xeon"
+        );
+
+        Assert.Contains("invalid", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    // Drive errors
+    [Fact]
+    public async Task drive_add_missing_desktop_returns_error()
+    {
+        var (output, _) = await ExecuteAsync(
+            "desktops", "drive", "add", "ghost",
+            "--type", "ssd",
+            "--size", "1000"
+        );
+
+        Assert.Contains("not found", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    // GPU errors
+    [Fact]
+    public async Task gpu_add_missing_desktop_returns_error()
+    {
+        var (output, _) = await ExecuteAsync(
+            "desktops", "gpu", "add", "ghost",
+            "--model", "RTX 4090",
+            "--vram", "24"
+        );
+
+        Assert.Contains("not found", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    // NIC errors
+    [Fact]
+    public async Task nic_add_missing_desktop_returns_error()
+    {
+        var (output, _) = await ExecuteAsync(
+            "desktops", "nic", "add", "ghost",
+            "--type", "rj45",
+            "--speed", "10",
+            "--ports", "2"
+        );
+
+        Assert.Contains("not found", output, StringComparison.OrdinalIgnoreCase);
+    }
+}

+ 179 - 0
Tests/EndToEnd/DesktopTests/DesktopWorkflowTests.cs

@@ -0,0 +1,179 @@
+using Tests.EndToEnd.Infra;
+using Xunit.Abstractions;
+
+namespace Tests.EndToEnd;
+
+[Collection("Yaml CLI tests")]
+public class DesktopWorkflowTests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)
+    : IClassFixture<TempYamlCliFixture>
+{
+    private async Task<(string, string)> 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 desktops_cli_workflow_test()
+    {
+        await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), "");
+
+        // Add desktop
+        var (output, yaml) = await ExecuteAsync("desktops", "add", "workstation01");
+        Assert.Equal("Desktop 'workstation01' added.\n", output);
+        Assert.Contains("name: workstation01", yaml);
+
+        // Update desktop
+        (output, yaml) = await ExecuteAsync(
+            "desktops", "set", "workstation01",
+            "--model", "Dell Precision 7960"
+        );
+        Assert.Equal("Desktop 'workstation01' updated.\n", output);
+        Assert.Contains("model: Dell Precision 7960", yaml);
+
+        // Add CPU
+        (output, yaml) = await ExecuteAsync(
+            "desktops", "cpu", "add", "workstation01",
+            "--model", "Intel Xeon W7-2495X",
+            "--cores", "24",
+            "--threads", "48"
+        );
+        Assert.Equal("CPU added to desktop 'workstation01'.\n", output);
+
+        // Add Drive
+        (output, yaml) = await ExecuteAsync(
+            "desktops", "drive", "add", "workstation01",
+            "--type", "ssd",
+            "--size", "2000"
+        );
+        Assert.Equal("Drive added to desktop 'workstation01'.\n", output);
+
+        // Add GPU
+        (output, yaml) = await ExecuteAsync(
+            "desktops", "gpu", "add", "workstation01",
+            "--model", "NVIDIA RTX 4090",
+            "--vram", "24"
+        );
+        Assert.Equal("GPU added to desktop 'workstation01'.\n", output);
+
+        // Add NIC
+        (output, yaml) = await ExecuteAsync(
+            "desktops", "nic", "add", "workstation01",
+            "--type", "rj45",
+            "--speed", "10",
+            "--ports", "2"
+        );
+        Assert.Equal("NIC added to desktop 'workstation01'.\n", output);
+
+        // List desktops
+        (output, yaml) = await ExecuteAsync("desktops", "list");
+        Assert.Contains("workstation01", output);
+
+        // Summary
+        (output, yaml) = await ExecuteAsync("desktops", "summary");
+
+        // Describe
+        (output, yaml) = await ExecuteAsync("desktops", "describe", "workstation01");
+
+        // Identity
+        Assert.Contains("Desktop", output);
+        Assert.Contains("workstation01", output);
+
+        // Model
+        Assert.Contains("Dell Precision 7960", output);
+
+        // CPU summary
+        Assert.Contains("CPUs:", output);
+        Assert.Contains("1", output);
+        
+        // RAM summary
+        Assert.Contains("RAM:", output);
+        Assert.Contains("1", output);
+
+        // Drive summary
+        Assert.Contains("Drives:", output);
+        Assert.Contains("1", output);
+
+        // NIC summary
+        Assert.Contains("NICs:", output);
+        Assert.Contains("1", output);
+
+        // GPU summary
+        Assert.Contains("GPUs:", output);
+        Assert.Contains("1", output);
+
+        // ToDo Tree command not currently working as intended
+        
+        // Tree 
+        (output, yaml) = await ExecuteAsync("desktops", "tree", "workstation01");
+        Assert.Contains("workstation01", output);
+        Assert.Contains("CPU:", output);
+        Assert.Contains("RAM:", output);
+        Assert.Contains("Drive:", output);
+        Assert.Contains("GPU:", output);
+        Assert.Contains("NIC:", output);
+
+        // Delete desktop
+        (output, yaml) = await ExecuteAsync("desktops", "del", "workstation01");
+        Assert.Equal("""
+                     Desktop 'workstation01' deleted.
+
+                     """, output);
+    }
+
+    [Fact]
+    public async Task desktops_tree_cli_workflow_test()
+    {
+        await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), "");
+
+        // Add desktop
+        var (output, yaml) = await ExecuteAsync("desktops", "add", "workstation01");
+        Assert.Equal("Desktop 'workstation01' added.\n", output);
+
+        // Add systems
+        (output, yaml) = await ExecuteAsync("systems", "add", "sys01");
+        Assert.Equal("System 'sys01' added.\n", output);
+
+        (output, yaml) = await ExecuteAsync("systems", "add", "sys02");
+        Assert.Equal("System 'sys02' added.\n", output);
+
+        (output, yaml) = await ExecuteAsync("systems", "add", "sys03");
+        Assert.Equal("System 'sys03' added.\n", output);
+
+        // Attach systems
+        await ExecuteAsync("systems", "set", "sys01", "--runs-on", "workstation01");
+        await ExecuteAsync("systems", "set", "sys02", "--runs-on", "workstation01");
+        await ExecuteAsync("systems", "set", "sys03", "--runs-on", "workstation01");
+
+        // Add services
+        await ExecuteAsync("services", "add", "immich");
+        await ExecuteAsync("services", "add", "paperless");
+
+        // Attach services
+        await ExecuteAsync("services", "set", "immich", "--runs-on", "sys01");
+        await ExecuteAsync("services", "set", "paperless", "--runs-on", "sys01");
+
+        // Render tree
+        (output, yaml) = await ExecuteAsync("desktops", "tree", "workstation01");
+        Assert.Equal("""
+                     workstation01
+                     ├── System: sys01
+                     │   ├── Service: immich
+                     │   └── Service: paperless
+                     ├── System: sys02
+                     └── System: sys03
+
+                     """, output);
+    }
+}

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

@@ -0,0 +1,54 @@
+using Tests.EndToEnd.Infra;
+using Xunit.Abstractions;
+
+namespace Tests.EndToEnd;
+
+[Collection("Yaml CLI tests")]
+public class SwitchCommandTests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)
+    : IClassFixture<TempYamlCliFixture>
+{
+    private async Task<(string, string)> ExecuteAsync(params string[] args)
+    {
+        var output = await YamlCliTestHost.RunAsync(
+            args,
+            fs.Root,
+            outputHelper,
+            "config.yaml"
+        );
+
+        var yaml = await File.ReadAllTextAsync(Path.Combine(fs.Root, "config.yaml"));
+        return (output, yaml);
+    }
+
+    [Fact]
+    public async Task describe_outputs_expected_information()
+    {
+        await ExecuteAsync("switches", "add", "sw01");
+        await ExecuteAsync("switches", "set", "sw01", "--Model", "Netgear GS108", "--managed", "true", "--poe", "true");
+
+        var (output, _) = await ExecuteAsync("switches", "describe", "sw01");
+
+        Assert.Contains("sw01", output);
+        Assert.Contains("Netgear GS108", output);
+        Assert.Contains("Managed", output);
+        Assert.Contains("PoE", output);
+    }
+
+    [Fact]
+    public async Task help_commands_do_not_throw()
+    {
+        Assert.Contains("Manage network switches", (await ExecuteAsync("switches", "--help")).Item1);
+        Assert.Contains("Add a new network switch", (await ExecuteAsync("switches", "add", "--help")).Item1);
+        Assert.Contains("List all switches", (await ExecuteAsync("switches", "list", "--help")).Item1);
+        Assert.Contains("Retrieve details", (await ExecuteAsync("switches", "get", "--help")).Item1);
+        Assert.Contains("Show detailed information", (await ExecuteAsync("switches", "describe", "--help")).Item1);
+        Assert.Contains("Update properties", (await ExecuteAsync("switches", "set", "--help")).Item1);
+        Assert.Contains("Delete a switch", (await ExecuteAsync("switches", "del", "--help")).Item1);
+
+        // Port help
+        Assert.Contains("Manage ports", (await ExecuteAsync("switches", "port", "--help")).Item1);
+        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);
+    }
+}

+ 92 - 0
Tests/EndToEnd/SwitchTests/SwitchErrorTests.cs

@@ -0,0 +1,92 @@
+using Tests.EndToEnd.Infra;
+using Xunit.Abstractions;
+
+namespace Tests.EndToEnd;
+
+[Collection("Yaml CLI tests")]
+public class SwitchErrorTests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)
+    : IClassFixture<TempYamlCliFixture>
+{
+    private async Task<(string, string)> ExecuteAsync(params string[] args)
+    {
+        var output = await YamlCliTestHost.RunAsync(
+            args,
+            fs.Root,
+            outputHelper,
+            "config.yaml"
+        );
+
+        var yaml = await File.ReadAllTextAsync(Path.Combine(fs.Root, "config.yaml"));
+        return (output, yaml);
+    }
+
+    [Fact]
+    public async Task adding_duplicate_switch_returns_error()
+    {
+        await ExecuteAsync("switches", "add", "sw01");
+        var (output, _) = await ExecuteAsync("switches", "add", "sw01");
+        Assert.Contains("already exists", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    [Fact]
+    public async Task get_missing_switch_returns_error()
+    {
+        var (output, _) = await ExecuteAsync("switches", "get", "ghost");
+        Assert.Contains("not found", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    [Fact]
+    public async Task set_missing_switch_returns_error()
+    {
+        var (output, _) = await ExecuteAsync("switches", "set", "ghost", "--Model", "X");
+        Assert.Contains("not found", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    [Fact]
+    public async Task delete_missing_switch_returns_error()
+    {
+        var (output, _) = await ExecuteAsync("switches", "del", "ghost");
+        Assert.Contains("not found", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    // Port errors
+    [Fact]
+    public async Task port_add_missing_switch_returns_error()
+    {
+        var (output, _) = await ExecuteAsync(
+            "switches", "port", "add", "ghost",
+            "--type", "rj45",
+            "--speed", "1",
+            "--count", "8"
+        );
+
+        Assert.Contains("not found", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    [Fact]
+    public async Task port_set_invalid_index_returns_error()
+    {
+        await ExecuteAsync("switches", "add", "sw01");
+
+        var (output, _) = await ExecuteAsync(
+            "switches", "port", "set", "sw01",
+            "--index", "5",
+            "--type", "rj45"
+        );
+
+        Assert.Contains("invalid", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    [Fact]
+    public async Task port_del_invalid_index_returns_error()
+    {
+        await ExecuteAsync("switches", "add", "sw01");
+
+        var (output, _) = await ExecuteAsync(
+            "switches", "port", "del", "sw01",
+            "--index", "3"
+        );
+
+        Assert.Contains("invalid", output, StringComparison.OrdinalIgnoreCase);
+    }
+}

+ 131 - 0
Tests/EndToEnd/SwitchTests/SwitchWorkflowTests.cs

@@ -0,0 +1,131 @@
+using Tests.EndToEnd.Infra;
+using Xunit.Abstractions;
+
+namespace Tests.EndToEnd;
+
+[Collection("Yaml CLI tests")]
+public class SwitchWorkflowTests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)
+    : IClassFixture<TempYamlCliFixture>
+{
+    private async Task<(string, string)> 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 switches_cli_workflow_test()
+    {
+        await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), "");
+
+        // Add switch
+        var (output, yaml) = await ExecuteAsync("switches", "add", "sw01");
+        Assert.Equal("Switch 'sw01' added.\n", output);
+        Assert.Contains("name: sw01", yaml);
+
+        // Update switch
+        (output, yaml) = await ExecuteAsync(
+            "switches", "set", "sw01",
+            "--Model", "Netgear GS108",
+            "--managed", "true",
+            "--poe", "true"
+        );
+        // ToDo fails due command incorrect 
+        Assert.Equal("Switch 'sw01' updated.\n", output);
+
+        Assert.Equal("""
+                     resources:
+                     - kind: Switch
+                       model: Netgear GS108
+                       managed: true
+                       poe: true
+                       name: sw01
+
+                     """, yaml);
+
+        // Add second switch
+        (output, yaml) = await ExecuteAsync("switches", "add", "sw02");
+        Assert.Equal("Switch 'sw02' added.\n", output);
+
+        (output, yaml) = await ExecuteAsync(
+            "switches", "set", "sw02",
+            "--Model", "TP-Link TL-SG108E",
+            "--managed", "false",
+            "--poe", "false"
+        );
+        // ToDo fails due command incorrect 
+        Assert.Equal("Switch 'sw02' updated.\n", output);
+
+        Assert.Equal("""
+                     resources:
+                     - kind: Switch
+                       model: Netgear GS108
+                       managed: true
+                       poe: true
+                       name: sw01
+                     - kind: Switch
+                       model: TP-Link TL-SG108E
+                       managed: false
+                       poe: false
+                       name: sw02
+
+                     """, yaml);
+
+        // Get switch
+        (output, yaml) = await ExecuteAsync("switches", "get", "sw01");
+        Assert.Equal("sw01  Model: Netgear GS108, Managed: Yes, PoE: Yes\n", output);
+
+        // List switches
+        (output, yaml) = await ExecuteAsync("switches", "list");
+        Assert.Equal("""
+                     ╭──────┬───────────────────┬─────────┬─────┬───────┬──────────────╮
+                     │ Name │ Model             │ Managed │ PoE │ Ports │ Port Summary │
+                     ├──────┼───────────────────┼─────────┼─────┼───────┼──────────────┤
+                     │ sw01 │ Netgear GS108     │ yes     │ yes │ 0     │ Unknown      │
+                     │ sw02 │ TP-Link TL-SG108E │ no      │ no  │ 0     │ Unknown      │
+                     ╰──────┴───────────────────┴─────────┴─────┴───────┴──────────────╯
+
+                     """, output);
+
+        // Summary
+        (output, yaml) = await ExecuteAsync("switches", "summary");
+        Assert.Equal("""
+                     ╭──────┬───────────────────┬─────────┬─────┬───────┬───────────┬──────────────╮
+                     │ Name │ Model             │ Managed │ PoE │ Ports │ Max Speed │ Port Summary │
+                     ├──────┼───────────────────┼─────────┼─────┼───────┼───────────┼──────────────┤
+                     │ sw01 │ Netgear GS108     │ yes     │ yes │ 0     │ 0G        │ Unknown      │
+                     │ sw02 │ TP-Link TL-SG108E │ no      │ no  │ 0     │ 0G        │ Unknown      │
+                     ╰──────┴───────────────────┴─────────┴─────┴───────┴───────────┴──────────────╯
+
+                     """, output);
+
+        // Delete switch
+        (output, yaml) = await ExecuteAsync("switches", "del", "sw02");
+        Assert.Equal("""
+                     Switch 'sw02' deleted.
+
+                     """, output);
+
+        // List again
+        (output, yaml) = await ExecuteAsync("switches", "list");
+        Assert.Equal("""
+                     ╭──────┬───────────────┬─────────┬─────┬───────┬──────────────╮
+                     │ Name │ Model         │ Managed │ PoE │ Ports │ Port Summary │
+                     ├──────┼───────────────┼─────────┼─────┼───────┼──────────────┤
+                     │ sw01 │ Netgear GS108 │ yes     │ yes │ 0     │ Unknown      │
+                     ╰──────┴───────────────┴─────────┴─────┴───────┴──────────────╯
+
+                     """, output);
+    }
+}

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

@@ -0,0 +1,61 @@
+using Tests.EndToEnd.Infra;
+using Xunit.Abstractions;
+
+namespace Tests.EndToEnd;
+
+[Collection("Yaml CLI tests")]
+public class UpsCommandTests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)
+    : IClassFixture<TempYamlCliFixture>
+{
+    private async Task<(string, string)> ExecuteAsync(params string[] args)
+    {
+        var output = await YamlCliTestHost.RunAsync(
+            args,
+            fs.Root,
+            outputHelper,
+            "config.yaml");
+
+        var yaml = await File.ReadAllTextAsync(Path.Combine(fs.Root, "config.yaml"));
+        return (output, yaml);
+    }
+
+    [Fact]
+    public async Task describe_returns_detailed_information()
+    {
+        // given
+        await ExecuteAsync("ups", "add", "ups01");
+        await ExecuteAsync("ups", "set", "ups01", "--model", "APC Smart-UPS 1500", "--va", "1500");
+
+        // when
+        var (output, _) = await ExecuteAsync("ups", "describe", "ups01");
+
+        // then 
+        Assert.Contains("Name:", output);
+        Assert.Contains("ups01", output);
+
+        Assert.Contains("Model:", output);
+        Assert.Contains("APC Smart-UPS 1500", output);
+
+        Assert.Contains("VA", output);
+        Assert.Contains("1500", output);
+    }
+
+    [Fact]
+    public async Task help_outputs_do_not_throw()
+    {
+        var (rootHelp, _) = await ExecuteAsync("ups", "--help");
+        Assert.Contains("Manage UPS units", rootHelp);
+
+        var (addHelp, _) = await ExecuteAsync("ups", "add", "--help");
+        Assert.Contains("Add a new UPS unit", addHelp);
+
+        var (setHelp, _) = await ExecuteAsync("ups", "set", "--help");
+        Assert.Contains("Update properties", setHelp);
+
+        var (describeHelp, _) = await ExecuteAsync("ups", "describe", "--help");
+        Assert.Contains("Show detailed information", describeHelp);
+
+        var (delHelp, _) = await ExecuteAsync("ups", "del", "--help");
+        Assert.Contains("Delete a UPS unit", delHelp);
+    }
+}

+ 72 - 0
Tests/EndToEnd/UpsTests/UpsErrorTest.cs

@@ -0,0 +1,72 @@
+using Tests.EndToEnd.Infra;
+using Xunit.Abstractions;
+
+namespace Tests.EndToEnd;
+
+[Collection("Yaml CLI tests")]
+public class UpsErrorTests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)
+    : IClassFixture<TempYamlCliFixture>
+{
+    private async Task<(string, string)> ExecuteAsync(params string[] args)
+    {
+        var output = await YamlCliTestHost.RunAsync(
+            args,
+            fs.Root,
+            outputHelper,
+            "config.yaml");
+
+        var yaml = await File.ReadAllTextAsync(Path.Combine(fs.Root, "config.yaml"));
+        return (output, yaml);
+    }
+
+    [Fact]
+    public async Task adding_duplicate_ups_returns_error()
+    {
+        await ExecuteAsync("ups", "add", "ups01");
+
+        var (output, _) = await ExecuteAsync("ups", "add", "ups01");
+
+        Assert.Contains("already exists", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    [Fact]
+    public async Task get_missing_ups_returns_error()
+    {
+        var (output, _) = await ExecuteAsync("ups", "get", "ghost");
+
+        Assert.Contains("not found", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    [Fact]
+    public async Task set_missing_ups_returns_error()
+    {
+        var (output, _) = await ExecuteAsync(
+            "ups", "set", "ghost",
+            "--model", "X",
+            "--va", "1000"
+        );
+
+        Assert.Contains("not found", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    [Fact]
+    public async Task delete_missing_ups_returns_error()
+    {
+        var (output, _) = await ExecuteAsync("ups", "del", "ghost");
+
+        Assert.Contains("not found", output, StringComparison.OrdinalIgnoreCase);
+    }
+
+    [Fact]
+    public async Task invalid_va_value_returns_error()
+    {
+        await ExecuteAsync("ups", "add", "ups01");
+
+        var (output, _) = await ExecuteAsync(
+            "ups", "set", "ups01",
+            "--va", "not-a-number"
+        );
+
+        Assert.Contains("invalid", output, StringComparison.OrdinalIgnoreCase);
+    }
+}

+ 103 - 0
Tests/EndToEnd/UpsTests/UpsWorkflowtests.cs

@@ -0,0 +1,103 @@
+using Tests.EndToEnd.Infra;
+using Xunit.Abstractions;
+
+namespace Tests.EndToEnd;
+
+[Collection("Yaml CLI tests")]
+public class UpsWorkflowTests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)
+    : IClassFixture<TempYamlCliFixture>
+{
+    private async Task<(string, string)> 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 ups_cli_workflow_test()
+    {
+        // Add UPS
+        var (output, yaml) = await ExecuteAsync("ups", "add", "ups01");
+        Assert.Equal("UPS 'ups01' added.\n", output);
+        Assert.Contains("name: ups01", yaml);
+
+        // Update UPS
+        (output, yaml) = await ExecuteAsync(
+            "ups", "set", "ups01",
+            "--model", "APC-SmartUPS-1500",
+            "--va", "1500"
+        );
+        Assert.Equal("UPS 'ups01' updated.\n", output);
+
+        Assert.Equal("""
+                     resources:
+                     - kind: Ups
+                       model: APC-SmartUPS-1500
+                       va: 1500
+                       name: ups01
+
+                     """, yaml);
+
+        // Add second UPS
+        (output, yaml) = await ExecuteAsync("ups", "add", "ups02");
+        Assert.Equal("UPS 'ups02' added.\n", output);
+
+        (output, yaml) = await ExecuteAsync(
+            "ups", "set", "ups02",
+            "--model", "CyberPower-2200VA",
+            "--va", "2200"
+        );
+        Assert.Equal("UPS 'ups02' updated.\n", output);
+
+        Assert.Equal("""
+                     resources:
+                     - kind: Ups
+                       model: APC-SmartUPS-1500
+                       va: 1500
+                       name: ups01
+                     - kind: Ups
+                       model: CyberPower-2200VA
+                       va: 2200
+                       name: ups02
+
+                     """, yaml);
+
+        // Get UPS
+        (output, yaml) = await ExecuteAsync("ups", "get", "ups01");
+        Assert.Contains("ups01", output);
+        Assert.Contains("APC-SmartUPS-1500", output);
+        Assert.Contains("1500", output);
+
+        // List UPS units
+        (output, yaml) = await ExecuteAsync("ups", "list");
+        Assert.Contains("ups01", output);
+        Assert.Contains("ups02", output);
+
+        // Summary
+        (output, yaml) = await ExecuteAsync("ups", "summary");
+        Assert.Contains("ups01", output);
+        Assert.Contains("ups02", output);
+
+        // Delete UPS
+        (output, yaml) = await ExecuteAsync("ups", "del", "ups02");
+        Assert.Equal("""
+                     UPS 'ups02' deleted.
+
+                     """, output);
+
+        // List again
+        (output, yaml) = await ExecuteAsync("ups", "list");
+        Assert.Contains("ups01", output);
+        Assert.DoesNotContain("ups02", output);
+    }
+}