Refactor the template variable framework

This commit is contained in:
2026-06-14 08:57:53 -05:00
parent bb8de054dc
commit 28b8391d53
8 changed files with 512 additions and 552 deletions

View File

@@ -11,7 +11,7 @@ func TestTemplateLookup(t *testing.T) {
if err != nil {
t.Fatalf("Template() error = %v", err)
}
for _, want := range []string{"# {{ .ReportTitle }}", "## Summary", "## Hourly Forecast", "## Weather Story"} {
for _, want := range []string{"# {{ .Report.Title }}", "## Summary", "## Hourly Forecast", "## Weather Story"} {
if !strings.Contains(source, want) {
t.Fatalf("template missing %q:\n%s", want, source)
}
@@ -54,29 +54,58 @@ func TestSchemaLookup(t *testing.T) {
func TestRenderHourly(t *testing.T) {
rendered, err := Render("hourly", testRenderContext{
ReportTitle: "Hourly Report",
LocationName: "Brentwood",
ValidPeriod: "May 29, 8:30 AM to 2:30 PM",
GeneratedAt: "May 29, 8:30 AM",
CurrentConditions: "74 F, light south wind.",
PrecipitationTiming: "Showers are most likely late morning.",
WeatherStory: "Morning storms remain the main story.",
Report: testReportContext{
Title: "Hourly Report",
LocationName: "Brentwood",
ValidPeriodLabel: "May 29, 8:30 AM to 2:30 PM",
GeneratedAtLabel: "May 29, 8:30 AM",
},
GeneratedText: testGeneratedText{
Summary: "Storm chances increase through late morning.",
Timing: "The main window is 10 AM to noon.",
Impacts: "Brief downpours may slow travel.",
Confidence: "Medium confidence in timing.",
},
HourlyForecast: []testHourlyRow{
{Time: "9 AM", Summary: "Cloudy", Temperature: "74 F", Precipitation: "30% showers", Wind: "S 8 mph"},
{Time: "10 AM", Summary: "Showers", Temperature: "75 F", Precipitation: "70% showers", Wind: "S 10 mph"},
},
Alerts: []string{"Flood Watch until 2:30 PM"},
SPCOutlooks: []string{"Slight Risk through afternoon"},
SPCDiscussions: []string{"Strong storms may develop late morning."},
ForecastDiscussion: testForecastDiscussion{
KeyMessages: []string{"Storms are most likely late morning."},
ShortTerm: "Short-term discussion favors increasing rain coverage.",
Modules: testModules{
CurrentConditions: &testCurrentConditions{
ConditionText: "Partly cloudy",
TemperatureF: floatPtr(74),
ApparentTemperatureF: floatPtr(76),
RelativeHumidityPercent: floatPtr(71),
WindDirection: "S",
WindSpeedMph: floatPtr(8),
},
HourlyForecast: &testHourlyForecast{
Periods: []testHourlyPeriod{
{PeriodBegins: "9 AM", TextDescription: "Cloudy", TemperatureF: floatPtr(74), ProbabilityOfPrecipitationPercent: floatPtr(30), WindDirection: "S", WindSpeedMph: floatPtr(8)},
{PeriodBegins: "10 AM", TextDescription: "Showers", TemperatureF: floatPtr(75), ProbabilityOfPrecipitationPercent: floatPtr(70), WindDirection: "S", WindSpeedMph: floatPtr(10)},
},
},
PrecipTiming: &testPrecipTiming{
MaxPopPercent: intPtr(70),
MaxPopTime: "10 AM",
PrecipitationWindows: []testPrecipWindow{
{PeriodBegins: "10 AM", PeriodEnds: "12 PM", MaxPopPercent: intPtr(70), MaxPopTime: "10 AM"},
},
},
AlertDigest: &testAlertDigest{
Relevant: []testAlert{{Event: "Flood Watch", Headline: "Flood Watch until 2:30 PM", Severity: "Moderate"}},
},
SPCConvectiveOutlooks: &testSPCOutlooks{
Outlooks: []testSPCOutlook{{LabelText: "Slight Risk", PeriodBegins: "8 AM", PeriodEnds: "2 PM"}},
},
SPCConvectiveDiscussion: &testSPCDiscussion{
Discussions: []testSPCDiscussionRecord{{Summary: "Strong storms may develop late morning."}},
},
AreaForecastDiscussion: &testForecastDiscussion{
KeyMessages: []string{"Storms are most likely late morning."},
ShortTerm: "Short-term discussion favors increasing rain coverage.",
},
WeatherStory: &testWeatherStory{
Available: true,
Title: "Morning storms",
Description: "Morning storms remain the main story.",
},
},
})
if err != nil {
@@ -88,8 +117,8 @@ func TestRenderHourly(t *testing.T) {
"Valid: May 29, 8:30 AM to 2:30 PM",
"Storm chances increase through late morning.",
"## Confidence",
"- 10 AM: Showers; 75 F; 70% showers; S 10 mph",
"- Flood Watch until 2:30 PM",
"- 10 AM: Showers; 75 F; 70% precipitation; wind S 10 mph",
"- Flood Watch: Flood Watch until 2:30 PM (Moderate)",
"Short-term discussion favors increasing rain coverage.",
} {
if !strings.Contains(text, want) {
@@ -126,7 +155,7 @@ func TestUnknownAssetsReturnActionableErrors(t *testing.T) {
}
func TestRenderFailsForMissingContextFields(t *testing.T) {
_, err := Render("hourly", map[string]any{"ReportTitle": "Hourly Report"})
_, err := Render("hourly", map[string]any{"Report": map[string]any{"Title": "Hourly Report"}})
if err == nil {
t.Fatal("Render() error = nil, want missing field error")
}
@@ -136,19 +165,16 @@ func TestRenderFailsForMissingContextFields(t *testing.T) {
}
type testRenderContext struct {
ReportTitle string
LocationName string
ValidPeriod string
GeneratedAt string
CurrentConditions string
PrecipitationTiming string
WeatherStory string
GeneratedText testGeneratedText
HourlyForecast []testHourlyRow
Alerts []string
SPCOutlooks []string
SPCDiscussions []string
ForecastDiscussion testForecastDiscussion
Report testReportContext
GeneratedText testGeneratedText
Modules testModules
}
type testReportContext struct {
Title string
LocationName string
ValidPeriodLabel string
GeneratedAtLabel string
}
type testGeneratedText struct {
@@ -158,12 +184,76 @@ type testGeneratedText struct {
Confidence string
}
type testHourlyRow struct {
Time string
Summary string
Temperature string
Precipitation string
Wind string
type testModules struct {
CurrentConditions *testCurrentConditions
HourlyForecast *testHourlyForecast
PrecipTiming *testPrecipTiming
AlertDigest *testAlertDigest
SPCConvectiveOutlooks *testSPCOutlooks
AreaForecastDiscussion *testForecastDiscussion
SPCConvectiveDiscussion *testSPCDiscussion
WeatherStory *testWeatherStory
}
type testCurrentConditions struct {
ConditionText string
TemperatureF *float64
ApparentTemperatureF *float64
RelativeHumidityPercent *float64
WindSpeedMph *float64
WindDirection string
}
type testHourlyForecast struct {
Periods []testHourlyPeriod
}
type testHourlyPeriod struct {
PeriodBegins string
Name string
TextDescription string
TemperatureF *float64
WindSpeedMph *float64
WindGustMph *float64
WindDirection string
ProbabilityOfPrecipitationPercent *float64
}
type testPrecipTiming struct {
MaxPopPercent *int
MaxPopTime string
PrecipitationWindows []testPrecipWindow
ThunderMentioned bool
}
type testPrecipWindow struct {
PeriodBegins string
PeriodEnds string
MaxPopPercent *int
MaxPopTime string
}
type testAlertDigest struct {
Missing bool
Relevant []testAlert
}
type testAlert struct {
Event string
Headline string
Severity string
}
type testSPCOutlooks struct {
Outlooks []testSPCOutlook
}
type testSPCOutlook struct {
Label string
LabelText string
OutlookType string
PeriodBegins string
PeriodEnds string
}
type testForecastDiscussion struct {
@@ -171,6 +261,30 @@ type testForecastDiscussion struct {
ShortTerm string
}
type testSPCDiscussion struct {
Discussions []testSPCDiscussionRecord
}
type testSPCDiscussionRecord struct {
Headline string
Summary string
Discussion string
}
type testWeatherStory struct {
Available bool
Title string
Description string
}
func floatPtr(value float64) *float64 {
return &value
}
func intPtr(value int) *int {
return &value
}
func assertOrderedText(t *testing.T, text string, ordered []string) {
t.Helper()
previousIndex := -1