ExportToolTests.cs 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. using ModelContextProtocol.Client;
  2. using RackPeek.Domain.UseCases.Ansible;
  3. using RackPeek.Domain.UseCases.Hosts;
  4. using RackPeek.Domain.UseCases.SSH;
  5. namespace Tests.Mcp;
  6. /// <summary>
  7. /// The exporters render the same seed everywhere, so these tests check the shape a
  8. /// downstream consumer (ansible, ssh, /etc/hosts, mermaid) would actually parse.
  9. /// </summary>
  10. public class ExportToolTests {
  11. [Fact]
  12. public async Task The_ansible_inventory_groups_labelled_hosts_in_both_formats() {
  13. // The generator addresses hosts by their ansible_host/ip/hostname label and
  14. // emits them under the groups asked for — no grouping, no output.
  15. using var api = new McpFixture(TestData.Seed);
  16. await using McpClient client = await api.ConnectAsync();
  17. var args = new Dictionary<string, object?> { ["groupByTags"] = new[] { "prod" } };
  18. InventoryResult ini = await client.CallOkAsync<InventoryResult>("export_ansible_inventory", args);
  19. InventoryResult yaml = await client.CallOkAsync<InventoryResult>(
  20. "export_ansible_inventory",
  21. new Dictionary<string, object?>(args) { ["format"] = "Yaml" });
  22. Assert.Contains("[prod]", ini.InventoryText);
  23. Assert.Contains("rack-server", ini.InventoryText);
  24. Assert.Contains("ansible_host=10.0.0.2", ini.InventoryText);
  25. Assert.Contains("rack-server", yaml.InventoryText);
  26. Assert.NotEqual(ini.InventoryText, yaml.InventoryText);
  27. }
  28. [Fact]
  29. public async Task The_ssh_config_writes_host_blocks_with_the_chosen_defaults() {
  30. using var api = new McpFixture(TestData.Seed);
  31. await using McpClient client = await api.ConnectAsync();
  32. SshExportResult result = await client.CallOkAsync<SshExportResult>(
  33. "export_ssh_config", new Dictionary<string, object?> { ["defaultUser"] = "admin" });
  34. Assert.Contains("Host host-os", result.ConfigText);
  35. Assert.Contains("HostName 10.0.0.5", result.ConfigText);
  36. Assert.Contains("User admin", result.ConfigText);
  37. }
  38. [Fact]
  39. public async Task The_hosts_file_maps_addresses_to_names_with_an_optional_suffix() {
  40. using var api = new McpFixture(TestData.Seed);
  41. await using McpClient client = await api.ConnectAsync();
  42. HostsExportResult plain = await client.CallOkAsync<HostsExportResult>("export_hosts_file");
  43. HostsExportResult suffixed = await client.CallOkAsync<HostsExportResult>(
  44. "export_hosts_file", new Dictionary<string, object?> {
  45. ["domainSuffix"] = "home.lan",
  46. ["includeLocalhostDefaults"] = false
  47. });
  48. Assert.Contains("127.0.0.1 localhost", plain.HostsText);
  49. Assert.Contains("10.0.0.5 host-os", plain.HostsText);
  50. Assert.Contains("10.0.0.5 host-os.home.lan", suffixed.HostsText);
  51. Assert.DoesNotContain("localhost", suffixed.HostsText);
  52. }
  53. [Fact]
  54. public async Task The_mermaid_views_draw_the_physical_and_logical_pictures() {
  55. using var api = new McpFixture(TestData.Seed);
  56. await using McpClient client = await api.ConnectAsync();
  57. var physical = await client.CallTextAsync("export_topology_mermaid");
  58. var logical = await client.CallTextAsync(
  59. "export_topology_mermaid", new Dictionary<string, object?> { ["view"] = "Logical" });
  60. // Physical: hardware nodes and the cabled edge between them.
  61. Assert.Contains("rack-server", physical);
  62. Assert.Contains("rack-switch", physical);
  63. Assert.Contains("uplink", physical);
  64. // Logical: host cards carrying their services; no cabling.
  65. Assert.Contains("host-os", logical);
  66. Assert.Contains("grafana", logical);
  67. Assert.DoesNotContain("uplink", logical);
  68. }
  69. [Fact]
  70. public async Task Exports_over_the_demo_inventory_produce_output_for_every_format() {
  71. using var api = new McpFixture(TestData.DemoConfig());
  72. await using McpClient client = await api.ConnectAsync();
  73. InventoryResult ansible = await client.CallOkAsync<InventoryResult>("export_ansible_inventory");
  74. SshExportResult ssh = await client.CallOkAsync<SshExportResult>("export_ssh_config");
  75. HostsExportResult hosts = await client.CallOkAsync<HostsExportResult>("export_hosts_file");
  76. var mermaid = await client.CallTextAsync("export_topology_mermaid");
  77. // No grouping asked for, so ansible legitimately answers with a warning
  78. // rather than hosts; the call itself must still succeed.
  79. Assert.NotNull(ansible);
  80. Assert.False(string.IsNullOrWhiteSpace(ssh.ConfigText));
  81. Assert.Contains("pfsense-fw", mermaid);
  82. Assert.NotEmpty(hosts.HostsText);
  83. }
  84. }