--- /dev/null
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "reflect"
+ "sort"
+ "strings"
+ "testing"
+ "time"
+)
+
+type A struct {
+ ID int
+ Name string
+ B B
+ private int
+}
+
+type B struct {
+ Extra string
+}
+
+var (
+ ParamTestValues = map[string][]string{
+ "int": {"1"},
+ "int8": {"1"},
+ "int16": {"1"},
+ "int32": {"1"},
+ "int64": {"1"},
+ "uint": {"1"},
+ "uint8": {"1"},
+ "uint16": {"1"},
+ "uint32": {"1"},
+ "uint64": {"1"},
+ "float32": {"1.000000"},
+ "float64": {"1.000000"},
+ "str": {"hello"},
+ "bool-true": {"true"},
+ "bool-1": {"1"},
+ "bool-on": {"on"},
+ "bool-false": {"false"},
+ "bool-0": {"0"},
+ "bool-0.0": {"0.0"},
+ "bool-off": {"off"},
+ "bool-f": {"f"},
+ "date": {"1982-07-09"},
+ "datetime": {"1982-07-09 21:30"},
+ "customDate": {"07/09/1982"},
+ "arr[0]": {"1"},
+ "arr[1]": {"2"},
+ "arr[3]": {"3"},
+ "uarr[]": {"1", "2"},
+ "arruarr[0][]": {"1", "2"},
+ "arruarr[1][]": {"3", "4"},
+ "2darr[0][0]": {"0"},
+ "2darr[0][1]": {"1"},
+ "2darr[1][0]": {"10"},
+ "2darr[1][1]": {"11"},
+ "A.ID": {"123"},
+ "A.Name": {"rob"},
+ "B.ID": {"123"},
+ "B.Name": {"rob"},
+ "B.B.Extra": {"hello"},
+ "pB.ID": {"123"},
+ "pB.Name": {"rob"},
+ "pB.B.Extra": {"hello"},
+ "priv.private": {"123"},
+ "arrC[0].ID": {"5"},
+ "arrC[0].Name": {"rob"},
+ "arrC[0].B.Extra": {"foo"},
+ "arrC[1].ID": {"8"},
+ "arrC[1].Name": {"bill"},
+ "m[a]": {"foo"},
+ "m[b]": {"bar"},
+ "m2[1]": {"foo"},
+ "m2[2]": {"bar"},
+ "m3[a]": {"1"},
+ "m3[b]": {"2"},
+ "m4[a].ID": {"1"},
+ "m4[a].Name": {"foo"},
+ "m4[b].ID": {"2"},
+ "m4[b].Name": {"bar"},
+ "mapWithAMuchLongerName[a].ID": {"1"},
+ "mapWithAMuchLongerName[a].Name": {"foo"},
+ "mapWithAMuchLongerName[b].ID": {"2"},
+ "mapWithAMuchLongerName[b].Name": {"bar"},
+ "invalidInt": {"xyz"},
+ "invalidInt2": {""},
+ "invalidBool": {"xyz"},
+ "invalidArr": {"xyz"},
+ "int8-overflow": {"1024"},
+ "uint8-overflow": {"1024"},
+ }
+
+ testDate = time.Date(1982, time.July, 9, 0, 0, 0, 0, time.UTC)
+ testDatetime = time.Date(1982, time.July, 9, 21, 30, 0, 0, time.UTC)
+)
+
+var binderTestCases = map[string]interface{}{
+ "int": 1,
+ "int8": int8(1),
+ "int16": int16(1),
+ "int32": int32(1),
+ "int64": int64(1),
+ "uint": 1,
+ "uint8": uint8(1),
+ "uint16": uint16(1),
+ "uint32": uint32(1),
+ "uint64": uint64(1),
+ "float32": float32(1.0),
+ "float64": float64(1.0),
+ "str": "hello",
+ "bool-true": true,
+ "bool-1": true,
+ "bool-on": true,
+ "bool-false": false,
+ "bool-0": false,
+ "bool-0.0": false,
+ "bool-off": false,
+ "bool-f": false,
+ "date": testDate,
+ "datetime": testDatetime,
+ "customDate": testDate,
+ "arr": []int{1, 2, 0, 3},
+ "uarr": []int{1, 2},
+ "arruarr": [][]int{{1, 2}, {3, 4}},
+ "2darr": [][]int{{0, 1}, {10, 11}},
+ "A": A{ID: 123, Name: "rob"},
+ "B": A{ID: 123, Name: "rob", B: B{Extra: "hello"}},
+ "pB": &A{ID: 123, Name: "rob", B: B{Extra: "hello"}},
+ "arrC": []A{
+ {
+ ID: 5,
+ Name: "rob",
+ B: B{"foo"},
+ },
+ {
+ ID: 8,
+ Name: "bill",
+ },
+ },
+ "m": map[string]string{"a": "foo", "b": "bar"},
+ "m2": map[int]string{1: "foo", 2: "bar"},
+ "m3": map[string]int{"a": 1, "b": 2},
+ "m4": map[string]A{"a": {ID: 1, Name: "foo"}, "b": {ID: 2, Name: "bar"}},
+
+ // NOTE: We also include a map with a longer name than the others since this has caused problems
+ // described in github issue #1285, resolved in pull request #1344. This test case should
+ // prevent regression.
+ "mapWithAMuchLongerName": map[string]A{"a": {ID: 1, Name: "foo"}, "b": {ID: 2, Name: "bar"}},
+
+ // TODO: Tests that use TypeBinders
+
+ // Invalid value tests (the result should always be the zero value for that type)
+ // The point of these is to ensure that invalid user input does not cause panics.
+ "invalidInt": 0,
+ "invalidInt2": 0,
+ "invalidBool": true,
+ "invalidArr": []int{},
+ "priv": A{},
+ "int8-overflow": int8(0),
+ "uint8-overflow": uint8(0),
+}
+
+// Types that files may be bound to, and a func that can read the content from
+// that type.
+// TODO: Is there any way to create a slice, given only the element Type?
+var fileBindings = []struct{ val, arrval, f interface{} }{
+ {(**os.File)(nil), []*os.File{}, ioutil.ReadAll},
+ {(*[]byte)(nil), [][]byte{}, func(b []byte) []byte { return b }},
+ {(*io.Reader)(nil), []io.Reader{}, ioutil.ReadAll},
+ {(*io.ReadSeeker)(nil), []io.ReadSeeker{}, ioutil.ReadAll},
+}
+
+func TestJsonBinder(t *testing.T) {
+ // create a structure to be populated
+ {
+ d, _ := json.Marshal(map[string]int{"a": 1})
+ params := &Params{JSON: d}
+ foo := struct{ A int }{}
+ c := NewTestController(nil, getMultipartRequest())
+
+ ParseParams(params, NewRequest(c.Request.In))
+ actual := Bind(params, "test", reflect.TypeOf(foo))
+ valEq(t, "TestJsonBinder", reflect.ValueOf(actual.Interface().(struct{ A int }).A), reflect.ValueOf(1))
+ }
+ {
+ d, _ := json.Marshal(map[string]interface{}{"a": map[string]int{"b": 45}})
+ params := &Params{JSON: d}
+ testMap := map[string]interface{}{}
+ actual := Bind(params, "test", reflect.TypeOf(testMap)).Interface().(map[string]interface{})
+ if actual["a"].(map[string]interface{})["b"].(float64) != 45 {
+ t.Errorf("Failed to fetch map value %#v", actual["a"])
+ }
+ // Check to see if a named map works
+ actualb := Bind(params, "test", reflect.TypeOf(map[string]map[string]float64{})).Interface().(map[string]map[string]float64)
+ if actualb["a"]["b"] != 45 {
+ t.Errorf("Failed to fetch map value %#v", actual["a"])
+ }
+
+ }
+}
+
+func TestBinder(t *testing.T) {
+ // Reuse the mvc_test.go multipart request to test the binder.
+ params := &Params{}
+ c := NewTestController(nil, getMultipartRequest())
+ ParseParams(params, NewRequest(c.Request.In))
+ params.Values = ParamTestValues
+
+ // Values
+ for k, v := range binderTestCases {
+ actual := Bind(params, k, reflect.TypeOf(v))
+ expected := reflect.ValueOf(v)
+ valEq(t, k, actual, expected)
+ }
+
+ // Files
+
+ // Get the keys in sorted order to make the expectation right.
+ keys := []string{}
+ for k := range expectedFiles {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+
+ expectedBoundFiles := make(map[string][]fh)
+ for _, k := range keys {
+ fhs := expectedFiles[k]
+ k := nextKey(k)
+ expectedBoundFiles[k] = append(expectedBoundFiles[k], fhs...)
+ }
+
+ for k, fhs := range expectedBoundFiles {
+
+ if len(fhs) == 1 {
+ // Test binding single files to: *os.File, []byte, io.Reader, io.ReadSeeker
+ for _, binding := range fileBindings {
+ typ := reflect.TypeOf(binding.val).Elem()
+ actual := Bind(params, k, typ)
+ if !actual.IsValid() || (actual.Kind() == reflect.Interface && actual.IsNil()) {
+ t.Errorf("%s (%s) - Returned nil.", k, typ)
+ continue
+ }
+ returns := reflect.ValueOf(binding.f).Call([]reflect.Value{actual})
+ valEq(t, k, returns[0], reflect.ValueOf(fhs[0].content))
+ }
+
+ } else {
+ // Test binding multi to:
+ // []*os.File, [][]byte, []io.Reader, []io.ReadSeeker
+ for _, binding := range fileBindings {
+ typ := reflect.TypeOf(binding.arrval)
+ actual := Bind(params, k, typ)
+ if actual.Len() != len(fhs) {
+ t.Fatalf("%s (%s) - Number of files: (expected) %d != %d (actual)",
+ k, typ, len(fhs), actual.Len())
+ }
+ for i := range fhs {
+ returns := reflect.ValueOf(binding.f).Call([]reflect.Value{actual.Index(i)})
+ if !returns[0].IsValid() {
+ t.Errorf("%s (%s) - Returned nil.", k, typ)
+ continue
+ }
+ valEq(t, k, returns[0], reflect.ValueOf(fhs[i].content))
+ }
+ }
+ }
+ }
+}
+
+// Unbinding tests
+
+var unbinderTestCases = map[string]interface{}{
+ "int": 1,
+ "int8": int8(1),
+ "int16": int16(1),
+ "int32": int32(1),
+ "int64": int64(1),
+ "uint": 1,
+ "uint8": uint8(1),
+ "uint16": uint16(1),
+ "uint32": uint32(1),
+ "uint64": uint64(1),
+ "float32": float32(1.0),
+ "float64": float64(1.0),
+ "str": "hello",
+ "bool-true": true,
+ "bool-false": false,
+ "date": testDate,
+ "datetime": testDatetime,
+ "arr": []int{1, 2, 0, 3},
+ "2darr": [][]int{{0, 1}, {10, 11}},
+ "A": A{ID: 123, Name: "rob"},
+ "B": A{ID: 123, Name: "rob", B: B{Extra: "hello"}},
+ "pB": &A{ID: 123, Name: "rob", B: B{Extra: "hello"}},
+ "arrC": []A{
+ {
+ ID: 5,
+ Name: "rob",
+ B: B{"foo"},
+ },
+ {
+ ID: 8,
+ Name: "bill",
+ },
+ },
+ "m": map[string]string{"a": "foo", "b": "bar"},
+ "m2": map[int]string{1: "foo", 2: "bar"},
+ "m3": map[string]int{"a": 1, "b": 2},
+}
+
+// Some of the unbinding results are not exactly what is in ParamTestValues, since it
+// serializes implicit zero values explicitly.
+var unbinderOverrideAnswers = map[string]map[string]string{
+ "arr": {
+ "arr[0]": "1",
+ "arr[1]": "2",
+ "arr[2]": "0",
+ "arr[3]": "3",
+ },
+ "A": {
+ "A.ID": "123",
+ "A.Name": "rob",
+ "A.B.Extra": "",
+ },
+ "arrC": {
+ "arrC[0].ID": "5",
+ "arrC[0].Name": "rob",
+ "arrC[0].B.Extra": "foo",
+ "arrC[1].ID": "8",
+ "arrC[1].Name": "bill",
+ "arrC[1].B.Extra": "",
+ },
+ "m": {"m[a]": "foo", "m[b]": "bar"},
+ "m2": {"m2[1]": "foo", "m2[2]": "bar"},
+ "m3": {"m3[a]": "1", "m3[b]": "2"},
+}
+
+func TestUnbinder(t *testing.T) {
+ for k, v := range unbinderTestCases {
+ actual := make(map[string]string)
+ Unbind(actual, k, v)
+
+ // Get the expected key/values.
+ expected, ok := unbinderOverrideAnswers[k]
+ if !ok {
+ expected = make(map[string]string)
+ for k2, v2 := range ParamTestValues {
+ if k == k2 || strings.HasPrefix(k2, k+".") || strings.HasPrefix(k2, k+"[") {
+ expected[k2] = v2[0]
+ }
+ }
+ }
+
+ // Compare length and values.
+ if len(actual) != len(expected) {
+ t.Errorf("Length mismatch\nExpected length %d, actual %d\nExpected: %s\nActual: %s",
+ len(expected), len(actual), expected, actual)
+ }
+ for k, v := range actual {
+ if expected[k] != v {
+ t.Errorf("Value mismatch.\nExpected: %s\nActual: %s", expected, actual)
+ }
+ }
+ }
+}
+
+// Helpers
+
+func valEq(t *testing.T, name string, actual, expected reflect.Value) {
+ switch expected.Kind() {
+ case reflect.Slice:
+ // Check the type/length/element type
+ if !eq(t, name+" (type)", actual.Kind(), expected.Kind()) ||
+ !eq(t, name+" (len)", actual.Len(), expected.Len()) ||
+ !eq(t, name+" (elem)", actual.Type().Elem(), expected.Type().Elem()) {
+ return
+ }
+
+ // Check value equality for each element.
+ for i := 0; i < actual.Len(); i++ {
+ valEq(t, fmt.Sprintf("%s[%d]", name, i), actual.Index(i), expected.Index(i))
+ }
+
+ case reflect.Ptr:
+ // Check equality on the element type.
+ valEq(t, name, actual.Elem(), expected.Elem())
+ case reflect.Map:
+ if !eq(t, name+" (len)", actual.Len(), expected.Len()) {
+ return
+ }
+ for _, key := range expected.MapKeys() {
+ expectedValue := expected.MapIndex(key)
+ actualValue := actual.MapIndex(key)
+ if actualValue.IsValid() {
+ valEq(t, fmt.Sprintf("%s[%s]", name, key), actualValue, expectedValue)
+ } else {
+ t.Errorf("Expected key %s not found", key)
+ }
+ }
+ default:
+ eq(t, name, actual.Interface(), expected.Interface())
+ }
+}
+
+func init() {
+ DateFormat = DefaultDateFormat
+ DateTimeFormat = DefaultDateTimeFormat
+ TimeFormats = append(TimeFormats, DefaultDateFormat, DefaultDateTimeFormat, "01/02/2006")
+}