iris/hero/binding_test.go

549 lines
15 KiB
Go
Raw Normal View History

package hero
import (
stdContext "context"
"fmt"
"net/http"
"reflect"
"testing"
"time"
2020-08-01 04:11:08 +02:00
"github.com/kataras/golog"
"github.com/kataras/iris/v12/context"
"github.com/kataras/iris/v12/sessions"
)
var (
stdContextTyp = reflect.TypeOf((*stdContext.Context)(nil)).Elem()
sessionTyp = reflect.TypeOf((*sessions.Session)(nil))
timeTyp = reflect.TypeOf((*time.Time)(nil)).Elem()
mapStringsTyp = reflect.TypeOf(map[string][]string{})
)
func contextBinding(index int) *binding {
return &binding{
Dependency: BuiltinDependencies[0],
Input: &Input{Type: BuiltinDependencies[0].DestType, Index: index},
}
}
func TestGetBindingsForFunc(t *testing.T) {
type (
testResponse struct {
Name string `json:"name"`
}
testRequest struct {
Email string `json:"email"`
}
testRequest2 struct {
// normally a body can't have two requests but let's test it.
Age int `json:"age"`
}
)
var testRequestTyp = reflect.TypeOf(testRequest{})
var deps = []*Dependency{
NewDependency(func(ctx *context.Context) testRequest { return testRequest{Email: "should be ignored"} }),
NewDependency(42),
NewDependency(func(ctx *context.Context) (v testRequest, err error) {
err = ctx.ReadJSON(&v)
return
}),
NewDependency("if two strings requested this should be the last one"),
NewDependency("should not be ignored when requested"),
// Dependencies like these should always be registered last.
NewDependency(func(ctx *context.Context, input *Input) (newValue reflect.Value, err error) {
wasPtr := input.Type.Kind() == reflect.Ptr
newValue = reflect.New(indirectType(input.Type))
ptr := newValue.Interface()
err = ctx.ReadJSON(ptr)
if !wasPtr {
newValue = newValue.Elem()
}
return newValue, err
}),
}
var tests = []struct {
Func interface{}
Expected []*binding
}{
{ // 0
Func: func(ctx *context.Context) {
ctx.WriteString("t1")
},
Expected: []*binding{contextBinding(0)},
},
{ // 1
Func: func(ctx *context.Context) error {
return fmt.Errorf("err1")
},
Expected: []*binding{contextBinding(0)},
},
{ // 2
Func: func(ctx *context.Context) testResponse {
return testResponse{Name: "name"}
},
Expected: []*binding{contextBinding(0)},
},
{ // 3
Func: func(in testRequest) (testResponse, error) {
return testResponse{Name: "email of " + in.Email}, nil
},
Expected: []*binding{{Dependency: deps[2], Input: &Input{Index: 0, Type: testRequestTyp}}},
},
{ // 4
Func: func(in testRequest) (testResponse, error) {
return testResponse{Name: "not valid "}, fmt.Errorf("invalid")
},
Expected: []*binding{{Dependency: deps[2], Input: &Input{Index: 0, Type: testRequestTyp}}},
},
{ // 5
Func: func(ctx *context.Context, in testRequest) testResponse {
return testResponse{Name: "(with ctx) email of " + in.Email}
},
Expected: []*binding{contextBinding(0), {Dependency: deps[2], Input: &Input{Index: 1, Type: testRequestTyp}}},
},
{ // 6
Func: func(in testRequest, ctx *context.Context) testResponse { // reversed.
return testResponse{Name: "(with ctx) email of " + in.Email}
},
Expected: []*binding{{Dependency: deps[2], Input: &Input{Index: 0, Type: testRequestTyp}}, contextBinding(1)},
},
{ // 7
Func: func(in testRequest, ctx *context.Context, in2 string) testResponse { // reversed.
return testResponse{Name: "(with ctx) email of " + in.Email + "and in2: " + in2}
},
Expected: []*binding{
{
Dependency: deps[2],
Input: &Input{Index: 0, Type: testRequestTyp},
},
contextBinding(1),
{
Dependency: deps[4],
Input: &Input{Index: 2, Type: reflect.TypeOf("")},
},
},
},
{ // 8
Func: func(in testRequest, ctx *context.Context, in2, in3 string) testResponse { // reversed.
return testResponse{Name: "(with ctx) email of " + in.Email + " | in2: " + in2 + " in3: " + in3}
},
Expected: []*binding{
{
Dependency: deps[2],
Input: &Input{Index: 0, Type: testRequestTyp},
},
contextBinding(1),
{
Dependency: deps[len(deps)-3],
Input: &Input{Index: 2, Type: reflect.TypeOf("")},
},
{
Dependency: deps[len(deps)-2],
Input: &Input{Index: 3, Type: reflect.TypeOf("")},
},
},
},
{ // 9
Func: func(ctx *context.Context, in testRequest, in2 testRequest2) testResponse {
return testResponse{Name: fmt.Sprintf("(with ctx) email of %s and in2.Age %d", in.Email, in2.Age)}
},
Expected: []*binding{
contextBinding(0),
{
Dependency: deps[2],
Input: &Input{Index: 1, Type: testRequestTyp},
},
{
Dependency: deps[len(deps)-1],
Input: &Input{Index: 2, Type: reflect.TypeOf(testRequest2{})},
},
},
},
{ // 10
Func: func() testResponse {
return testResponse{Name: "empty in, one out"}
},
Expected: nil,
},
{ // 1
Func: func(userID string, age int) testResponse {
return testResponse{Name: "in from path parameters"}
},
Expected: []*binding{
paramBinding(0, 0, reflect.TypeOf("")),
paramBinding(1, 1, reflect.TypeOf(0)),
},
},
// test std context, session, time, request, response writer and headers bindings.
{ // 12
2020-08-01 04:11:08 +02:00
Func: func(stdContext.Context, *sessions.Session, *golog.Logger, time.Time, *http.Request, http.ResponseWriter, http.Header) testResponse {
return testResponse{"builtin deps"}
},
Expected: []*binding{
{
Dependency: NewDependency(BuiltinDependencies[1]),
Input: &Input{Index: 0, Type: stdContextTyp},
},
{
Dependency: NewDependency(BuiltinDependencies[2]),
Input: &Input{Index: 1, Type: sessionTyp},
},
{
Dependency: NewDependency(BuiltinDependencies[3]),
2020-08-01 04:11:08 +02:00
Input: &Input{Index: 2, Type: BuiltinDependencies[3].DestType},
},
{
Dependency: NewDependency(BuiltinDependencies[4]),
2020-08-01 04:11:08 +02:00
Input: &Input{Index: 3, Type: timeTyp},
},
{
Dependency: NewDependency(BuiltinDependencies[5]),
Input: &Input{Index: 4, Type: BuiltinDependencies[5].DestType},
},
{
Dependency: NewDependency(BuiltinDependencies[6]),
Input: &Input{Index: 5, Type: BuiltinDependencies[6].DestType},
},
2020-08-01 04:11:08 +02:00
{
Dependency: NewDependency(BuiltinDependencies[7]),
Input: &Input{Index: 6, Type: BuiltinDependencies[7].DestType},
},
},
},
// test explicitly of http.Header and its underline type map[string][]string which
// but shouldn't be binded to request headers because of the (.Explicitly()), instead
// the map should be binded to our last of "deps" which is is a dynamic functions reads from request body's JSON
// (it's a builtin dependency as well but we declared it to test user dynamic dependencies too).
{ // 13
Func: func(http.Header) testResponse {
return testResponse{"builtin http.Header dep"}
},
Expected: []*binding{
{
2020-08-01 04:11:08 +02:00
Dependency: NewDependency(BuiltinDependencies[7]),
Input: &Input{Index: 0, Type: BuiltinDependencies[7].DestType},
},
},
},
{ // 14
Func: func(map[string][]string) testResponse {
return testResponse{"not dep registered except the dynamic one"}
},
Expected: []*binding{
{
Dependency: deps[len(deps)-1],
Input: &Input{Index: 0, Type: mapStringsTyp},
},
},
},
{ // 15
Func: func(http.Header, map[string][]string) testResponse {
return testResponse{}
},
Expected: []*binding{ // only http.Header should be binded, we don't have map[string][]string registered.
{
2020-08-01 04:11:08 +02:00
Dependency: NewDependency(BuiltinDependencies[7]),
Input: &Input{Index: 0, Type: BuiltinDependencies[7].DestType},
},
{
Dependency: deps[len(deps)-1],
Input: &Input{Index: 1, Type: mapStringsTyp},
},
},
},
}
c := New()
for _, dependency := range deps {
c.Register(dependency)
}
for i, tt := range tests {
bindings := getBindingsForFunc(reflect.ValueOf(tt.Func), c.Dependencies, 0)
if expected, got := len(tt.Expected), len(bindings); expected != got {
t.Fatalf("[%d] expected bindings length to be: %d but got: %d of: %s", i, expected, got, bindings)
}
for j, b := range bindings {
if b == nil {
t.Fatalf("[%d:%d] binding is nil!", i, j)
}
if tt.Expected[j] == nil {
t.Fatalf("[%d:%d] expected dependency was not found!", i, j)
}
// if expected := tt.Expected[j]; !expected.Equal(b) {
// t.Fatalf("[%d:%d] got unexpected binding:\n%s", i, j, spew.Sdump(expected, b))
// }
if expected := tt.Expected[j]; !expected.Equal(b) {
t.Fatalf("[%d:%d] expected binding:\n%s\nbut got:\n%s", i, j, expected, b)
}
}
}
}
type (
service interface {
String() string
}
serviceImpl struct{}
)
var serviceTyp = reflect.TypeOf((*service)(nil)).Elem()
func (s *serviceImpl) String() string {
return "service"
}
func TestBindingsForStruct(t *testing.T) {
type (
controller struct {
Name string
Service service
}
embedded1 struct {
Age int
}
embedded2 struct {
Now time.Time
}
Embedded3 struct {
Age int
}
Embedded4 struct {
Now time.Time
}
controllerEmbeddingExported struct {
Embedded3
Embedded4
}
controllerEmbeddingUnexported struct {
embedded1
embedded2
}
controller2 struct {
Emb1 embedded1
Emb2 embedded2
}
controller3 struct {
Emb1 embedded1
emb2 embedded2 // unused
}
)
var deps = []*Dependency{
NewDependency("name"),
NewDependency(new(serviceImpl)),
}
var depsForAnonymousEmbedded = []*Dependency{
NewDependency(42),
NewDependency(time.Now()),
}
var depsForFieldsOfStruct = []*Dependency{
NewDependency(embedded1{Age: 42}),
NewDependency(embedded2{time.Now()}),
}
var depsInterfaces = []*Dependency{
NewDependency(func(ctx *context.Context) interface{} {
return "name"
}),
}
var autoBindings = []*binding{
payloadBinding(0, reflect.TypeOf(embedded1{})),
payloadBinding(1, reflect.TypeOf(embedded2{})),
}
for _, b := range autoBindings {
b.Input.StructFieldIndex = []int{b.Input.Index}
}
var tests = []struct {
Value interface{}
Registered []*Dependency
Expected []*binding
}{
{ // 0.
Value: &controller{},
Registered: deps,
Expected: []*binding{
{
Dependency: deps[0],
Input: &Input{Index: 0, StructFieldIndex: []int{0}, Type: reflect.TypeOf("")},
},
{
Dependency: deps[1],
Input: &Input{Index: 1, StructFieldIndex: []int{1}, Type: serviceTyp},
},
},
},
// 1. test controller with pre-defined variables.
{
Value: &controller{Name: "name_struct", Service: new(serviceImpl)},
Expected: []*binding{
{
Dependency: NewDependency("name_struct"),
Input: &Input{Index: 0, StructFieldIndex: []int{0}, Type: reflect.TypeOf("")},
},
{
Dependency: NewDependency(new(serviceImpl)),
Input: &Input{Index: 1, StructFieldIndex: []int{1}, Type: serviceTyp},
},
},
},
// 2. test controller with pre-defined variables and other deps with the exact order and value
// (deps from non zero values should be registerded only, if not the Dependency:name_struct will fail for sure).
{
Value: &controller{Name: "name_struct", Service: new(serviceImpl)},
Registered: deps,
Expected: []*binding{
{
Dependency: NewDependency("name_struct"),
Input: &Input{Index: 0, StructFieldIndex: []int{0}, Type: reflect.TypeOf("")},
},
{
Dependency: NewDependency(new(serviceImpl)),
Input: &Input{Index: 1, StructFieldIndex: []int{1}, Type: serviceTyp},
},
},
},
// 3. test embedded structs with anonymous and exported.
{
Value: &controllerEmbeddingExported{},
Registered: depsForAnonymousEmbedded,
Expected: []*binding{
{
Dependency: depsForAnonymousEmbedded[0],
Input: &Input{Index: 0, StructFieldIndex: []int{0, 0}, Type: reflect.TypeOf(0)},
},
{
Dependency: depsForAnonymousEmbedded[1],
Input: &Input{Index: 1, StructFieldIndex: []int{1, 0}, Type: reflect.TypeOf(time.Time{})},
},
},
},
// 4. test for anonymous but not exported (should still be 2, unexported structs are binded).
{
Value: &controllerEmbeddingUnexported{},
Registered: depsForAnonymousEmbedded,
Expected: []*binding{
{
Dependency: depsForAnonymousEmbedded[0],
Input: &Input{Index: 0, StructFieldIndex: []int{0, 0}, Type: reflect.TypeOf(0)},
},
{
Dependency: depsForAnonymousEmbedded[1],
Input: &Input{Index: 1, StructFieldIndex: []int{1, 0}, Type: reflect.TypeOf(time.Time{})},
},
},
},
// 5. test for auto-bindings with zero registered.
{
Value: &controller2{},
Registered: nil,
Expected: autoBindings,
},
// 6. test for embedded with named fields which should NOT contain any registered deps
// except the two auto-bindings for structs,
{
Value: &controller2{},
Registered: depsForAnonymousEmbedded,
Expected: autoBindings,
}, // 7. and only embedded struct's fields are readen, otherwise we expect the struct to be a dependency.
{
Value: &controller2{},
Registered: depsForFieldsOfStruct,
Expected: []*binding{
{
Dependency: depsForFieldsOfStruct[0],
Input: &Input{Index: 0, StructFieldIndex: []int{0}, Type: reflect.TypeOf(embedded1{})},
},
{
Dependency: depsForFieldsOfStruct[1],
Input: &Input{Index: 1, StructFieldIndex: []int{1}, Type: reflect.TypeOf(embedded2{})},
},
},
},
// 8. test one exported and other not exported.
{
Value: &controller3{},
Registered: []*Dependency{depsForFieldsOfStruct[0]},
Expected: []*binding{
{
Dependency: depsForFieldsOfStruct[0],
Input: &Input{Index: 0, StructFieldIndex: []int{0}, Type: reflect.TypeOf(embedded1{})},
},
},
},
// 9. test same as the above but by registering all dependencies.
{
Value: &controller3{},
Registered: depsForFieldsOfStruct,
Expected: []*binding{
{
Dependency: depsForFieldsOfStruct[0],
Input: &Input{Index: 0, StructFieldIndex: []int{0}, Type: reflect.TypeOf(embedded1{})},
},
},
},
// 10. test bind an interface{}.
{
Value: &controller{},
Registered: depsInterfaces,
Expected: []*binding{
{
Dependency: depsInterfaces[0],
Input: &Input{Index: 0, StructFieldIndex: []int{0}, Type: reflect.TypeOf("")},
},
},
},
}
for i, tt := range tests {
bindings := getBindingsForStruct(reflect.ValueOf(tt.Value), tt.Registered, 0, nil)
if expected, got := len(tt.Expected), len(bindings); expected != got {
t.Logf("[%d] expected bindings length to be: %d but got: %d:\n", i, expected, got)
for _, b := range bindings {
t.Logf("\t%s\n", b)
}
t.FailNow()
}
for j, b := range bindings {
if tt.Expected[j] == nil {
t.Fatalf("[%d:%d] expected dependency was not found!", i, j)
}
if expected := tt.Expected[j]; !expected.Equal(b) {
t.Fatalf("[%d:%d] expected binding:\n%s\nbut got:\n%s", i, j, expected, b)
}
}
}
}