Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

215 lines
9.2KB

  1. using System.Net;
  2. using System.Net.Http.Headers;
  3. using System.Net.Http.Json;
  4. using Campaign_Tracker.Server.Audit;
  5. using Microsoft.Extensions.DependencyInjection;
  6. namespace Campaign_Tracker.Server.Tests;
  7. public sealed class MunicipalityProfileControllerTests
  8. {
  9. // ── AC #1: profile created and saved with legacy link ────────────────────
  10. [Fact]
  11. public async Task CreateProfile_ValidJCode_Returns200WithCombinedView_AC1_AC2()
  12. {
  13. await using var factory = new AuthIntegrationTestFactory();
  14. using var client = factory.CreateClient();
  15. client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
  16. "Bearer", AuthIntegrationTestFactory.CreateToken("cs@example.test", "client-services"));
  17. var response = await client.PostAsJsonAsync("/api/municipalities/profiles", new
  18. {
  19. jCode = "FAIR01",
  20. displayName = "Fairview Borough Profile",
  21. });
  22. Assert.Equal(HttpStatusCode.OK, response.StatusCode);
  23. var body = await response.Content.ReadFromJsonAsync<MunicipalityProfileDto>();
  24. Assert.NotNull(body);
  25. Assert.Equal("FAIR01", body.JCode);
  26. Assert.Equal("Fairview Borough Profile", body.DisplayName);
  27. // AC #2: combined view includes resolved legacy name
  28. Assert.Equal("Fairview Borough", body.LegacyName);
  29. }
  30. // ── AC #2: list returns combined extension + legacy fields ────────────────
  31. [Fact]
  32. public async Task GetAllProfiles_ReturnsResolvedLegacyData_AC2()
  33. {
  34. await using var factory = new AuthIntegrationTestFactory();
  35. using var client = factory.CreateClient();
  36. client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
  37. "Bearer", AuthIntegrationTestFactory.CreateToken("cs@example.test", "client-services"));
  38. await client.PostAsJsonAsync("/api/municipalities/profiles", new { jCode = "LAKE02", displayName = (string?)null });
  39. var response = await client.GetAsync("/api/municipalities/profiles");
  40. Assert.Equal(HttpStatusCode.OK, response.StatusCode);
  41. var profiles = await response.Content.ReadFromJsonAsync<MunicipalityProfileDto[]>();
  42. Assert.NotNull(profiles);
  43. var lake = Assert.Single(profiles, p => p.JCode == "LAKE02");
  44. Assert.Equal("Lake Township", lake.LegacyName);
  45. }
  46. // ── AC #3: update audits the change (server-side; response includes actor) ─
  47. [Fact]
  48. public async Task UpdateProfile_ChangesDisplayName_Returns200_AC3()
  49. {
  50. await using var factory = new AuthIntegrationTestFactory();
  51. using var client = factory.CreateClient();
  52. client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
  53. "Bearer", AuthIntegrationTestFactory.CreateToken("cs@example.test", "client-services"));
  54. var created = await (await client.PostAsJsonAsync("/api/municipalities/profiles",
  55. new { jCode = "FAIR01", displayName = "Old Name" }))
  56. .Content.ReadFromJsonAsync<MunicipalityProfileDto>();
  57. var response = await client.PutAsJsonAsync(
  58. $"/api/municipalities/profiles/{created!.ProfileId}",
  59. new { displayName = "New Name" });
  60. Assert.Equal(HttpStatusCode.OK, response.StatusCode);
  61. var updated = await response.Content.ReadFromJsonAsync<MunicipalityProfileDto>();
  62. Assert.Equal("New Name", updated!.DisplayName);
  63. }
  64. // ── AC #4: invalid JCode rejected before save ─────────────────────────────
  65. [Fact]
  66. public async Task CreateProfile_InvalidJCode_Returns422WithDescription_AC4()
  67. {
  68. await using var factory = new AuthIntegrationTestFactory();
  69. using var client = factory.CreateClient();
  70. client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
  71. "Bearer", AuthIntegrationTestFactory.CreateToken("cs@example.test", "client-services"));
  72. var response = await client.PostAsJsonAsync("/api/municipalities/profiles", new
  73. {
  74. jCode = "DOESNOTEXIST",
  75. displayName = (string?)null,
  76. });
  77. Assert.Equal(HttpStatusCode.UnprocessableEntity, response.StatusCode);
  78. var body = await response.Content.ReadFromJsonAsync<MunicipalityProfileProblemDto>();
  79. Assert.NotNull(body);
  80. Assert.Contains("DOESNOTEXIST", body.Error);
  81. }
  82. // ── Authorization ─────────────────────────────────────────────────────────
  83. [Fact]
  84. public async Task CreateProfile_NoToken_Returns401()
  85. {
  86. await using var factory = new AuthIntegrationTestFactory();
  87. using var client = factory.CreateClient();
  88. var response = await client.PostAsJsonAsync("/api/municipalities/profiles",
  89. new { jCode = "FAIR01", displayName = (string?)null });
  90. Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
  91. }
  92. [Fact]
  93. public async Task CreateProfile_WrongRoleToken_Returns403()
  94. {
  95. await using var factory = new AuthIntegrationTestFactory();
  96. using var client = factory.CreateClient();
  97. client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
  98. "Bearer", AuthIntegrationTestFactory.CreateToken("prod@example.test", "production"));
  99. var response = await client.PostAsJsonAsync("/api/municipalities/profiles",
  100. new { jCode = "FAIR01", displayName = (string?)null });
  101. Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
  102. }
  103. [Fact]
  104. public async Task GetJurisdictions_NoToken_Returns401()
  105. {
  106. await using var factory = new AuthIntegrationTestFactory();
  107. using var client = factory.CreateClient();
  108. var response = await client.GetAsync("/api/municipalities/jurisdictions");
  109. Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
  110. }
  111. // ── AC #3: audit events recorded on create and update ────────────────────
  112. [Fact]
  113. public async Task CreateProfile_RecordsAuditEvent_AC3()
  114. {
  115. await using var factory = new AuthIntegrationTestFactory();
  116. using var client = factory.CreateClient();
  117. client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
  118. "Bearer", AuthIntegrationTestFactory.CreateToken("cs@example.test", "client-services"));
  119. await client.PostAsJsonAsync("/api/municipalities/profiles",
  120. new { jCode = "FAIR01", displayName = "Fairview" });
  121. var auditService = factory.Services.GetRequiredService<IAuditService>();
  122. var events = auditService.GetRecent();
  123. Assert.Contains(events, e =>
  124. e.EventType == "MUNICIPALITY_PROFILE_CREATED" &&
  125. e.ActorIdentity == "cs@example.test");
  126. }
  127. [Fact]
  128. public async Task UpdateProfile_RecordsAuditEvent_AC3()
  129. {
  130. await using var factory = new AuthIntegrationTestFactory();
  131. using var client = factory.CreateClient();
  132. client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
  133. "Bearer", AuthIntegrationTestFactory.CreateToken("cs@example.test", "client-services"));
  134. var created = await (await client.PostAsJsonAsync("/api/municipalities/profiles",
  135. new { jCode = "LAKE02", displayName = (string?)null }))
  136. .Content.ReadFromJsonAsync<MunicipalityProfileDto>();
  137. await client.PutAsJsonAsync(
  138. $"/api/municipalities/profiles/{created!.ProfileId}",
  139. new { displayName = "Updated Name" });
  140. var auditService = factory.Services.GetRequiredService<IAuditService>();
  141. var events = auditService.GetRecent();
  142. Assert.Contains(events, e =>
  143. e.EventType == "MUNICIPALITY_PROFILE_UPDATED" &&
  144. e.ActorIdentity == "cs@example.test");
  145. }
  146. // ── Update not-found returns 404 ─────────────────────────────────────────
  147. [Fact]
  148. public async Task UpdateProfile_UnknownId_Returns404()
  149. {
  150. await using var factory = new AuthIntegrationTestFactory();
  151. using var client = factory.CreateClient();
  152. client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
  153. "Bearer", AuthIntegrationTestFactory.CreateToken("cs@example.test", "client-services"));
  154. var response = await client.PutAsJsonAsync(
  155. "/api/municipalities/profiles/does-not-exist",
  156. new { displayName = "X" });
  157. Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
  158. }
  159. // ── Local DTOs for deserialization ────────────────────────────────────────
  160. private sealed record MunicipalityProfileDto(
  161. string ProfileId,
  162. string JCode,
  163. string? DisplayName,
  164. string UpdatedAt,
  165. string UpdatedBy,
  166. string? LegacyName,
  167. string? LegacyMailingAddress,
  168. string? LegacyCityStateZip);
  169. private sealed record MunicipalityProfileProblemDto(string Error);
  170. }

Powered by TurnKey Linux.