mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
5fc24812bc
total refactor of the hero and mvc packages, see README#Next (it's not completed yet) Former-commit-id: b85ae99cbfe5965ba919c1e15cf4989e787982c0
483 lines
12 KiB
Go
483 lines
12 KiB
Go
package hero
|
|
|
|
import (
|
|
stdContext "context"
|
|
"fmt"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/kataras/iris/v12/context"
|
|
"github.com/kataras/iris/v12/sessions"
|
|
)
|
|
|
|
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 and session bindings.
|
|
{ // 12
|
|
Func: func(ctx stdContext.Context, s *sessions.Session, t time.Time) testResponse {
|
|
return testResponse{"from std context and session"}
|
|
},
|
|
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: reflect.TypeOf(time.Time{})},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
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", i, expected, got)
|
|
}
|
|
|
|
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
|
|
}
|
|
)
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|