Skip to content
quarry Artifact [7b9776ac4a]
Login

Artifact [7b9776ac4a]

Artifact 7b9776ac4a5f11c1f5fa66683282e996dce4dd4dac534dbd5a8b36627482129d:


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)
	}
}