Keep generated prose out of Markdown structure

This commit is contained in:
2026-08-13 02:17:11 +00:00
parent 2bd921f247
commit a18d5134c7
12 changed files with 233 additions and 13 deletions

View File

@@ -920,6 +920,94 @@ func TestRenderHourlyUsesSharedOpenEndedPrecipitationTiming(t *testing.T) {
}
}
func TestRenderTreatsGeneratedProseAsPlainText(t *testing.T) {
const summary = "Summary paragraph.\n\n## Fabricated Alert\n- Fabricated warning\n1. Fabricated list\n> Fabricated quote"
const discussion = "Discussion paragraph.\n\n<script>unsafe</script>\n[unsafe](javascript:alert(1))\n```code```\n**emphasis**\n indented code\x00"
const timing = "Timing paragraph.\n\n## Fabricated Timing"
tests := []struct {
name string
template string
context any
}{
{
name: "daily",
template: "daily",
context: testDailyRenderContext{
Report: testDailyReportContext{Title: "Daily"},
GeneratedText: testDailyGeneratedText{Summary: summary, ForecastDiscussion: []string{discussion}, PrecipitationTiming: timing},
Modules: testDailyModules{PrecipTiming: testPrecipitationTimingForGeneratedProseTest()},
},
},
{
name: "today",
template: "today",
context: testTodayRenderContext{
Report: testTodayReportContext{Title: "Today"},
GeneratedText: testTomorrowGeneratedText{Summary: summary, ForecastDiscussion: []string{discussion}, PrecipitationTiming: timing},
Modules: testTodayModules{PrecipTiming: testPrecipitationTimingForGeneratedProseTest()},
},
},
{
name: "tomorrow",
template: "tomorrow",
context: testTomorrowRenderContext{
Report: testTomorrowReportContext{Title: "Tomorrow"},
GeneratedText: testTomorrowGeneratedText{Summary: summary, ForecastDiscussion: []string{discussion}, PrecipitationTiming: timing},
Modules: testTomorrowModules{PrecipTiming: testPrecipitationTimingForGeneratedProseTest()},
},
},
{
name: "hourly",
template: "hourly",
context: testRenderContext{
Report: testReportContext{Title: "Hourly"},
GeneratedText: testGeneratedText{Summary: summary, ForecastDiscussion: discussion, PrecipitationTiming: timing},
Modules: testModules{PrecipTiming: testPrecipitationTimingForGeneratedProseTest()},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
rendered, err := Render(test.template, test.context)
if err != nil {
t.Fatalf("Render() error = %v", err)
}
text := string(rendered)
for _, want := range []string{
"Summary paragraph.\n\n\\## Fabricated Alert\n\\- Fabricated warning\n1\\. Fabricated list\n\\> Fabricated quote",
"Discussion paragraph.\n\n\\<script\\>unsafe\\</script\\>\n\\[unsafe\\]\\(javascript:alert\\(1\\)\\)\n\\`\\`\\`code\\`\\`\\`\n\\*\\*emphasis\\*\\*\nindented code ",
"Timing paragraph.\n\n\\## Fabricated Timing",
} {
if !strings.Contains(text, want) {
t.Fatalf("rendered template missing escaped prose %q:\n%s", want, text)
}
}
for _, unwanted := range []string{
"\n## Fabricated Alert",
"\n- Fabricated warning",
"\n1. Fabricated list",
"\n> Fabricated quote",
"<script>unsafe</script>",
"[unsafe](javascript:alert(1))",
"```code```",
"**emphasis**",
"\n indented code",
"\n## Fabricated Timing",
} {
if strings.Contains(text, unwanted) {
t.Fatalf("rendered template preserved Markdown structure %q:\n%s", unwanted, text)
}
}
})
}
}
func testPrecipitationTimingForGeneratedProseTest() *testPrecipTiming {
return &testPrecipTiming{PrecipitationWindows: []testPrecipWindow{{}}}
}
func TestUnknownAssetsReturnActionableErrors(t *testing.T) {
if _, err := Template("missing"); err == nil || !strings.Contains(err.Error(), `unknown report template "missing"`) {
t.Fatalf("Template() error = %v, want unknown template", err)