using Campaign_Tracker.Server.Seed; using Campaign_Tracker.Server.Seed.Models; namespace Campaign_Tracker.Server.Tests; public sealed class SeedServiceTests { private static readonly DateTimeOffset FixedNow = new(2026, 5, 6, 12, 0, 0, TimeSpan.Zero); [Fact] public async Task IsSeededAsync_ReportsFalseBeforeSeedAndTrueAfterSeed_AC1_AC4() { var store = new InMemorySeedDataStore(); var sut = BuildSut(store); Assert.False(await sut.IsSeededAsync()); await sut.SeedAsync(); Assert.True(await sut.IsSeededAsync()); } [Fact] public async Task SeedAsync_PopulatesReferenceValuesRequiredRulesAndEscalationDefaults_AC1_AC2_AC3() { var store = new InMemorySeedDataStore(); var sut = BuildSut(store); await sut.SeedAsync(); var referenceValues = await store.GetReferenceValuesAsync(); var requiredRules = await store.GetRequiredFieldRulesAsync(); var escalationRules = await store.GetEscalationRulesAsync(); Assert.Contains(referenceValues, value => value.Category == "OperationalStatus" && value.Value == "blocked"); Assert.Contains(referenceValues, value => value.Category == "ServiceTemplate" && value.Value == "transportation"); Assert.Contains(referenceValues, value => value.Category == "ElectionCycleType" && value.Value == "general"); Assert.Contains(requiredRules, rule => rule.EntityType == "ElectionCycleJob" && rule.FieldPath == "electionDate" && rule.ReadinessFeatureKey == "FR29.ReadinessStatus" && rule.IsRequired); Assert.Contains(escalationRules, rule => rule.Scenario == "OverdueMilestoneAlert" && rule.Action == "NotifyOperationsLead" && rule.TriggerCondition.Contains("dueDate", StringComparison.OrdinalIgnoreCase)); } [Fact] public async Task SeedAsync_IsFullyIdempotentAndCreatesNoDuplicateSeedKeys_AC4() { var store = new InMemorySeedDataStore(); var sut = BuildSut(store); await sut.SeedAsync(); await sut.SeedAsync(); var referenceValues = await store.GetReferenceValuesAsync(); var requiredRules = await store.GetRequiredFieldRulesAsync(); var escalationRules = await store.GetEscalationRulesAsync(); Assert.Equal(referenceValues.Count, referenceValues.Select(value => value.SeedKey).Distinct(StringComparer.OrdinalIgnoreCase).Count()); Assert.Equal(requiredRules.Count, requiredRules.Select(rule => rule.SeedKey).Distinct(StringComparer.OrdinalIgnoreCase).Count()); Assert.Equal(escalationRules.Count, escalationRules.Select(rule => rule.SeedKey).Distinct(StringComparer.OrdinalIgnoreCase).Count()); } [Fact] public async Task SeedAsync_DoesNotOverwriteAdminManagedValuesOnRerun_AC5() { var store = new InMemorySeedDataStore(); var sut = BuildSut(store); await sut.SeedAsync(); var template = (await store.GetReferenceValuesAsync()) .Single(value => value.SeedKey == "service-template.addressing"); template.Name = "Custom Addressing Template"; template.Value = "custom-addressing"; template.Source = SeedRecordSource.AdminManaged; template.UpdatedAt = FixedNow.AddHours(1); await store.SaveReferenceValueAsync(template); await sut.SeedAsync(); var persisted = (await store.GetReferenceValuesAsync()) .Single(value => value.SeedKey == "service-template.addressing"); Assert.Equal("Custom Addressing Template", persisted.Name); Assert.Equal("custom-addressing", persisted.Value); Assert.Equal(SeedRecordSource.AdminManaged, persisted.Source); Assert.Equal(FixedNow.AddHours(1), persisted.UpdatedAt); } [Fact] public async Task RequiredFieldRules_AreScopedForEpicTwoReadinessEvaluation_AC2() { var store = new InMemorySeedDataStore(); var sut = BuildSut(store); await sut.SeedAsync(); var rules = await store.GetRequiredFieldRulesAsync(); Assert.All(rules, rule => { Assert.Equal("ElectionCycleJob", rule.EntityType); Assert.Equal("FR29.ReadinessStatus", rule.ReadinessFeatureKey); Assert.False(string.IsNullOrWhiteSpace(rule.FieldPath)); }); Assert.Contains(rules, rule => rule.FieldPath == "mailDate"); Assert.Contains(rules, rule => rule.FieldPath == "legacyJurisdictionJCode"); } [Fact] public async Task FileSeedDataStore_PersistsSeededDefaultsAcrossInstances_AC1() { var path = Path.Combine(Path.GetTempPath(), $"campaign-tracker-seed-{Guid.NewGuid():N}.json"); try { var firstStore = new FileSeedDataStore(path); var firstService = BuildSut(firstStore); await firstService.SeedAsync(); var secondStore = new FileSeedDataStore(path); var secondService = BuildSut(secondStore); Assert.True(await secondService.IsSeededAsync()); Assert.Contains(await secondStore.GetEscalationRulesAsync(), rule => rule.SeedKey == "escalation.overdue-milestone.operations-lead"); } finally { if (File.Exists(path)) { File.Delete(path); } } } private static SeedService BuildSut(ISeedDataStore store) => new(store, new FakeTimeProvider(FixedNow)); private sealed class FakeTimeProvider(DateTimeOffset utcNow) : TimeProvider { public override DateTimeOffset GetUtcNow() => utcNow; } }