package dynfunc import ( "errors" "fmt" "reflect" "testing" "git.xdrm.io/go/aicra/api" ) func TestInputCheck(t *testing.T) { tcases := []struct { Name string Input map[string]reflect.Type Fn interface{} Err error }{ { Name: "no argument required, missing context", Input: map[string]reflect.Type{}, Fn: func() {}, Err: errMissingContext, }, { Name: "no argument required, invalid context", Input: map[string]reflect.Type{}, Fn: func(int) {}, Err: errMissingContext, }, { Name: "no argument required, valid context", Input: map[string]reflect.Type{}, Fn: func(*api.Context) {}, Err: nil, }, { Name: "argument but none required", Input: map[string]reflect.Type{}, Fn: func(*api.Context, int) {}, Err: errUnexpectedInput, }, { Name: "arguments but none required", Input: map[string]reflect.Type{}, Fn: func(*api.Context, int, string) {}, Err: errUnexpectedInput, }, { Name: "int required, no context", Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(int(0)), }, Fn: func() {}, Err: errMissingContext, }, { Name: "int required, invalid context", Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(int(0)), }, Fn: func(int) {}, Err: errMissingContext, }, { Name: "int required, only context provided", Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(int(0)), }, Fn: func(*api.Context) {}, Err: errMissingHandlerArgumentParam, }, { Name: "non-struct second argument", Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(int(0)), }, Fn: func(*api.Context, int) {}, Err: errMissingParamArgument, }, { Name: "fail on unexported param", Input: map[string]reflect.Type{ "test1": reflect.TypeOf(int(0)), }, Fn: func(*api.Context, struct{}) {}, Err: errUnexportedName, }, { Name: "struct with missing field", Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(int(0)), }, Fn: func(*api.Context, struct{}) {}, Err: errMissingParamFromConfig, }, { Name: "field with missing type", Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(int(0)), }, Fn: func(*api.Context, struct{ Test1 string }) {}, Err: errWrongParamTypeFromConfig, }, { Name: "valid input", Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(int(0)), }, Fn: func(*api.Context, struct{ Test1 int }) {}, Err: nil, }, } for _, tcase := range tcases { t.Run(tcase.Name, func(t *testing.T) { // mock spec s := spec{ Input: tcase.Input, Output: nil, } err := s.checkInput(reflect.ValueOf(tcase.Fn)) if err == nil && tcase.Err != nil { t.Errorf("expected an error: '%s'", tcase.Err.Error()) t.FailNow() } if err != nil && tcase.Err == nil { t.Errorf("unexpected error: '%s'", err.Error()) t.FailNow() } if err != nil && tcase.Err != nil { if !errors.Is(err, tcase.Err) { t.Errorf("expected the error <%s> got <%s>", tcase.Err, err) t.FailNow() } } }) } } func TestOutputCheck(t *testing.T) { tcases := []struct { Output map[string]reflect.Type Fn interface{} Err error }{ // no input -> missing api.Err { Output: map[string]reflect.Type{}, Fn: func() {}, Err: errMissingHandlerOutput, }, // no input -> with last type not api.Err { Output: map[string]reflect.Type{}, Fn: func() bool { return true }, Err: errMissingHandlerErrorOutput, }, // no input -> with api.Err { Output: map[string]reflect.Type{}, Fn: func() api.Err { return api.ErrSuccess }, Err: nil, }, // func can have output if not specified { Output: map[string]reflect.Type{}, Fn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess }, Err: nil, }, // missing output struct in func { Output: map[string]reflect.Type{ "Test1": reflect.TypeOf(int(0)), }, Fn: func() api.Err { return api.ErrSuccess }, Err: errMissingParamOutput, }, // output not a pointer { Output: map[string]reflect.Type{ "Test1": reflect.TypeOf(int(0)), }, Fn: func() (int, api.Err) { return 0, api.ErrSuccess }, Err: errMissingParamOutput, }, // output not a pointer to struct { Output: map[string]reflect.Type{ "Test1": reflect.TypeOf(int(0)), }, Fn: func() (*int, api.Err) { return nil, api.ErrSuccess }, Err: errMissingParamOutput, }, // unexported param name { Output: map[string]reflect.Type{ "test1": reflect.TypeOf(int(0)), }, Fn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess }, Err: errUnexportedName, }, // output field missing { Output: map[string]reflect.Type{ "Test1": reflect.TypeOf(int(0)), }, Fn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess }, Err: errMissingParamFromConfig, }, // output field invalid type { Output: map[string]reflect.Type{ "Test1": reflect.TypeOf(int(0)), }, Fn: func() (*struct{ Test1 string }, api.Err) { return nil, api.ErrSuccess }, Err: errWrongParamTypeFromConfig, }, // output field valid type { Output: map[string]reflect.Type{ "Test1": reflect.TypeOf(int(0)), }, Fn: func() (*struct{ Test1 int }, api.Err) { return nil, api.ErrSuccess }, Err: nil, }, // ignore type check on nil type { Output: map[string]reflect.Type{ "Test1": nil, }, Fn: func() (*struct{ Test1 int }, api.Err) { return nil, api.ErrSuccess }, Err: nil, }, } for i, tcase := range tcases { t.Run(fmt.Sprintf("case.%d", i), func(t *testing.T) { // mock spec s := spec{ Input: nil, Output: tcase.Output, } err := s.checkOutput(reflect.ValueOf(tcase.Fn)) if err == nil && tcase.Err != nil { t.Errorf("expected an error: '%s'", tcase.Err.Error()) t.FailNow() } if err != nil && tcase.Err == nil { t.Errorf("unexpected error: '%s'", err.Error()) t.FailNow() } if err != nil && tcase.Err != nil { if !errors.Is(err, tcase.Err) { t.Errorf("expected the error <%s> got <%s>", tcase.Err, err) t.FailNow() } } }) } }