X-Git-Url: https://gerrit.akraino.org/r/gitweb?a=blobdiff_plain;f=src%2Ffoundation%2Fapi%2Frevel%2Fbinder_test.go;fp=src%2Ffoundation%2Fapi%2Frevel%2Fbinder_test.go;h=5ffd5cd6a46cdae61a3712184d58560e510627ea;hb=1d1ee6961c93781e1187d8c7faa868da6b2f01f4;hp=0000000000000000000000000000000000000000;hpb=56dd5e0f2164b37b40ac1daa188ccc618b4cbd19;p=iec.git diff --git a/src/foundation/api/revel/binder_test.go b/src/foundation/api/revel/binder_test.go new file mode 100644 index 0000000..5ffd5cd --- /dev/null +++ b/src/foundation/api/revel/binder_test.go @@ -0,0 +1,419 @@ +// 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") +}