Harden NWS key message parsing
This commit is contained in:
@@ -740,6 +740,178 @@ func TestParseForecastDiscussionTextSectionKeepsArbitraryDashedProse(t *testing.
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsForecastDiscussionKeyMessageMetadataLine(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "issued at",
|
||||
line: "Issued at 300 PM CDT Sat Mar 28 2026",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "updated at ASCII case insensitive",
|
||||
line: "uPdAtEd At 300 PM CDT Sat Mar 28 2026",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "label without whitespace boundary",
|
||||
line: "Updated at300 PM CDT Sat Mar 28 2026",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "invalid timestamp",
|
||||
line: "Updated at not a timestamp",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "nonmetadata prose",
|
||||
line: "Updated atmospheric conditions remain unsettled.",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := isForecastDiscussionKeyMessageMetadataLine(tt.line); got != tt.want {
|
||||
t.Fatalf("isForecastDiscussionKeyMessageMetadataLine(%q) = %t, want %t", tt.line, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripForecastDiscussionKeyMessageMarker(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
want string
|
||||
ok bool
|
||||
}{
|
||||
{name: "hyphen", line: "- First message.", want: "First message.", ok: true},
|
||||
{name: "hyphen without whitespace", line: "-First message.", want: "First message.", ok: true},
|
||||
{name: "asterisk", line: "* First message.", want: "First message.", ok: true},
|
||||
{name: "numeric parenthesis", line: "1) First message.", want: "First message.", ok: true},
|
||||
{name: "numeric dot", line: "2. Second message.", want: "Second message.", ok: true},
|
||||
{name: "composite hyphen", line: "- 1. First message.", want: "First message.", ok: true},
|
||||
{name: "composite asterisk", line: "* 1) First message.", want: "First message.", ok: true},
|
||||
{name: "multi digit numeric", line: "12. Twelfth message.", want: "Twelfth message.", ok: true},
|
||||
{name: "hyphen only", line: "-", want: "", ok: true},
|
||||
{name: "numeric marker only", line: "1)", want: "", ok: true},
|
||||
{name: "zero numeric marker", line: "0) Not a marker.", want: "", ok: false},
|
||||
{name: "overflow numeric marker", line: "999999999999999999999999999999. Too big.", want: "", ok: false},
|
||||
{name: "alphanumeric numeric prefix", line: "1x Not a marker.", want: "", ok: false},
|
||||
{name: "numeric marker without boundary", line: "1)Not a marker.", want: "", ok: false},
|
||||
{name: "numeric marker with unsupported punctuation", line: "1, Not a marker.", want: "", ok: false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, ok := stripForecastDiscussionKeyMessageMarker(tt.line)
|
||||
if ok != tt.ok || got != tt.want {
|
||||
t.Fatalf("stripForecastDiscussionKeyMessageMarker(%q) = (%q, %t), want (%q, %t)", tt.line, got, ok, tt.want, tt.ok)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseForecastDiscussionKeyMessagesClassifiesListsAndMetadata(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
body []string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "numbered list with continuation",
|
||||
body: []string{
|
||||
"1) First numbered message.",
|
||||
"Wrapped continuation.",
|
||||
"2. Second numbered message.",
|
||||
},
|
||||
want: []string{"First numbered message. Wrapped continuation.", "Second numbered message."},
|
||||
},
|
||||
{
|
||||
name: "asterisk list",
|
||||
body: []string{
|
||||
"* First asterisk message.",
|
||||
"*Second asterisk message.",
|
||||
},
|
||||
want: []string{"First asterisk message.", "Second asterisk message."},
|
||||
},
|
||||
{
|
||||
name: "unmarked paragraphs",
|
||||
body: []string{
|
||||
"First paragraph.",
|
||||
"Wrapped continuation.",
|
||||
"",
|
||||
"",
|
||||
"Second paragraph.",
|
||||
},
|
||||
want: []string{"First paragraph. Wrapped continuation.", "Second paragraph."},
|
||||
},
|
||||
{
|
||||
name: "introductory prose before marked items",
|
||||
body: []string{
|
||||
"Introductory prose.",
|
||||
"",
|
||||
"- First marked message.",
|
||||
"Wrapped continuation.",
|
||||
"- Second marked message.",
|
||||
},
|
||||
want: []string{"Introductory prose.", "First marked message. Wrapped continuation.", "Second marked message."},
|
||||
},
|
||||
{
|
||||
name: "leading issued metadata",
|
||||
body: []string{
|
||||
"iSsUeD At 300 PM CDT Sat Mar 28 2026",
|
||||
"- First message.",
|
||||
},
|
||||
want: []string{"First message."},
|
||||
},
|
||||
{
|
||||
name: "leading updated metadata",
|
||||
body: []string{
|
||||
"Updated at 300 PM CDT Sat Mar 28 2026",
|
||||
"- First message.",
|
||||
},
|
||||
want: []string{"First message."},
|
||||
},
|
||||
{
|
||||
name: "updated atmospheric prose remains content",
|
||||
body: []string{
|
||||
"Updated atmospheric conditions remain unsettled.",
|
||||
"Additional detail.",
|
||||
},
|
||||
want: []string{"Updated atmospheric conditions remain unsettled. Additional detail."},
|
||||
},
|
||||
{
|
||||
name: "invalid updated metadata remains content",
|
||||
body: []string{
|
||||
"Updated at not a timestamp",
|
||||
"Additional detail.",
|
||||
},
|
||||
want: []string{"Updated at not a timestamp Additional detail."},
|
||||
},
|
||||
{
|
||||
name: "later valid metadata remains content",
|
||||
body: []string{
|
||||
"- First message.",
|
||||
"Updated at 300 PM CDT Sat Mar 28 2026",
|
||||
},
|
||||
want: []string{"First message. Updated at 300 PM CDT Sat Mar 28 2026"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := parseForecastDiscussionKeyMessages(tt.body); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Fatalf("KeyMessages = %#v, want %#v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseForecastDiscussionHTMLParsesCrossOfficeLayout(t *testing.T) {
|
||||
got, err := ParseForecastDiscussionHTML(loadForecastDiscussionBOUSampleHTML(t))
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user