mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 18:51:03 +01:00
570 lines
15 KiB
Go
570 lines
15 KiB
Go
package hero
|
|
|
|
import (
|
|
stdContext "context"
|
|
"fmt"
|
|
"net/http"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"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
|
|
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]),
|
|
Input: &Input{Index: 2, Type: BuiltinDependencies[3].DestType},
|
|
},
|
|
{
|
|
Dependency: NewDependency(BuiltinDependencies[4]),
|
|
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},
|
|
},
|
|
{
|
|
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{
|
|
{
|
|
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.
|
|
{
|
|
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, c.DisablePayloadAutoBinding, 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, false, false, DefaultDependencyMatcher, 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)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func TestBindingsForStructMarkExportedFieldsAsRequred(t *testing.T) {
|
|
type (
|
|
Embedded struct {
|
|
Val string
|
|
}
|
|
|
|
controller struct {
|
|
MyService service
|
|
Embedded *Embedded
|
|
}
|
|
)
|
|
|
|
dependencies := []*Dependency{
|
|
NewDependency(&Embedded{"test"}),
|
|
NewDependency(&serviceImpl{}),
|
|
}
|
|
|
|
// should panic if fail.
|
|
_ = getBindingsForStruct(reflect.ValueOf(new(controller)), dependencies, true, true, DefaultDependencyMatcher, 0, nil)
|
|
}
|