|
- using System.Net;
- using System.Net.Http.Headers;
- using System.Net.Http.Json;
- using Campaign_Tracker.Server.Audit;
- using Microsoft.Extensions.DependencyInjection;
-
- namespace Campaign_Tracker.Server.Tests;
-
- public sealed class MunicipalityContactControllerTests
- {
- [Fact]
- public async Task AddContact_ValidRequest_Returns201WithContactTypeAndDetails_AC1()
- {
- await using var factory = new AuthIntegrationTestFactory();
- using var client = CreateClient(factory);
- var profile = await CreateProfile(client);
-
- var response = await client.PostAsJsonAsync(
- $"/api/municipalities/{profile.ProfileId}/contacts",
- new
- {
- contactType = "Primary",
- name = "Ada Clerk",
- roleTitle = "Town Clerk",
- phone = "555-0100",
- email = "ada@example.test",
- });
-
- Assert.Equal(HttpStatusCode.Created, response.StatusCode);
- var body = await response.Content.ReadFromJsonAsync<MunicipalityContactDto>();
- Assert.NotNull(body);
- Assert.Equal("Primary", body.ContactType);
- Assert.Equal("Ada Clerk", body.Name);
- Assert.Equal("Town Clerk", body.RoleTitle);
- Assert.Equal("555-0100", body.Phone);
- Assert.Equal("ada@example.test", body.Email);
- }
-
- [Fact]
- public async Task GetContacts_DisplaysPrimaryAndSecondaryDesignations_AC2()
- {
- await using var factory = new AuthIntegrationTestFactory();
- using var client = CreateClient(factory);
- var profile = await CreateProfile(client);
-
- await client.PostAsJsonAsync(
- $"/api/municipalities/{profile.ProfileId}/contacts",
- new { contactType = "Secondary", name = "Backup Clerk" });
- await client.PostAsJsonAsync(
- $"/api/municipalities/{profile.ProfileId}/contacts",
- new { contactType = "Primary", name = "Main Clerk" });
-
- var response = await client.GetAsync($"/api/municipalities/{profile.ProfileId}/contacts");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var body = await response.Content.ReadFromJsonAsync<MunicipalityContactDto[]>();
- Assert.NotNull(body);
- Assert.Equal(["Primary", "Secondary"], body.Select(c => c.ContactType).ToArray());
- }
-
- [Fact]
- public async Task UpdateAndDeleteContact_RecordAuditEvents_AC3()
- {
- await using var factory = new AuthIntegrationTestFactory();
- using var client = CreateClient(factory);
- var profile = await CreateProfile(client);
- var created = await (await client.PostAsJsonAsync(
- $"/api/municipalities/{profile.ProfileId}/contacts",
- new { contactType = "Primary", name = "Ada Clerk" }))
- .Content.ReadFromJsonAsync<MunicipalityContactDto>();
-
- var updateResponse = await client.PutAsJsonAsync(
- $"/api/municipalities/{profile.ProfileId}/contacts/{created!.ContactId}",
- new { contactType = "Secondary", name = "Ada Updated", roleTitle = "Manager" });
- var deleteResponse = await client.DeleteAsync(
- $"/api/municipalities/{profile.ProfileId}/contacts/{created.ContactId}");
-
- Assert.Equal(HttpStatusCode.OK, updateResponse.StatusCode);
- Assert.Equal(HttpStatusCode.NoContent, deleteResponse.StatusCode);
- var auditService = factory.Services.GetRequiredService<IAuditService>();
- var events = auditService.GetRecent();
- Assert.Contains(events, e =>
- e.EventType == "MUNICIPALITY_CONTACT_UPDATED" &&
- e.ActorIdentity == "cs@example.test");
- Assert.Contains(events, e =>
- e.EventType == "MUNICIPALITY_CONTACT_DELETED" &&
- e.ActorIdentity == "cs@example.test");
- }
-
- [Theory]
- [InlineData("", "Primary", "Name is required.")]
- [InlineData("Ada Clerk", "", "Contact type is required.")]
- public async Task AddContact_MissingRequiredFields_Returns422Problem_AC4(
- string name,
- string contactType,
- string expectedError)
- {
- await using var factory = new AuthIntegrationTestFactory();
- using var client = CreateClient(factory);
- var profile = await CreateProfile(client);
-
- var response = await client.PostAsJsonAsync(
- $"/api/municipalities/{profile.ProfileId}/contacts",
- new { contactType, name });
-
- Assert.Equal(HttpStatusCode.UnprocessableEntity, response.StatusCode);
- var problem = await response.Content.ReadFromJsonAsync<MunicipalityContactProblemDto>();
- Assert.NotNull(problem);
- Assert.Equal(expectedError, problem.Error);
- }
-
- [Fact]
- public async Task AddContact_UnknownProfile_Returns404()
- {
- await using var factory = new AuthIntegrationTestFactory();
- using var client = CreateClient(factory);
-
- var response = await client.PostAsJsonAsync(
- "/api/municipalities/does-not-exist/contacts",
- new { contactType = "Primary", name = "Ada Clerk" });
-
- Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
- }
-
- [Fact]
- public async Task ContactRoutes_RejectContactFromDifferentProfile()
- {
- await using var factory = new AuthIntegrationTestFactory();
- using var client = CreateClient(factory);
- var profileA = await CreateProfile(client, "FAIR01");
- var profileB = await CreateProfile(client, "LAKE02");
- var created = await (await client.PostAsJsonAsync(
- $"/api/municipalities/{profileA.ProfileId}/contacts",
- new { contactType = "Primary", name = "Ada Clerk" }))
- .Content.ReadFromJsonAsync<MunicipalityContactDto>();
-
- var getResponse = await client.GetAsync(
- $"/api/municipalities/{profileB.ProfileId}/contacts/{created!.ContactId}");
- var updateResponse = await client.PutAsJsonAsync(
- $"/api/municipalities/{profileB.ProfileId}/contacts/{created.ContactId}",
- new { contactType = "Secondary", name = "Wrong Profile" });
- var deleteResponse = await client.DeleteAsync(
- $"/api/municipalities/{profileB.ProfileId}/contacts/{created.ContactId}");
-
- Assert.Equal(HttpStatusCode.NotFound, getResponse.StatusCode);
- Assert.Equal(HttpStatusCode.NotFound, updateResponse.StatusCode);
- Assert.Equal(HttpStatusCode.NotFound, deleteResponse.StatusCode);
-
- var originalProfileResponse = await client.GetAsync(
- $"/api/municipalities/{profileA.ProfileId}/contacts/{created.ContactId}");
- Assert.Equal(HttpStatusCode.OK, originalProfileResponse.StatusCode);
- }
-
- private static HttpClient CreateClient(AuthIntegrationTestFactory factory)
- {
- var client = factory.CreateClient();
- client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
- "Bearer", AuthIntegrationTestFactory.CreateToken("cs@example.test", "client-services"));
- return client;
- }
-
- private static async Task<MunicipalityProfileDto> CreateProfile(
- HttpClient client,
- string jCode = "FAIR01")
- {
- var created = await (await client.PostAsJsonAsync("/api/municipalities/profiles", new
- {
- jCode,
- displayName = $"{jCode} Profile",
- })).Content.ReadFromJsonAsync<MunicipalityProfileDto>();
-
- Assert.NotNull(created);
- return created;
- }
-
- private sealed record MunicipalityProfileDto(string ProfileId);
-
- private sealed record MunicipalityContactDto(
- string ContactId,
- string ProfileId,
- string ContactType,
- string Name,
- string? RoleTitle,
- string? Phone,
- string? Email,
- string UpdatedAt,
- string UpdatedBy);
-
- private sealed record MunicipalityContactProblemDto(string Error);
- }
|