using BigRegister.Api.Data; using BigRegister.Domain.Diplomas; using BigRegister.Stamdata; namespace BigRegister.Tests; /// /// The compile-time gate for business-editable stamdata (ADR-0004). The C# compiler /// already catches shape/type mistakes; these tests catch the referential integrity it /// can't, so a bad config edit fails the build instead of reaching production. /// public class StamdataValidationTests { /// Declared references INTO stamdata keys — the FK-like invariants the build gate enforces /// (WP-48). Add an entry when a consumer starts depending on a stamdata key; the gate then /// fails a delete/rename/expire that orphans it. Resolvers use the "valid today" views, so /// expiring a row (geldigTot in the past) that current data still references also fails — /// which steers the editor toward closing validity only once nothing current relies on it. private sealed record StamdataRef(string Description, IEnumerable Keys, Func Resolves); private static readonly IReadOnlyList References = new[] { new StamdataRef( "Diploma.Opleiding → professions.program (valid today)", SeedData.Diplomas.Select(d => d.Opleiding), key => Professions.ByProgram.ContainsKey(key)), }; [Fact] public void Every_declared_reference_into_stamdata_resolves() { // The dangling-reference guard (generalized): a referenced key with no (currently valid) // stamdata row would silently break its consumer. Fail the build instead of prod. foreach (var r in References) foreach (var key in r.Keys) Assert.True(r.Resolves(key), $"Dangling stamdata reference [{r.Description}]: '{key}' no longer resolves — " + "deleting or expiring the referenced row would break it."); } [Fact] public void Profession_map_has_no_blank_programs_or_professions() { Assert.All(Professions.ByProgram, kv => { Assert.False(string.IsNullOrWhiteSpace(kv.Key), "A profession-map program key is blank."); Assert.False(string.IsNullOrWhiteSpace(kv.Value), $"Program '{kv.Key}' maps to a blank profession."); }); } [Fact] public void Manual_professions_are_non_empty_and_distinct() { var professions = DiplomaRules.ManualProfessions(); Assert.NotEmpty(professions); Assert.Equal(professions.Count, professions.Distinct().Count()); } [Fact] public void Every_policy_question_has_an_id_and_wording() { Assert.All(PolicyQuestions.All, q => { Assert.False(string.IsNullOrWhiteSpace(q.Id), "A policy question has a blank id."); Assert.False(string.IsNullOrWhiteSpace(q.Vraag), $"Policy question '{q.Id}' has blank wording."); }); } [Fact] public void Manual_question_set_has_distinct_ids() // no field is asked twice { var ids = PolicyQuestions.ManualSet.Select(q => q.Id).ToList(); Assert.Equal(ids.Count, ids.Distinct().Count()); } // The GENERIC gate (ADR-0004): every table registered in the catalog is validated the // same way — its JSON deserializes into its typed record, keys are non-blank and don't // overlap in time, and valid-time windows are well-formed. A new stamdata type is covered // the moment it's added to StamdataCatalog; no new test needed. A bad edit fails the build. [Fact] public void Every_catalog_table_is_valid() { foreach (var table in StamdataCatalog.All) Assert.True(table.Validate().Count == 0, $"Stamdata table '{table.Id}' has problems: {string.Join("; ", table.Validate())}"); } }