NetworkScanMapperTests.cs 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. using RackPeek.Domain.Discovery;
  2. using RackPeek.Domain.Resources;
  3. using RackPeek.Domain.Resources.SystemResources;
  4. namespace Tests.Discovery;
  5. /// <summary>
  6. /// Swept hosts → the System cards RackPeek stores. The contract that matters most
  7. /// is identity: MAC-seeded, format-independent, IP only as a last resort.
  8. /// </summary>
  9. public class NetworkScanMapperTests {
  10. private static NetworkHostFact Host(
  11. string ip = "192.168.1.20",
  12. string? mac = "dc:a6:32:0f:11:22",
  13. string? hostname = "nas01.lan") =>
  14. new(ip, mac, hostname, true, []);
  15. [Fact]
  16. public void A_host_becomes_a_system_card_with_ip_mac_and_its_dns_name() {
  17. List<Resource> resources = NetworkScanMapper.ToResources([Host()]);
  18. SystemResource system = Assert.IsType<SystemResource>(Assert.Single(resources));
  19. Assert.Equal("nas01", system.Name); // first label of nas01.lan
  20. Assert.Equal("System", system.Kind);
  21. Assert.Equal("192.168.1.20", system.Ip);
  22. // Deliberately sparse: anything a scan cannot see stays null so a rescan can
  23. // never overwrite what the user or an agent collector filled in.
  24. Assert.Null(system.Type);
  25. Assert.Null(system.Os);
  26. Assert.Null(system.Cores);
  27. Assert.Equal("dc:a6:32:0f:11:22", system.Labels["mac"]);
  28. Assert.StartsWith("rpk1:net:", system.DiscoveryId);
  29. }
  30. [Fact]
  31. public void Identity_rides_on_the_mac_so_a_dhcp_move_is_the_same_machine() {
  32. List<Resource> before = NetworkScanMapper.ToResources([Host(ip: "192.168.1.20")]);
  33. List<Resource> after = NetworkScanMapper.ToResources([Host(ip: "192.168.1.99")]);
  34. Assert.Equal(before[0].DiscoveryId, after[0].DiscoveryId);
  35. }
  36. [Fact]
  37. public void Without_a_mac_the_ip_seeds_the_identity_instead() {
  38. List<Resource> resources = NetworkScanMapper.ToResources([Host(mac: null)]);
  39. Assert.StartsWith("rpk1:net:", resources[0].DiscoveryId);
  40. Assert.False(Assert.IsType<SystemResource>(resources[0]).Labels.ContainsKey("mac"));
  41. // ...and it is a different identity than the MAC would have produced.
  42. Assert.NotEqual(
  43. NetworkScanMapper.ToResources([Host()])[0].DiscoveryId,
  44. resources[0].DiscoveryId);
  45. }
  46. [Fact]
  47. public void A_host_with_no_dns_name_gets_a_deterministic_one_from_its_id() {
  48. List<Resource> resources = NetworkScanMapper.ToResources([Host(hostname: null)]);
  49. Assert.StartsWith("host-", resources[0].Name);
  50. // Deterministic: the same machine names itself the same way on every run.
  51. Assert.Equal(resources[0].Name, NetworkScanMapper.ToResources([Host(hostname: null)])[0].Name);
  52. }
  53. [Fact]
  54. public void Two_hosts_answering_to_the_same_dns_name_stay_distinct() {
  55. // A lazy resolver that answers every PTR with the router's name must not
  56. // collapse the whole network into one card (the import rejects duplicates).
  57. List<Resource> resources = NetworkScanMapper.ToResources([
  58. Host(ip: "192.168.1.1", mac: "a4:91:b1:4e:3c:20", hostname: "router.lan"),
  59. Host(ip: "192.168.1.2", mac: "b0:00:00:00:00:02", hostname: "router.lan")
  60. ]);
  61. Assert.Equal(2, resources.Select(r => r.Name).Distinct(StringComparer.OrdinalIgnoreCase).Count());
  62. Assert.Equal("router", resources[0].Name);
  63. Assert.StartsWith("router-", resources[1].Name);
  64. }
  65. [Fact]
  66. public void One_mac_answering_on_several_addresses_yields_distinct_stable_identities() {
  67. // Gateways answer on VIPs and aliases all the time: one MAC, many addresses.
  68. // The shared MAC alone cannot identify the cards — the import rejects duplicate
  69. // ids — so each address folds into the seed, deterministically.
  70. NetworkHostFact[] swept = [
  71. Host(ip: "192.168.1.1", hostname: "gw.lan"),
  72. Host(ip: "192.168.1.2", hostname: null)
  73. ];
  74. List<Resource> resources = NetworkScanMapper.ToResources(swept);
  75. Assert.Equal(2, resources.Select(r => r.DiscoveryId).Distinct().Count());
  76. Assert.Equal(
  77. resources.Select(r => r.DiscoveryId),
  78. NetworkScanMapper.ToResources(swept).Select(r => r.DiscoveryId));
  79. }
  80. [Fact]
  81. public void The_emitted_document_conforms_to_the_published_schema() {
  82. List<Resource> resources = NetworkScanMapper.ToResources([
  83. Host(),
  84. Host(ip: "192.168.1.30", mac: null, hostname: null)
  85. ]);
  86. Fixture.AssertConformsToSchema(DiscoveryDocument.ToYaml(resources));
  87. }
  88. }