ConnectionCasingTests.cs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. using System.Text.RegularExpressions;
  2. using Microsoft.Playwright;
  3. using Tests.E2e.Infra;
  4. using Tests.E2e.PageObjectModels;
  5. using Xunit.Abstractions;
  6. namespace Tests.E2e;
  7. /// <summary>
  8. /// Reproduces https://github.com/Timmoth/RackPeek/issues/333: when a stored
  9. /// connection endpoint differs in case from the resource name (created by
  10. /// `rpk connections add` or hand-edited/imported YAML), the Web UI treats
  11. /// the port as unconnected — the card square stays grey with an "Available"
  12. /// tooltip, and the details-page Ports panel shows "free" instead of a link
  13. /// to the peer. Clicking the supposedly free port then crashes the Blazor
  14. /// circuit. Both tests fail on staging until the endpoint comparisons (or
  15. /// the stored casing) are fixed.
  16. /// </summary>
  17. public class ConnectionCasingTests(
  18. PlaywrightFixture fixture,
  19. ITestOutputHelper output) : E2ETestBase(fixture, output) {
  20. private readonly PlaywrightFixture _fixture = fixture;
  21. private readonly ITestOutputHelper _output = output;
  22. private static string CaseMismatchedConfig(string switchA, string switchB) =>
  23. $"""
  24. version: 3
  25. resources:
  26. - kind: Switch
  27. name: {switchA}
  28. ports:
  29. - type: rj45
  30. speed: 1
  31. count: 2
  32. - kind: Switch
  33. name: {switchB}
  34. ports:
  35. - type: rj45
  36. speed: 1
  37. count: 2
  38. connections:
  39. - a:
  40. resource: {switchA.ToLowerInvariant()}
  41. portGroup: 0
  42. portIndex: 0
  43. b:
  44. resource: {switchB.ToLowerInvariant()}
  45. portGroup: 0
  46. portIndex: 0
  47. label: probe-link
  48. """;
  49. [Fact]
  50. public async Task Card_Port_Square_Shows_The_Case_Mismatched_Connection() {
  51. (IBrowserContext context, IPage page) = await CreatePageAsync();
  52. var switchA = $"E2E-Csa-{Guid.NewGuid():N}"[..14];
  53. var switchB = $"E2E-Csb-{Guid.NewGuid():N}"[..14];
  54. try {
  55. await SeedAsync(page, switchA, switchB);
  56. await page.GotoAsync(
  57. $"{_fixture.BaseUrl}/resources/hardware/{Uri.EscapeDataString(switchA)}");
  58. var ports = new PortsPom(page);
  59. ILocator square = ports.Port("switch-ports", 0, 0);
  60. await Assertions.Expect(square).ToBeVisibleAsync();
  61. // A connected port's tooltip names the peer; "Available" means the
  62. // UI failed to match the stored connection to this port.
  63. await Assertions.Expect(square).Not.ToHaveAttributeAsync("title", "Available");
  64. }
  65. catch (Exception) {
  66. await DumpAsync(page);
  67. throw;
  68. }
  69. finally {
  70. await context.CloseAsync();
  71. }
  72. }
  73. [Fact]
  74. public async Task Details_Ports_Panel_Links_To_The_Case_Mismatched_Peer() {
  75. (IBrowserContext context, IPage page) = await CreatePageAsync();
  76. var switchA = $"E2E-Cta-{Guid.NewGuid():N}"[..14];
  77. var switchB = $"E2E-Ctb-{Guid.NewGuid():N}"[..14];
  78. try {
  79. await SeedAsync(page, switchA, switchB);
  80. await page.GotoAsync(
  81. $"{_fixture.BaseUrl}/resources/hardware/{Uri.EscapeDataString(switchA)}");
  82. // A connected port renders as a NavLink to the peer's details page.
  83. // Today the row renders "1 - free" instead, and clicking it kills
  84. // the Blazor circuit.
  85. ILocator peerLink = page.Locator(
  86. $"a[href*='{Uri.EscapeDataString(switchB)}']");
  87. await Assertions.Expect(peerLink.First).ToBeVisibleAsync();
  88. await peerLink.First.ClickAsync();
  89. await Assertions.Expect(page).ToHaveURLAsync(
  90. new Regex(Regex.Escape(Uri.EscapeDataString(switchB))));
  91. }
  92. catch (Exception) {
  93. await DumpAsync(page);
  94. throw;
  95. }
  96. finally {
  97. await context.CloseAsync();
  98. }
  99. }
  100. private async Task SeedAsync(IPage page, string switchA, string switchB) {
  101. var import = new YamlImportPom(page);
  102. await import.GotoAsync(_fixture.BaseUrl);
  103. await import.PasteAsync(CaseMismatchedConfig(switchA, switchB));
  104. await import.AssertNoErrorAsync();
  105. await import.ApplyAsync();
  106. }
  107. private async Task DumpAsync(IPage page) {
  108. _output.WriteLine("TEST FAILED — Capturing diagnostics");
  109. _output.WriteLine($"Current URL: {page.Url}");
  110. var html = await page.ContentAsync();
  111. _output.WriteLine("==== DOM SNAPSHOT START ====");
  112. _output.WriteLine(html);
  113. _output.WriteLine("==== DOM SNAPSHOT END ====");
  114. }
  115. }