Normalize briefing module options and weather stories
This commit is contained in:
@@ -462,19 +462,58 @@ func TestAreaForecastDiscussionModuleCanSelectSections(t *testing.T) {
|
||||
registry := MustDefaultModuleRegistry()
|
||||
ctx := testModuleContext()
|
||||
|
||||
output, err := registry.BuildModule(ctx, module.ConfigItem{
|
||||
ID: module.AreaForecastDiscussion,
|
||||
Options: module.AreaForecastDiscussionOptions{Sections: []string{"short_term"}},
|
||||
})
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
options any
|
||||
}{
|
||||
{name: "value", options: module.AreaForecastDiscussionOptions{Sections: []string{"short_term"}}},
|
||||
{name: "pointer", options: &module.AreaForecastDiscussionOptions{Sections: []string{"short_term"}}},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
output, err := registry.BuildModule(ctx, module.ConfigItem{
|
||||
ID: module.AreaForecastDiscussion,
|
||||
Options: tt.options,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("BuildModule() error = %v", err)
|
||||
}
|
||||
afd := moduleValue[AreaForecastDiscussionModule](t, output)
|
||||
if afd.ShortTerm != "Showers increase this afternoon." {
|
||||
t.Fatalf("ShortTerm = %q, want selected short term section", afd.ShortTerm)
|
||||
}
|
||||
if afd.Product != "" || len(afd.KeyMessages) != 0 || afd.LongTerm != "" {
|
||||
t.Fatalf("AFD = %#v, want only short_term section", afd)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWeatherStoryModuleOmitsEmptyContent(t *testing.T) {
|
||||
registry := MustDefaultModuleRegistry()
|
||||
ctx := testModuleContext()
|
||||
ctx.Collected.WeatherStory = &weatherdata.WeatherStory{OfficeID: "LSX", Priority: true, Order: 1}
|
||||
|
||||
output, err := registry.BuildModule(ctx, module.ConfigItem{ID: module.WeatherStory})
|
||||
if err != nil {
|
||||
t.Fatalf("BuildModule() error = %v", err)
|
||||
}
|
||||
afd := moduleValue[AreaForecastDiscussionModule](t, output)
|
||||
if afd.ShortTerm != "Showers increase this afternoon." {
|
||||
t.Fatalf("ShortTerm = %q, want selected short term section", afd.ShortTerm)
|
||||
if output != nil {
|
||||
t.Fatalf("output = %#v, want omitted weather story", output)
|
||||
}
|
||||
if afd.Product != "" || len(afd.KeyMessages) != 0 || afd.LongTerm != "" {
|
||||
t.Fatalf("AFD = %#v, want only short_term section", afd)
|
||||
}
|
||||
|
||||
func TestWeatherStoryModulePreservesZeroPriorityAndOrder(t *testing.T) {
|
||||
registry := MustDefaultModuleRegistry()
|
||||
ctx := testModuleContext()
|
||||
ctx.Collected.WeatherStory = &weatherdata.WeatherStory{Title: "Rain Chances"}
|
||||
|
||||
output, err := registry.BuildModule(ctx, module.ConfigItem{ID: module.WeatherStory})
|
||||
if err != nil {
|
||||
t.Fatalf("BuildModule() error = %v", err)
|
||||
}
|
||||
story := moduleValue[WeatherStoryModule](t, output)
|
||||
if !story.Available || story.Priority || story.Order != 0 {
|
||||
t.Fatalf("WeatherStory = %#v, want available story with zero priority and order", story)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,8 @@ func (r ModuleRegistry) BuildModule(ctx ModuleContext, item module.ConfigItem) (
|
||||
if !definition.SupportsReport(ctx.Resolved.Definition.ID) {
|
||||
return nil, fmt.Errorf("module %q is not compatible with report %q", item.ID, ctx.Resolved.Definition.ID)
|
||||
}
|
||||
if err := definition.ValidateOptions(item.Options); err != nil {
|
||||
options, err := definition.CanonicalOptions(item.Options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if definition.Builder == nil {
|
||||
@@ -120,7 +121,6 @@ func (r ModuleRegistry) BuildModule(ctx ModuleContext, item module.ConfigItem) (
|
||||
return nil, fmt.Errorf("module %q has unknown missing data behavior %q", item.ID, definition.MissingData)
|
||||
}
|
||||
}
|
||||
options := item.Options
|
||||
if options == nil {
|
||||
options = definition.DefaultOptions
|
||||
}
|
||||
@@ -264,6 +264,24 @@ func (d ModuleDefinition) ValidateOptions(options any) error {
|
||||
return fmt.Errorf("module %q options have type %s, want %s", d.ID, got, want)
|
||||
}
|
||||
|
||||
// CanonicalOptions validates options and converts an accepted typed pointer to its value form.
|
||||
func (d ModuleDefinition) CanonicalOptions(options any) (any, error) {
|
||||
if err := d.ValidateOptions(options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if options == nil {
|
||||
return nil, nil
|
||||
}
|
||||
value := reflect.ValueOf(options)
|
||||
if value.Kind() != reflect.Pointer {
|
||||
return options, nil
|
||||
}
|
||||
if value.IsNil() {
|
||||
return nil, fmt.Errorf("module %q options must not be a nil pointer", d.ID)
|
||||
}
|
||||
return value.Elem().Interface(), nil
|
||||
}
|
||||
|
||||
func defaultModuleDefinitions() []ModuleDefinition {
|
||||
allReports := []report.ID{report.Daily, report.Today, report.Tomorrow, report.Hourly}
|
||||
daypartReports := []report.ID{report.Daily, report.Today, report.Tomorrow}
|
||||
|
||||
@@ -458,6 +458,7 @@ func TestModuleRegistryAcceptsTypedOptions(t *testing.T) {
|
||||
err := registry.ValidateComposition(report.Daily, []module.ConfigItem{
|
||||
{ID: module.Metadata, Options: module.MetadataOptions{}},
|
||||
{ID: module.CurrentConditions, Options: &module.CurrentConditionsOptions{}},
|
||||
{ID: module.AreaForecastDiscussion, Options: &module.AreaForecastDiscussionOptions{Sections: []string{"short_term"}}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateComposition() error = %v", err)
|
||||
|
||||
@@ -23,7 +23,7 @@ type WeatherStoryModule struct {
|
||||
|
||||
func buildWeatherStoryModule(ctx ModuleContext, _ any) (*module.Output, error) {
|
||||
story := ctx.Collected.WeatherStory
|
||||
if story == nil {
|
||||
if story == nil || !story.HasUsableContent() {
|
||||
return nil, nil
|
||||
}
|
||||
period := timeutil.Period{Start: story.StartTime, End: story.EndTime}
|
||||
|
||||
Reference in New Issue
Block a user