Files
weatherreporter/internal/generatedtext/limits_test.go

113 lines
4.8 KiB
Go

package generatedtext
import (
"fmt"
"strings"
"testing"
)
func TestValidateHourlyGeneratedTextEnforcesContentLimits(t *testing.T) {
input := func(summary, discussion, timing string) []byte {
return []byte(fmt.Sprintf(`{"summary":%q,"forecast_discussion":%q,"precipitation_timing":%q}`, summary, discussion, timing))
}
for _, test := range []struct {
name string
input []byte
want string
}{
{name: "summary at limit", input: input(strings.Repeat("s", maxGeneratedTextStringCharacters), "discussion", "")},
{name: "summary over limit", input: input(strings.Repeat("s", maxGeneratedTextStringCharacters+1), "discussion", ""), want: "summary exceeds"},
{name: "discussion at limit", input: input("summary", strings.Repeat("d", maxHourlyDiscussionCharacters), "")},
{name: "discussion over limit", input: input("summary", strings.Repeat("d", maxHourlyDiscussionCharacters+1), ""), want: "forecast discussion exceeds"},
{name: "total at limit", input: input(strings.Repeat("s", 4_000), strings.Repeat("d", 12_000), strings.Repeat("t", 4_000))},
} {
t.Run(test.name, func(t *testing.T) {
err := validateHourlyJSON(test.input)
if test.want == "" && err != nil {
t.Fatalf("validate() error = %v", err)
}
if test.want != "" && (err == nil || !strings.Contains(err.Error(), test.want)) {
t.Fatalf("validate() error = %v, want %q", err, test.want)
}
})
}
}
func TestValidateDayStyleGeneratedTextEnforcesDiscussionLimits(t *testing.T) {
for _, test := range []struct {
name string
input string
want string
}{
{
name: "paragraph count at limit",
input: fmt.Sprintf(`{"summary":"summary","forecast_discussion":[%s],"precipitation_timing":""}`, strings.TrimSuffix(strings.Repeat(`"p",`, maxDayStyleDiscussionParagraphs), ",")),
},
{
name: "paragraph count over limit",
input: fmt.Sprintf(`{"summary":"summary","forecast_discussion":[%s],"precipitation_timing":""}`, strings.TrimSuffix(strings.Repeat(`"p",`, maxDayStyleDiscussionParagraphs+1), ",")),
want: "12-paragraph limit",
},
{
name: "paragraph at limit",
input: fmt.Sprintf(`{"summary":"summary","forecast_discussion":[%q],"precipitation_timing":""}`, strings.Repeat("p", maxDayStyleDiscussionParagraphChars)),
},
{
name: "paragraph over limit",
input: fmt.Sprintf(`{"summary":"summary","forecast_discussion":[%q],"precipitation_timing":""}`, strings.Repeat("p", maxDayStyleDiscussionParagraphChars+1)),
want: "paragraphs exceed the 4000-character limit",
},
{
name: "total at limit",
input: fmt.Sprintf(`{"summary":%q,"forecast_discussion":[%q,%q,%q,%q],"precipitation_timing":""}`,
strings.Repeat("s", 4_000), strings.Repeat("p", 4_000), strings.Repeat("p", 4_000), strings.Repeat("p", 4_000), strings.Repeat("p", 4_000)),
},
{
name: "total over limit",
input: fmt.Sprintf(`{"summary":%q,"forecast_discussion":[%q,%q,%q,%q,%q],"precipitation_timing":""}`,
strings.Repeat("s", 4_000), strings.Repeat("p", 4_000), strings.Repeat("p", 4_000), strings.Repeat("p", 4_000), strings.Repeat("p", 4_000), strings.Repeat("p", 4_000)),
want: "exceeds the 20000-character limit",
},
} {
t.Run(test.name, func(t *testing.T) {
err := validateDailyJSON([]byte(test.input))
if test.want == "" && err != nil {
t.Fatalf("validate() error = %v", err)
}
if test.want != "" && (err == nil || !strings.Contains(err.Error(), test.want)) {
t.Fatalf("validate() error = %v, want %q", err, test.want)
}
})
}
}
func TestValidateGeneratedTextRejectsOversizedRawOutputWithoutLeakingContent(t *testing.T) {
valid := []byte(`{"summary":"summary","forecast_discussion":"discussion","precipitation_timing":""}`)
atLimit := append([]byte(strings.Repeat(" ", MaxGeneratedTextBytes-len(valid))), valid...)
if err := validateHourlyJSON(atLimit); err != nil {
t.Fatalf("validate() at raw limit error = %v", err)
}
marker := "provider-controlled-marker"
unknownPrefix := `{"summary":"summary","forecast_discussion":"discussion","precipitation_timing":"","`
unknownSuffix := `":"value"}`
unknownKey := marker + strings.Repeat("x", MaxGeneratedTextBytes-len(unknownPrefix)-len(unknownSuffix)-len(marker))
unknownField := []byte(unknownPrefix + unknownKey + unknownSuffix)
err := validateHourlyJSON(unknownField)
if err == nil || !strings.Contains(err.Error(), "unsupported field") {
t.Fatalf("validate() error = %v, want unsupported field error", err)
}
if len(err.Error()) > 128 || strings.Contains(err.Error(), marker) {
t.Fatalf("validate() leaked unknown field content: %q", err)
}
input := append(atLimit, []byte(marker)...)
err = validateHourlyJSON(input)
if err == nil || !strings.Contains(err.Error(), "65536-byte limit") {
t.Fatalf("validate() error = %v, want raw size error", err)
}
if len(err.Error()) > 128 || strings.Contains(err.Error(), marker) {
t.Fatalf("validate() leaked oversized provider content: %q", err)
}
}