using Campaign_Tracker.Server.Municipalities; namespace Campaign_Tracker.Server.Tests; public sealed class MunicipalityContactRepositoryTests { private static readonly DateTimeOffset FixedNow = new(2026, 5, 7, 12, 0, 0, TimeSpan.Zero); private static InMemoryMunicipalityContactRepository BuildSut() => new(new FakeTimeProvider(FixedNow)); [Fact] public async Task AddAsync_WithPrimaryContact_SavesExtensionContact_AC1() { var sut = BuildSut(); var result = await sut.AddAsync( "profile-1", "Primary", "Ada Clerk", "Town Clerk", "555-0100", "ada@example.test", "actor@example.test"); Assert.True(result.Saved); Assert.NotNull(result.Contact); Assert.Equal("Primary", result.Contact.ContactType); Assert.Equal("Ada Clerk", result.Contact.Name); Assert.Equal("Town Clerk", result.Contact.RoleTitle); Assert.Equal("555-0100", result.Contact.Phone); Assert.Equal("ada@example.test", result.Contact.Email); Assert.Equal("actor@example.test", result.Contact.UpdatedBy); Assert.Equal(FixedNow, result.Contact.UpdatedAt); } [Fact] public async Task GetByProfileIdAsync_ReturnsPrimaryBeforeSecondaryWithLabels_AC2() { var sut = BuildSut(); await sut.AddAsync("profile-1", "Secondary", "Backup Clerk", null, null, null, "actor"); await sut.AddAsync("profile-1", "Primary", "Main Clerk", null, null, null, "actor"); var contacts = await sut.GetByProfileIdAsync("profile-1"); Assert.Collection(contacts, primary => { Assert.Equal("Primary", primary.ContactType); Assert.Equal("Main Clerk", primary.Name); }, secondary => { Assert.Equal("Secondary", secondary.ContactType); Assert.Equal("Backup Clerk", secondary.Name); }); } [Fact] public async Task UpdateAsync_ChangesContactAndCapturesActor_AC3() { var sut = BuildSut(); var created = await sut.AddAsync("profile-1", "Primary", "Old", null, null, null, "creator"); var updated = await sut.UpdateAsync( created.Contact!.ContactId, "Secondary", "New", "Manager", "555-0101", "new@example.test", "updater@example.test"); Assert.True(updated.Saved); Assert.Equal("Secondary", updated.Contact!.ContactType); Assert.Equal("New", updated.Contact.Name); Assert.Equal("updater@example.test", updated.Contact.UpdatedBy); Assert.Equal(FixedNow, updated.Contact.UpdatedAt); } [Fact] public async Task SoftDeleteAsync_RemovesContactFromCurrentView_AC3() { var sut = BuildSut(); var created = await sut.AddAsync("profile-1", "Primary", "Ada", null, null, null, "actor"); var deleted = await sut.SoftDeleteAsync(created.Contact!.ContactId, "deleter@example.test"); var contacts = await sut.GetByProfileIdAsync("profile-1"); Assert.True(deleted.Saved); Assert.Empty(contacts); } [Theory] [InlineData("", "Primary", "Name is required.")] [InlineData("Ada", "", "Contact type is required.")] [InlineData("Ada", "Emergency", "Contact type must be Primary or Secondary.")] public async Task AddAsync_InvalidRequiredFields_ReturnsFailure_AC4( string name, string contactType, string expectedError) { var sut = BuildSut(); var result = await sut.AddAsync("profile-1", contactType, name, null, null, null, "actor"); Assert.False(result.Saved); Assert.Equal(expectedError, result.Error); } private sealed class FakeTimeProvider(DateTimeOffset utcNow) : TimeProvider { public override DateTimeOffset GetUtcNow() => utcNow; } }