Artifact 7b9776ac4a5f11c1f5fa66683282e996dce4dd4dac534dbd5a8b36627482129d:
- File
internal/ops/codec_test.go
— part of check-in
[839e3e756a]
at
2026-08-15 16:13:52
on branch trunk
— search fixes: folded ticket docs, mark snippets, honest limit (050/060/080/130)
search_doc gains label; tickets one folded doc per id; ⟦⟧ markers become <mark> post-escape; default= validate rule fills absent keys. (user: dmestas, size: 3848) [annotate] [blame] [check-ins using]
package ops import ( "net/url" "reflect" "strings" "testing" ) type formIn struct { Repo string `json:"repo"` Limit int `json:"limit"` After uint64 `json:"after"` Ratio float64 `json:"ratio"` Deleted bool `json:"deleted"` Tags []string `json:"tags"` Skip string `json:"-"` NoTag string } func TestDecodeValues(t *testing.T) { v := url.Values{ "repo": {"sqlite"}, "limit": {"50"}, "after": {"12345"}, "ratio": {"0.5"}, "deleted": {"true"}, "tags": {"a", "b"}, "NoTag": {"plain"}, } var in formIn if err := DecodeValues(v, &in); err != nil { t.Fatal(err) } want := formIn{ Repo: "sqlite", Limit: 50, After: 12345, Ratio: 0.5, Deleted: true, Tags: []string{"a", "b"}, NoTag: "plain", } if !reflect.DeepEqual(in, want) { t.Fatalf("got %+v, want %+v", in, want) } } func TestDecodeValuesMissingKeysLeaveZero(t *testing.T) { var in formIn if err := DecodeValues(url.Values{"repo": {"x"}}, &in); err != nil { t.Fatal(err) } if in.Repo != "x" || in.Limit != 0 || in.Tags != nil { t.Fatalf("got %+v", in) } } func TestDecodeValuesUnknownKeysIgnored(t *testing.T) { // HTML forms carry extras (CSRF tokens, submit buttons); never reject. var in formIn if err := DecodeValues(url.Values{"csrf": {"tok"}}, &in); err != nil { t.Fatal(err) } } func TestDecodeValuesBadNumber(t *testing.T) { var in formIn err := DecodeValues(url.Values{"limit": {"abc"}}, &in) if err == nil || !strings.Contains(err.Error(), "limit") { t.Fatalf("want error naming field limit, got %v", err) } } func TestDecodeValuesBadBool(t *testing.T) { var in formIn if err := DecodeValues(url.Values{"deleted": {"maybe"}}, &in); err == nil { t.Fatal("want bool parse error, got nil") } } func TestDecodeValuesSkipsDashTag(t *testing.T) { var in formIn if err := DecodeValues(url.Values{"Skip": {"x"}, "-": {"y"}}, &in); err != nil { t.Fatal(err) } if in.Skip != "" { t.Fatalf("json:\"-\" field decoded: %+v", in) } } func TestDecodeValuesRejectsNonPointerStruct(t *testing.T) { if err := DecodeValues(url.Values{}, formIn{}); err == nil { t.Fatal("want error for non-pointer dst, got nil") } x := 5 if err := DecodeValues(url.Values{}, &x); err == nil { t.Fatal("want error for non-struct dst, got nil") } } func TestDecodeValuesRejectsUnsupportedFieldType(t *testing.T) { type bad struct { Nested struct{ X int } `json:"nested"` } var b bad if err := DecodeValues(url.Values{"nested": {"{}"}}, &b); err == nil { t.Fatal("want unsupported-type error, got nil") } } func TestWildcards(t *testing.T) { for _, tc := range []struct { pattern string want []string }{ {"/api/v1/r/{repo}/timeline", []string{"repo"}}, {"/api/v1/r/{repo}/checkin/{hash}", []string{"repo", "hash"}}, {"/api/v1/version", nil}, {"/files/{path...}", []string{"path"}}, } { got := Wildcards(tc.pattern) if !reflect.DeepEqual(got, tc.want) { t.Errorf("Wildcards(%q) = %v, want %v", tc.pattern, got, tc.want) } } } func TestDecodeValuesAppliesDefaults(t *testing.T) { type withDef struct { Limit int `json:"limit" validate:"min=0,max=200,default=50"` } var in withDef if err := DecodeValues(url.Values{}, &in); err != nil { t.Fatal(err) } if in.Limit != 50 { t.Fatalf("absent key: limit = %d, want default 50", in.Limit) } // An explicit 0 is NOT the default (ticket 080). in = withDef{} if err := DecodeValues(url.Values{"limit": {"0"}}, &in); err != nil { t.Fatal(err) } if in.Limit != 0 { t.Fatalf("explicit 0: limit = %d", in.Limit) } } func TestDecodeValuesCleanParseErrors(t *testing.T) { var in formIn err := DecodeValues(url.Values{"limit": {"abc"}}, &in) if err == nil || strings.Contains(err.Error(), "strconv") || !strings.Contains(err.Error(), "must be an integer") { t.Fatalf("err = %v", err) } }