fix(api): align claim bounds across runtime snapshots
This commit is contained in:
@@ -202,6 +202,17 @@ func ValidGoodsID(value string) bool {
|
||||
func validBoundedText(value string, maximum int) bool {
|
||||
// RuneCountInString replaces malformed byte sequences with RuneError. Validate first
|
||||
// so corrupt SQLite text cannot consume the code-point budget as if it were legitimate.
|
||||
return utf8.ValidString(value) && value != "" && strings.TrimSpace(value) == value &&
|
||||
utf8.RuneCountInString(value) <= maximum
|
||||
if !utf8.ValidString(value) || value == "" || strings.TrimSpace(value) != value ||
|
||||
utf8.RuneCountInString(value) > maximum {
|
||||
return false
|
||||
}
|
||||
for _, character := range value {
|
||||
// Python str.strip treats these four C0 separators as whitespace while Go
|
||||
// TrimSpace does not. Reject them everywhere so both wire models have one
|
||||
// explicit persisted-text domain instead of runtime-dependent trimming.
|
||||
if character >= '\u001c' && character <= '\u001f' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -107,6 +107,35 @@ func TestValidateAcceptsWorstLegalUnicodeFieldBounds(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPersistedTextHasRuntimeIndependentC0AndNBSPDomain(t *testing.T) {
|
||||
for name, invalid := range map[string]string{
|
||||
"c0 prefix": "\u001cvalue",
|
||||
"c0 suffix": "value\u001f",
|
||||
"c0 interior": "value\u001dinside",
|
||||
"nbsp prefix": "\u00a0value",
|
||||
"nbsp suffix": "value\u00a0",
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if validBoundedText(invalid, MaxTitleCodePoints) {
|
||||
t.Fatalf("validBoundedText(%q) accepted runtime-dependent text", invalid)
|
||||
}
|
||||
})
|
||||
}
|
||||
if !validBoundedText("left\u00a0right", MaxTitleCodePoints) {
|
||||
t.Fatal("interior NBSP must remain a valid Unicode code point")
|
||||
}
|
||||
|
||||
// Manual form input is normalized with Go TrimSpace before persistence.
|
||||
draft, validation := Validate(Form{
|
||||
CreateKey: testKey, Title: "\u00a0title\u00a0",
|
||||
ProductURL: CanonicalURL("1"), SKUColor: "\u00a0black\u00a0",
|
||||
SKUSize: "\u00a0M\u00a0", Quantity: "1", MaxTotalPrice: "1",
|
||||
})
|
||||
if !validation.Valid() || draft.Title != "title" || draft.SKUColor != "black" || draft.SKUSize != "M" {
|
||||
t.Fatalf("NBSP form normalization = %#v, errors = %#v", draft, validation)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCreateKeyIsUUIDv4(t *testing.T) {
|
||||
key, err := NewCreateKey()
|
||||
if err != nil {
|
||||
@@ -179,6 +208,7 @@ func TestSQLiteStoreCreatesListsAndHandlesIdempotency(t *testing.T) {
|
||||
func TestSQLiteStoreRejectsInvalidDraftAtPersistenceBoundary(t *testing.T) {
|
||||
mutations := map[string]func(*Draft){
|
||||
"untrimmed title": func(draft *Draft) { draft.Title = " title" },
|
||||
"c0 interior title": func(draft *Draft) { draft.Title = "title\u001dhidden" },
|
||||
"invalid utf8 title": func(draft *Draft) { draft.Title = string([]byte{0xff}) },
|
||||
"long title": func(draft *Draft) { draft.Title = strings.Repeat("😀", MaxTitleCodePoints+1) },
|
||||
"long color": func(draft *Draft) { draft.SKUColor = strings.Repeat("色", MaxSKUTextCodePoints+1) },
|
||||
|
||||
Reference in New Issue
Block a user