You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

186 lines
7.1KB

  1. using System.Net;
  2. using System.Net.Http.Headers;
  3. using System.Net.Http.Json;
  4. using Campaign_Tracker.Server.ElectionCycles;
  5. using Campaign_Tracker.Server.ExtensionData;
  6. using Campaign_Tracker.Server.LegacyData;
  7. using Campaign_Tracker.Server.LegacyData.Models;
  8. using Campaign_Tracker.Server.Municipalities;
  9. using Microsoft.Extensions.DependencyInjection;
  10. namespace Campaign_Tracker.Server.Tests;
  11. public sealed class ElectionCycleKanbanReadModelTests
  12. {
  13. [Fact]
  14. public async Task GetKanban_GroupsActiveAssignmentsAndUnassignedMunicipalities_AC1_AC3()
  15. {
  16. await using var factory = new AuthIntegrationTestFactory();
  17. using var client = CreateClient(factory, "client-services");
  18. await CreateProfile(client, "FAIR01", "Fairview Display");
  19. await CreateProfile(client, "LAKE02", null);
  20. await CreateProfile(client, "PINE03", null);
  21. var response = await client.GetAsync("/api/election-cycles/kanban");
  22. Assert.Equal(HttpStatusCode.OK, response.StatusCode);
  23. var board = await response.Content.ReadFromJsonAsync<ElectionCycleKanbanDto>();
  24. Assert.NotNull(board);
  25. var primary = Assert.Single(board.Lanes, lane => lane.CycleId == "2026-primary");
  26. Assert.Equal("2026 Primary", primary.CycleName);
  27. var fairview = Assert.Single(primary.Cards, card => card.JCode == "FAIR01");
  28. Assert.Equal("Fairview Display", fairview.MunicipalityName);
  29. Assert.Equal("Ready", fairview.CycleJobStatus);
  30. Assert.Equal("FAIR01", fairview.LegacyJoinKey);
  31. Assert.Equal("/election-cycles/jobs/job-fair01-primary", fairview.QuickOpenHref);
  32. var unassigned = Assert.Single(board.Lanes, lane => lane.CycleId == ElectionCycleKanbanReadModel.UnassignedCycleId);
  33. Assert.Contains(unassigned.Cards, card =>
  34. card.JCode == "PINE03" &&
  35. card.CycleName == "Unassigned" &&
  36. card.CycleJobStatus == "Unassigned");
  37. }
  38. [Fact]
  39. public async Task ReadModel_ReturnsOneCardPerMunicipalityCyclePair_AC2()
  40. {
  41. var legacy = new InMemoryLegacyDataAccess();
  42. var profiles = BuildProfileRepository(legacy);
  43. await profiles.CreateAsync("LAKE02", null, "test@example.test");
  44. var jobs = new InMemoryElectionCycleJobRepository(
  45. [
  46. new ElectionCycleJobAssignment(
  47. "job-lake-primary",
  48. "LAKE02",
  49. "2026-primary",
  50. "2026 Primary",
  51. "In progress",
  52. IsActive: true),
  53. new ElectionCycleJobAssignment(
  54. "job-lake-special",
  55. "LAKE02",
  56. "2026-special",
  57. "2026 Special",
  58. "Blocked",
  59. IsActive: true),
  60. ], TimeProvider.System);
  61. var sut = new ElectionCycleKanbanReadModel(profiles, jobs);
  62. var board = await sut.GetAsync();
  63. var lakeCards = board.Lanes.SelectMany(lane => lane.Cards)
  64. .Where(card => card.JCode == "LAKE02")
  65. .ToArray();
  66. Assert.Equal(2, lakeCards.Length);
  67. Assert.Contains(lakeCards, card => card.CycleId == "2026-primary");
  68. Assert.Contains(lakeCards, card => card.CycleId == "2026-special");
  69. }
  70. [Fact]
  71. public async Task ReadModel_ExcludesInactiveAssignmentsFromCycleLanes_AC1()
  72. {
  73. var legacy = new InMemoryLegacyDataAccess();
  74. var profiles = BuildProfileRepository(legacy);
  75. await profiles.CreateAsync("FAIR01", null, "test@example.test");
  76. var jobs = new InMemoryElectionCycleJobRepository(
  77. [
  78. new ElectionCycleJobAssignment(
  79. "job-fair-old",
  80. "FAIR01",
  81. "2024-general",
  82. "2024 General",
  83. "Complete",
  84. IsActive: false),
  85. ], TimeProvider.System);
  86. var sut = new ElectionCycleKanbanReadModel(profiles, jobs);
  87. var board = await sut.GetAsync();
  88. Assert.DoesNotContain(board.Lanes, lane => lane.CycleId == "2024-general");
  89. var unassigned = Assert.Single(board.Lanes, lane => lane.CycleId == ElectionCycleKanbanReadModel.UnassignedCycleId);
  90. Assert.Contains(unassigned.Cards, card => card.JCode == "FAIR01");
  91. }
  92. [Fact]
  93. public async Task GetKanban_RequiresClientServicesRole_AC1()
  94. {
  95. await using var factory = new AuthIntegrationTestFactory();
  96. using var noToken = factory.CreateClient();
  97. using var production = CreateClient(factory, "production");
  98. var missingToken = await noToken.GetAsync("/api/election-cycles/kanban");
  99. var wrongRole = await production.GetAsync("/api/election-cycles/kanban");
  100. Assert.Equal(HttpStatusCode.Unauthorized, missingToken.StatusCode);
  101. Assert.Equal(HttpStatusCode.Forbidden, wrongRole.StatusCode);
  102. }
  103. [Fact]
  104. public async Task ReadModel_UsesReadOnlyLegacyAndExtensionRepositories_AC1()
  105. {
  106. ILegacyDataAccess legacy = new InMemoryLegacyDataAccess(
  107. jurisdictions:
  108. [
  109. new("FAIR01", "Fairview Borough", "100 Main St", "Fairview, PA 16415", null, null),
  110. ]);
  111. var profiles = BuildProfileRepository(legacy);
  112. await profiles.CreateAsync("FAIR01", null, "test@example.test");
  113. var jobs = new InMemoryElectionCycleJobRepository([], TimeProvider.System);
  114. var sut = new ElectionCycleKanbanReadModel(profiles, jobs);
  115. var board = await sut.GetAsync();
  116. Assert.Contains(board.Lanes.Single(lane => lane.CycleId == ElectionCycleKanbanReadModel.UnassignedCycleId).Cards,
  117. card => card.JCode == "FAIR01");
  118. Assert.True(typeof(ILegacyDataAccess).GetMethods().All(method =>
  119. method.Name.StartsWith("Get", StringComparison.Ordinal)));
  120. }
  121. private static InMemoryMunicipalityProfileRepository BuildProfileRepository(
  122. ILegacyDataAccess legacy)
  123. => new(
  124. new LegacyLinkValidator(legacy),
  125. legacy,
  126. TimeProvider.System);
  127. private static HttpClient CreateClient(AuthIntegrationTestFactory factory, string role)
  128. {
  129. var client = factory.CreateClient();
  130. client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
  131. "Bearer", AuthIntegrationTestFactory.CreateToken("cs@example.test", role));
  132. return client;
  133. }
  134. private static async Task CreateProfile(
  135. HttpClient client,
  136. string jCode,
  137. string? displayName)
  138. {
  139. var response = await client.PostAsJsonAsync("/api/municipalities/profiles", new
  140. {
  141. jCode,
  142. displayName,
  143. });
  144. Assert.Equal(HttpStatusCode.OK, response.StatusCode);
  145. }
  146. private sealed record ElectionCycleKanbanDto(ElectionCycleKanbanLaneDto[] Lanes);
  147. private sealed record ElectionCycleKanbanLaneDto(
  148. string CycleId,
  149. string CycleName,
  150. ElectionCycleKanbanCardDto[] Cards);
  151. private sealed record ElectionCycleKanbanCardDto(
  152. string CardId,
  153. string MunicipalityName,
  154. string JCode,
  155. string CycleId,
  156. string CycleName,
  157. string CycleJobStatus,
  158. string LegacyJoinKey,
  159. string QuickOpenHref);
  160. }

Powered by TurnKey Linux.