ServiceEndpointTests.cs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. using RackPeek.Domain.Resources.Services;
  2. namespace Tests.Discovery;
  3. /// <summary>
  4. /// Where a service answers, and whether a browser can do anything with it.
  5. /// <para>
  6. /// Both of these were wrong in the UI. The endpoint was rendered as
  7. /// <c>Ip: 10.0.50.105:3000</c> and then used as a link target, so clicking it did
  8. /// nothing. And the scheme fell back to <c>http</c> for every port, so an SSH
  9. /// service showed <c>http://10.0.50.105:22/</c> — a link that looks real, invites
  10. /// a click and cannot load.
  11. /// </para>
  12. /// </summary>
  13. public class ServiceEndpointTests {
  14. private static Network Net(int? port, string? protocol = "TCP", string? ip = "10.0.50.105", string? url = null) =>
  15. new() { Ip = ip, Port = port, Protocol = protocol, Url = url };
  16. private static Service Svc(Network? network) =>
  17. new() { Kind = Service.KindLabel, Name = "svc", Network = network };
  18. [Fact]
  19. public void An_endpoint_reads_as_an_address_and_nothing_else() =>
  20. // It goes on screen next to the service name; a label belongs in the markup.
  21. Assert.Equal("10.0.50.105:3000", Svc(Net(3000)).NetworkString());
  22. [Fact]
  23. public void An_endpoint_with_no_port_is_just_the_address() =>
  24. Assert.Equal("10.0.50.105", Svc(Net(null)).NetworkString());
  25. [Fact]
  26. public void A_service_with_no_network_has_no_endpoint() =>
  27. Assert.Equal(string.Empty, Svc(null).NetworkString());
  28. [Theory]
  29. [InlineData(22)] // ssh
  30. [InlineData(445)] // smb
  31. [InlineData(1883)] // mqtt
  32. [InlineData(53)] // dns
  33. [InlineData(3306)] // mysql
  34. [InlineData(9987)] // nothing curated
  35. public void A_port_a_browser_cannot_open_gets_no_link(int port) =>
  36. Assert.Null(Svc(Net(port)).BrowsableUrl());
  37. [Theory]
  38. [InlineData(80, "http://10.0.50.105/")]
  39. [InlineData(3000, "http://10.0.50.105:3000/")]
  40. [InlineData(8123, "http://10.0.50.105:8123/")]
  41. [InlineData(9000, "http://10.0.50.105:9000/")]
  42. public void A_web_port_gets_a_link(int port, string expected) =>
  43. Assert.Equal(expected, Svc(Net(port)).BrowsableUrl());
  44. [Theory]
  45. [InlineData(443, "https://10.0.50.105/")]
  46. [InlineData(8006, "https://10.0.50.105:8006/")]
  47. [InlineData(8443, "https://10.0.50.105:8443/")]
  48. public void A_tls_port_gets_an_https_link(int port, string expected) =>
  49. // Proxmox on 8006 redirects plain HTTP, so guessing http costs a round trip.
  50. Assert.Equal(expected, Svc(Net(port)).BrowsableUrl());
  51. [Fact]
  52. public void The_transport_is_not_mistaken_for_a_scheme() {
  53. // Discovery writes "TCP" into protocol, which says nothing about what rides on
  54. // top of it. Reading that as a scheme is what produced http:// on port 22.
  55. Assert.Null(Svc(Net(22, "TCP")).BrowsableUrl());
  56. Assert.Equal("http://10.0.50.105:3000/", Svc(Net(3000, "TCP")).BrowsableUrl());
  57. }
  58. [Theory]
  59. [InlineData("https", "https://10.0.50.105:9999/")]
  60. [InlineData("HTTP", "http://10.0.50.105:9999/")]
  61. public void A_protocol_that_names_a_scheme_is_believed(string protocol, string expected) =>
  62. // On an uncurated port this is the only thing that can answer.
  63. Assert.Equal(expected, Svc(Net(9999, protocol)).BrowsableUrl());
  64. [Fact]
  65. public void A_url_someone_typed_always_wins() {
  66. Service service = Svc(Net(22, "TCP", url: "https://git.example.com/"));
  67. Assert.Equal("https://git.example.com/", service.BrowsableUrl());
  68. Assert.Equal("https://git.example.com/", service.NetworkString());
  69. }
  70. [Fact]
  71. public void A_service_with_no_address_of_its_own_can_borrow_its_hosts() {
  72. // The card resolves the host's address when the service carries none.
  73. Service service = Svc(Net(8080, ip: null));
  74. Assert.Null(service.BrowsableUrl());
  75. Assert.Equal("http://10.0.20.7:8080/", service.BrowsableUrl("10.0.20.7"));
  76. }
  77. [Fact]
  78. public void A_service_with_no_port_is_not_guessed_at() =>
  79. Assert.Null(Svc(Net(null)).BrowsableUrl());
  80. [Theory]
  81. [InlineData("host name with spaces")]
  82. [InlineData("...")]
  83. [InlineData("a/b")]
  84. [InlineData("under_score.local")]
  85. [InlineData("fe80::1")]
  86. [InlineData("[::1]")]
  87. public void An_address_no_url_can_be_built_from_yields_no_link_rather_than_throwing(string ip) {
  88. // The address field holds whatever a person typed or a device reported, and
  89. // neither is obliged to produce something a URL can be built from. This is
  90. // rendered inside the hardware and system trees, so an exception here would
  91. // blank the whole page rather than spoil one link.
  92. Service service = Svc(Net(8080, ip: ip));
  93. Exception? thrown = Record.Exception(() => service.BrowsableUrl());
  94. Assert.Null(thrown);
  95. }
  96. }