Validate fact requirements through briefing vocabulary

This commit is contained in:
2026-08-13 04:02:21 +00:00
parent 2c4c0bbd90
commit 71b7a74d3d
4 changed files with 167 additions and 93 deletions

View File

@@ -31,7 +31,7 @@ func TestDefaultModuleRegistryValidatesReportDefaults(t *testing.T) {
}
func TestFactRequirementVocabularyMatchesModuleDefinitions(t *testing.T) {
vocabulary := make(map[string]factRequirement, len(factRequirementVocabulary))
vocabulary := make(map[string]*factRequirement, len(factRequirementVocabulary))
for _, requirement := range factRequirementVocabulary {
if requirement.identity == "" {
t.Fatal("fact requirement identity is empty")
@@ -42,10 +42,13 @@ func TestFactRequirementVocabularyMatchesModuleDefinitions(t *testing.T) {
if _, exists := vocabulary[requirement.identity]; exists {
t.Fatalf("duplicate fact requirement %q", requirement.identity)
}
if requirement.available == nil {
t.Fatalf("fact requirement %q has no availability predicate", requirement.identity)
}
vocabulary[requirement.identity] = requirement
}
used := map[factRequirement]struct{}{}
used := map[*factRequirement]struct{}{}
for _, definition := range defaultModuleDefinitions() {
for _, requirement := range definition.RequiredCollected {
assertFactRequirementCategory(t, vocabulary, used, definition.ID, requirement, collectedFactRequirement)
@@ -61,7 +64,7 @@ func TestFactRequirementVocabularyMatchesModuleDefinitions(t *testing.T) {
}
}
func assertFactRequirementCategory(t *testing.T, vocabulary map[string]factRequirement, used map[factRequirement]struct{}, moduleID module.ID, requirement factRequirement, want factRequirementCategory) {
func assertFactRequirementCategory(t *testing.T, vocabulary map[string]*factRequirement, used map[*factRequirement]struct{}, moduleID module.ID, requirement *factRequirement, want factRequirementCategory) {
t.Helper()
descriptor, ok := vocabulary[requirement.identity]
if !ok {
@@ -480,6 +483,52 @@ func TestModuleRegistryRejectsDefinitionsWithoutBuilders(t *testing.T) {
}
}
func TestModuleRegistryRejectsInvalidFactRequirements(t *testing.T) {
tests := []struct {
name string
configure func(*ModuleDefinition)
wantErr string
}{
{
name: "Unknown",
configure: func(definition *ModuleDefinition) {
definition.RequiredCollected = []*factRequirement{{identity: "collected.unknown", category: collectedFactRequirement}}
},
wantErr: `module "metadata" uses unknown collected fact requirement "collected.unknown"`,
},
{
name: "DerivedListedAsCollected",
configure: func(definition *ModuleDefinition) {
definition.RequiredCollected = []*factRequirement{dailySummariesRequirement}
},
wantErr: `module "metadata" lists derived fact requirement "derived.daily_summaries" as collected`,
},
{
name: "CollectedListedAsDerived",
configure: func(definition *ModuleDefinition) {
definition.RequiredDerived = []*factRequirement{currentConditionsRequirement}
},
wantErr: `module "metadata" lists collected fact requirement "collected.current_conditions" as derived`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
definition := ModuleDefinition{
ID: module.Metadata,
StanzaName: "metadata",
DefaultOptions: module.MetadataOptions{},
Builder: noopModuleBuilder,
}
tt.configure(&definition)
_, err := NewModuleRegistry([]ModuleDefinition{definition})
if err == nil || err.Error() != tt.wantErr {
t.Fatalf("error = %v, want %q", err, tt.wantErr)
}
})
}
}
func TestModuleRegistryRejectsUnsupportedMissingDataWarn(t *testing.T) {
_, err := NewModuleRegistry([]ModuleDefinition{
{ID: module.Metadata, StanzaName: "metadata", DefaultOptions: module.MetadataOptions{}, MissingData: module.MissingDataWarn, Builder: noopModuleBuilder},
@@ -513,25 +562,25 @@ func TestModuleRegistryAcceptsTypedOptions(t *testing.T) {
func TestSPCConvectiveOutlookCollectedRequirementAvailability(t *testing.T) {
ctx := ModuleContext{}
if collectedFactAvailable(spcOutlooksRequirement, ctx) {
t.Fatal("collectedFactAvailable() = true, want false without source")
if spcOutlooksRequirement.available(ctx) {
t.Fatal("SPC outlook requirement is available, want false without source")
}
ctx.Collected = facts.CollectedFacts{SPCConvectiveOutlooks: &weatherdata.ConvectiveOutlookRun{}}
if !collectedFactAvailable(spcOutlooksRequirement, ctx) {
t.Fatal("collectedFactAvailable() = false, want true with checked source")
if !spcOutlooksRequirement.available(ctx) {
t.Fatal("SPC outlook requirement is unavailable, want true with checked source")
}
}
func TestSPCConvectiveOutlookDerivedRequirementAvailability(t *testing.T) {
ctx := ModuleContext{}
if derivedFactAvailable(spcDerivedOutlooksRequirement, ctx) {
t.Fatal("derivedFactAvailable() = true, want false without derived outlooks")
if spcDerivedOutlooksRequirement.available(ctx) {
t.Fatal("derived SPC outlook requirement is available, want false without derived outlooks")
}
ctx.Derived = facts.DerivedFacts{SPCConvectiveOutlooks: []weatherdata.ConvectiveOutlook{}}
if !derivedFactAvailable(spcDerivedOutlooksRequirement, ctx) {
t.Fatal("derivedFactAvailable() = false, want true for checked empty derived outlooks")
if !spcDerivedOutlooksRequirement.available(ctx) {
t.Fatal("derived SPC outlook requirement is unavailable, want true for checked empty derived outlooks")
}
}