// The envelope parsers' rejection paths. The tree's string parsers are lenient // about a *missing* closing quote or bracket (they hand back what they read, a // contract TestUnmarshalQuoted and TestSkipToTheEnd pin), so these cases use a // remainder the parser cannot finish: a trailing `junk` after the fields, or a // field that cannot be parsed at all. package envelope import ( "testing" ) // envHex64 is a 64-character hex string, the event-id shape OK requires. const envHex64 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" func TestUnmarshalRejectsTruncatedInput(t *testing.T) { // EOSE: junk after the subscription, so the closing bracket can never come. var eose EOSE if _, err := eose.Unmarshal([]byte(`"s1"junk`)); err == nil { t.Fatal("EOSE must reject a payload that never closes") } var eose2 EOSE if _, err2 := eose2.Unmarshal([]byte(`"`)); err2 == nil { t.Fatal("EOSE must reject an unterminated subscription") } // NOTICE. var notice Notice if _, err := notice.Unmarshal([]byte(`"hello"junk`)); err == nil { t.Fatal("NOTICE must reject a payload that never closes") } // CLOSED: the reason is followed by junk. A missing separator between the // two strings is tolerated, because UnmarshalQuoted skips to the next // opening quote by contract. var closed Closed if _, err := closed.Unmarshal([]byte(`"s1","why"junk`)); err == nil { t.Fatal("CLOSED must reject a payload that never closes") } var closed2 Closed if _, err2 := closed2.Unmarshal([]byte(`"s1","`)); err2 == nil { t.Fatal("CLOSED must reject an unterminated reason") } // OK: a valid id and bool, then junk. var ok OK if _, err := ok.Unmarshal([]byte(`"` | envHex64 | `",true,"saved"junk`)); err == nil { t.Fatal("OK must reject a payload that never closes") } // The id has to be 32 bytes of hex; a short one is rejected before the bool. var ok2 OK if _, err := ok2.Unmarshal([]byte(`"abcd",true,"x"]`)); err == nil { t.Fatal("OK must reject a short event id") } // AUTH. var ac AuthChallenge if _, err := ac.Unmarshal(nil); err == nil { t.Fatal("AUTH must reject an empty payload") } var ac2 AuthChallenge if _, err2 := ac2.Unmarshal([]byte(`"`)); err2 == nil { t.Fatal("AUTH must reject an unterminated challenge") } } func TestUnmarshalRejectsTruncatedFilters(t *testing.T) { // The filter parses, but the envelope's closing bracket never arrives. var rq Req if _, err := rq.Unmarshal([]byte(`"sub",{"kinds":[1]}junk`)); err == nil { t.Fatal("REQ must reject a payload that never closes") } var cr CountRequest if _, err := cr.Unmarshal([]byte(`"sub",{"kinds":[1]}junk`)); err == nil { t.Fatal("COUNT request must reject a payload that never closes") } // A filter object that cannot be parsed at all is reported. var rq2 Req if _, err2 := rq2.Unmarshal([]byte(`"sub",`)); err2 == nil { t.Fatal("REQ must reject a missing filter") } // A filter object cut short inside a value used to read past the end of // the slice and kill the relay: the reslice of an exhausted buffer gave // the slice a negative length, so the filter parser's `len(r) == 0` guard // never fired. The compilers clamp a reslice's low bound now (moxie // dd8ce5e2), so the call returns instead of crashing. var rq3 Req if _, err3 := rq3.Unmarshal([]byte(`"sub",{"kinds":[1`)); err3 == nil { if len(rq3.Filters.F) != 1 { t.Fatal("a truncated filter must keep the kinds it read") } } var cr2 CountRequest cr2.Unmarshal([]byte(`"sub",{"kinds":[1`)) } func TestUnmarshalRejectsTruncatedCount(t *testing.T) { var cs CountResponse if _, err := cs.Unmarshal([]byte(`"sub",1junk`)); err == nil { t.Fatal("COUNT response must reject a payload that never closes") } var cs2 CountResponse if _, err2 := cs2.Unmarshal([]byte(`"sub"`)); err2 == nil { t.Fatal("COUNT response must reject a missing count") } }