Files
lneto/http/httpraw/form_test.go
T
2026-07-26 13:57:18 -03:00

135 lines
3.6 KiB
Go

package httpraw
import (
"strings"
"testing"
)
// render joins a form's pairs as "key=value", a valueless key as "key".
func render(f *Form) string {
var sb strings.Builder
for i := range f.Len() {
key, value := f.Pair(i)
if i > 0 {
sb.WriteByte('|')
}
sb.Write(key)
if value != nil {
sb.WriteByte('=')
sb.Write(value)
}
}
return sb.String()
}
func TestFormParse(t *testing.T) {
for _, test := range []struct {
body string
want string
}{
{body: "", want: ""},
{body: "q=go", want: "q=go"},
{body: "name=Jos%C3%A9+P%C3%A9rez&msg=hi+there&ok=on", want: "name=Jos%C3%A9+P%C3%A9rez|msg=hi+there|ok=on"},
{body: "ok", want: "ok"}, // Flag: no '=' at all.
{body: "ok=", want: "ok="}, // Present but empty.
{body: "&&q=go&", want: "q=go"}, // Empty sequences skipped.
{body: "tag=a&tag=b", want: "tag=a|tag=b"}, // Duplicates kept in order.
{body: "=v", want: "=v"}, // Empty name kept.
{body: "a=b=c", want: "a=b=c"}, // Only the first '=' splits.
} {
var f Form
if err := f.ParseBytes([]byte(test.body)); err != nil {
t.Fatalf("%q: %s", test.body, err)
}
if got := render(&f); got != test.want {
t.Errorf("%q: want %q, got %q", test.body, test.want, got)
}
}
}
// Decode rewrites keys and values in place: percent escapes and '+' as space.
func TestFormDecode(t *testing.T) {
var f Form
const body = "name=Jos%C3%A9+P%C3%A9rez&a%20b=c%2Bd&ok"
if err := f.ParseBytes([]byte(body)); err != nil {
t.Fatal(err)
}
if err := f.Decode(); err != nil {
t.Fatal(err)
}
const want = "name=José Pérez|a b=c+d|ok"
if got := render(&f); got != want {
t.Errorf("want %q, got %q", want, got)
}
if got := string(f.Get("a b")); got != "c+d" {
t.Errorf("want decoded key lookup %q, got %q", "c+d", got)
}
}
// A malformed escape must be reported, never silently passed through.
func TestFormDecodeMalformed(t *testing.T) {
for _, body := range []string{"q=%zz", "%zz=v", "q=%4"} {
var f Form
if err := f.ParseBytes([]byte(body)); err != nil {
t.Fatal(err)
}
if err := f.Decode(); err == nil {
t.Errorf("%q: want decode error, got nil", body)
}
}
}
func TestFormGetHas(t *testing.T) {
var f Form
if err := f.ParseBytes([]byte("tag=a&tag=b&ok&empty=")); err != nil {
t.Fatal(err)
}
if got := string(f.Get("tag")); got != "a" {
t.Errorf("want first value %q, got %q", "a", got)
}
if got := f.Get("ok"); got != nil {
t.Errorf("want nil value for valueless key, got %q", got)
}
if got := f.Get("nope"); got != nil {
t.Errorf("want nil for absent key, got %q", got)
}
if v := f.Get("empty"); v == nil || len(v) != 0 {
t.Errorf("want present empty value, got %v", v)
}
for _, key := range []string{"tag", "ok", "empty"} {
if !f.Has(key) {
t.Errorf("want Has(%q) true", key)
}
}
if f.Has("nope") {
t.Error("want Has(nope) false")
}
}
func TestFormAppendKeyValues(t *testing.T) {
const body = "name=go&ok&empty=&tag=a&tag=b"
var f Form
if err := f.ParseBytes([]byte(body)); err != nil {
t.Fatal(err)
}
if got := string(f.AppendKeyValues(nil)); got != body {
t.Errorf("want round trip %q, got %q", body, got)
}
}
// Parsing into a reused Form must not allocate: the pair storage is reused.
func TestFormParseReuseNoAlloc(t *testing.T) {
body := []byte("name=go&tag=a&tag=b&ok")
var f Form
if err := f.ParseBytes(body); err != nil { // Warm up the pair storage.
t.Fatal(err)
}
allocs := testing.AllocsPerRun(100, func() {
f.Reset(body)
f.Parse()
})
if allocs != 0 {
t.Errorf("reused Form allocated %v times, want 0", allocs)
}
}