package briefing import ( "strings" "testing" "gitea.maximumdirect.net/eric/weatherreporter/internal/facts" "gitea.maximumdirect.net/eric/weatherreporter/internal/module" "gitea.maximumdirect.net/eric/weatherreporter/internal/report" "gitea.maximumdirect.net/eric/weatherreporter/internal/weatherdata" ) func TestDefaultModuleRegistryValidatesReportDefaults(t *testing.T) { registry := MustDefaultModuleRegistry() for _, definition := range report.DefaultRegistry().All() { if err := registry.ValidateComposition(definition.ID, definition.Modules); err != nil { t.Fatalf("ValidateComposition(%s) error = %v", definition.ID, err) } for _, item := range definition.Modules { moduleDefinition, err := registry.Lookup(item.ID) if err != nil { t.Fatalf("Lookup(%s) error = %v", item.ID, err) } if moduleDefinition.Builder == nil { t.Fatalf("report %s module %s has no builder", definition.ID, item.ID) } } } } func TestDefaultReportModulesBuildSnapshots(t *testing.T) { registry := MustDefaultModuleRegistry() for _, definition := range report.DefaultRegistry().All() { t.Run(string(definition.ID), func(t *testing.T) { if len(definition.Modules) == 0 { return } ctx := derivedModuleContext(definition.ID) var outputs []module.Output for _, item := range definition.Modules { output, err := registry.BuildModule(ctx, item) if err != nil { t.Fatalf("BuildModule(%s) error = %v", item.ID, err) } if output != nil { outputs = append(outputs, *output) } } snapshot, err := module.NewSnapshot(outputs) if err != nil { t.Fatalf("NewSnapshot() error = %v", err) } if len(snapshot.Outputs) == 0 { t.Fatal("snapshot outputs = 0, want default report modules") } }) } } func TestModuleRegistryRejectsUnknownModule(t *testing.T) { registry := MustDefaultModuleRegistry() err := registry.ValidateComposition(report.DailyToday, []module.ConfigItem{{ID: module.ID("unknown")}}) if err == nil || !strings.Contains(err.Error(), `unknown module "unknown"`) { t.Fatalf("error = %v, want unknown module", err) } } func TestModuleRegistryRejectsDuplicateModuleIDs(t *testing.T) { registry := MustDefaultModuleRegistry() err := registry.ValidateComposition(report.DailyToday, []module.ConfigItem{ {ID: module.Metadata}, {ID: module.Metadata}, }) if err == nil || !strings.Contains(err.Error(), `duplicate module "metadata"`) { t.Fatalf("error = %v, want duplicate module", err) } } func TestModuleRegistryRejectsDuplicateStanzaNames(t *testing.T) { _, err := NewModuleRegistry([]ModuleDefinition{ {ID: module.Metadata, StanzaName: "metadata", DefaultOptions: module.MetadataOptions{}, Builder: noopModuleBuilder}, {ID: module.CurrentConditions, StanzaName: "metadata", DefaultOptions: module.CurrentConditionsOptions{}, Builder: noopModuleBuilder}, }) if err == nil || !strings.Contains(err.Error(), `duplicate stanza name "metadata"`) { t.Fatalf("error = %v, want duplicate stanza name", err) } } func TestModuleRegistryRejectsIncompatibleReports(t *testing.T) { registry := MustDefaultModuleRegistry() err := registry.ValidateComposition(report.DailyToday, []module.ConfigItem{{ID: module.TomorrowPlanning}}) if err == nil || !strings.Contains(err.Error(), `module "tomorrow_planning" is not compatible with report "daily_today"`) { t.Fatalf("error = %v, want incompatible report", err) } } func TestModuleRegistryRejectsDefinitionsWithoutBuilders(t *testing.T) { _, err := NewModuleRegistry([]ModuleDefinition{ {ID: module.Metadata, StanzaName: "metadata", DefaultOptions: module.MetadataOptions{}}, }) if err == nil || !strings.Contains(err.Error(), `module "metadata" has no builder`) { t.Fatalf("error = %v, want missing builder", err) } } func TestModuleRegistryRejectsUnsupportedMissingDataWarn(t *testing.T) { _, err := NewModuleRegistry([]ModuleDefinition{ {ID: module.Metadata, StanzaName: "metadata", DefaultOptions: module.MetadataOptions{}, MissingData: module.MissingDataWarn, Builder: noopModuleBuilder}, }) if err == nil || !strings.Contains(err.Error(), `unsupported missing data behavior`) { t.Fatalf("error = %v, want unsupported missing-data behavior", err) } } func TestModuleRegistryRejectsInvalidOptionShapes(t *testing.T) { registry := MustDefaultModuleRegistry() err := registry.ValidateComposition(report.DailyToday, []module.ConfigItem{ {ID: module.Metadata, Options: module.CurrentConditionsOptions{}}, }) if err == nil || !strings.Contains(err.Error(), `module "metadata" options have type module.CurrentConditionsOptions, want module.MetadataOptions`) { t.Fatalf("error = %v, want invalid option shape", err) } } func TestModuleRegistryAcceptsTypedOptions(t *testing.T) { registry := MustDefaultModuleRegistry() err := registry.ValidateComposition(report.DailyToday, []module.ConfigItem{ {ID: module.Metadata, Options: module.MetadataOptions{}}, {ID: module.CurrentConditions, Options: &module.CurrentConditionsOptions{}}, }) if err != nil { t.Fatalf("ValidateComposition() error = %v", err) } } func TestSPCConvectiveOutlookCollectedRequirementAvailability(t *testing.T) { ctx := ModuleContext{} if collectedFactAvailable(module.CollectedSPCConvectiveOutlooks, ctx) { t.Fatal("collectedFactAvailable() = true, want false without source") } ctx.Collected = facts.CollectedFacts{SPCConvectiveOutlooks: &weatherdata.ConvectiveOutlookRun{}} if !collectedFactAvailable(module.CollectedSPCConvectiveOutlooks, ctx) { t.Fatal("collectedFactAvailable() = false, want true with checked source") } } func TestSPCConvectiveOutlookDerivedRequirementAvailability(t *testing.T) { ctx := ModuleContext{} if derivedFactAvailable(module.RequiresDerivedSPCConvectiveOutlooks, ctx) { t.Fatal("derivedFactAvailable() = true, want false without derived outlooks") } ctx.Derived = facts.DerivedFacts{SPCConvectiveOutlooks: []weatherdata.ConvectiveOutlook{}} if !derivedFactAvailable(module.RequiresDerivedSPCConvectiveOutlooks, ctx) { t.Fatal("derivedFactAvailable() = false, want true for checked empty derived outlooks") } } func noopModuleBuilder(ModuleContext, any) (*module.Output, error) { return &module.Output{ID: module.Metadata, StanzaName: "metadata", Value: struct{}{}}, nil }