test: cover dynfunc signature to 100%
This commit is contained in:
parent
178d9a8eee
commit
ad178781ac
|
@ -17,6 +17,9 @@ const (
|
||||||
// errMissingHandlerArgumentParam - missing params arguments for handler
|
// errMissingHandlerArgumentParam - missing params arguments for handler
|
||||||
ErrMissingHandlerContextArgument = Err("missing handler first argument of type context.Context")
|
ErrMissingHandlerContextArgument = Err("missing handler first argument of type context.Context")
|
||||||
|
|
||||||
|
// ErrInvalidHandlerContextArgument - missing handler output error
|
||||||
|
ErrInvalidHandlerContextArgument = Err("first input argument should be of type context.Context")
|
||||||
|
|
||||||
// ErrMissingHandlerInputArgument - missing params arguments for handler
|
// ErrMissingHandlerInputArgument - missing params arguments for handler
|
||||||
ErrMissingHandlerInputArgument = Err("missing handler argument: input struct")
|
ErrMissingHandlerInputArgument = Err("missing handler argument: input struct")
|
||||||
|
|
||||||
|
@ -26,11 +29,11 @@ const (
|
||||||
// ErrMissingHandlerOutputArgument - missing output for handler
|
// ErrMissingHandlerOutputArgument - missing output for handler
|
||||||
ErrMissingHandlerOutputArgument = Err("missing handler first output argument: output struct")
|
ErrMissingHandlerOutputArgument = Err("missing handler first output argument: output struct")
|
||||||
|
|
||||||
// ErrMissingHandlerOutputError - missing error output for handler
|
// ErrMissingHandlerErrorArgument - missing error output for handler
|
||||||
ErrMissingHandlerOutputError = Err("missing handler last output argument of type api.Err")
|
ErrMissingHandlerErrorArgument = Err("missing handler last output argument of type api.Err")
|
||||||
|
|
||||||
// ErrMissingRequestArgument - missing request argument for handler
|
// ErrInvalidHandlerErrorArgument - missing handler output error
|
||||||
ErrMissingRequestArgument = Err("handler first argument must be of type api.Request")
|
ErrInvalidHandlerErrorArgument = Err("last output must be of type api.Err")
|
||||||
|
|
||||||
// ErrMissingParamArgument - missing parameters argument for handler
|
// ErrMissingParamArgument - missing parameters argument for handler
|
||||||
ErrMissingParamArgument = Err("handler second argument must be a struct")
|
ErrMissingParamArgument = Err("handler second argument must be a struct")
|
||||||
|
@ -46,7 +49,4 @@ const (
|
||||||
|
|
||||||
// ErrWrongParamTypeFromConfig - a configuration parameter type is invalid in the handler param struct
|
// ErrWrongParamTypeFromConfig - a configuration parameter type is invalid in the handler param struct
|
||||||
ErrWrongParamTypeFromConfig = Err("invalid struct field type")
|
ErrWrongParamTypeFromConfig = Err("invalid struct field type")
|
||||||
|
|
||||||
// ErrMissingHandlerErrorArgument - missing handler output error
|
|
||||||
ErrMissingHandlerErrorArgument = Err("last output must be of type api.Err")
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -20,14 +20,14 @@ type Handler struct {
|
||||||
|
|
||||||
// Build a handler from a dynamic function and checks its signature against a
|
// Build a handler from a dynamic function and checks its signature against a
|
||||||
// service configuration
|
// service configuration
|
||||||
//e
|
//
|
||||||
// `fn` must have as a signature : `func(*api.Context, in) (*out, api.Err)`
|
// `fn` must have as a signature : `func(context.Context, in) (*out, api.Err)`
|
||||||
// - `in` is a struct{} containing a field for each service input (with valid reflect.Type)
|
// - `in` is a struct{} containing a field for each service input (with valid reflect.Type)
|
||||||
// - `out` is a struct{} containing a field for each service output (with valid reflect.Type)
|
// - `out` is a struct{} containing a field for each service output (with valid reflect.Type)
|
||||||
//
|
//
|
||||||
// Special cases:
|
// Special cases:
|
||||||
// - it there is no input, `in` MUST be omitted
|
// - it there is no input, `in` MUST be omitted
|
||||||
// - it there is no output, `out` MUST be omitted
|
// - it there is no output, `out` CAN be omitted
|
||||||
func Build(fn interface{}, service config.Service) (*Handler, error) {
|
func Build(fn interface{}, service config.Service) (*Handler, error) {
|
||||||
var (
|
var (
|
||||||
h = &Handler{
|
h = &Handler{
|
||||||
|
|
|
@ -58,7 +58,7 @@ func (s *Signature) ValidateInput(handlerType reflect.Type) error {
|
||||||
firstArgType := handlerType.In(0)
|
firstArgType := handlerType.In(0)
|
||||||
|
|
||||||
if !firstArgType.Implements(ctxType) {
|
if !firstArgType.Implements(ctxType) {
|
||||||
return ErrMissingHandlerContextArgument
|
return ErrInvalidHandlerContextArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
// no input required
|
// no input required
|
||||||
|
@ -71,7 +71,7 @@ func (s *Signature) ValidateInput(handlerType reflect.Type) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// too much arguments
|
// too much arguments
|
||||||
if handlerType.NumIn() > 2 {
|
if handlerType.NumIn() != 2 {
|
||||||
return ErrMissingHandlerInputArgument
|
return ErrMissingHandlerInputArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,10 +111,10 @@ func (s Signature) ValidateOutput(handlerType reflect.Type) error {
|
||||||
// last output must be api.Err
|
// last output must be api.Err
|
||||||
lastArgType := handlerType.Out(handlerType.NumOut() - 1)
|
lastArgType := handlerType.Out(handlerType.NumOut() - 1)
|
||||||
if !lastArgType.AssignableTo(errType) {
|
if !lastArgType.AssignableTo(errType) {
|
||||||
return ErrMissingHandlerErrorArgument
|
return ErrInvalidHandlerErrorArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
// no output -> ok
|
// no output required -> ok
|
||||||
if len(s.Output) == 0 {
|
if len(s.Output) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,382 +8,562 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/xdrm-io/aicra/api"
|
"github.com/xdrm-io/aicra/api"
|
||||||
|
"github.com/xdrm-io/aicra/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInputCheck(t *testing.T) {
|
func TestInputValidation(t *testing.T) {
|
||||||
tcases := []struct {
|
tt := []struct {
|
||||||
Name string
|
name string
|
||||||
Input map[string]reflect.Type
|
input map[string]reflect.Type
|
||||||
Fn interface{}
|
fn interface{}
|
||||||
FnCtx interface{}
|
err error
|
||||||
Err error
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Name: "no input 0 given",
|
name: "missing context",
|
||||||
Input: map[string]reflect.Type{},
|
input: map[string]reflect.Type{},
|
||||||
Fn: func(context.Context) {},
|
fn: func() {},
|
||||||
FnCtx: func(context.Context) {},
|
err: ErrMissingHandlerContextArgument,
|
||||||
Err: nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "no input 1 given",
|
name: "invalid context",
|
||||||
Input: map[string]reflect.Type{},
|
input: map[string]reflect.Type{},
|
||||||
Fn: func(context.Context, int) {},
|
fn: func(int) {},
|
||||||
FnCtx: func(context.Context, int) {},
|
err: ErrInvalidHandlerContextArgument,
|
||||||
Err: ErrUnexpectedInput,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "no input 2 given",
|
name: "no input 0 given",
|
||||||
Input: map[string]reflect.Type{},
|
input: map[string]reflect.Type{},
|
||||||
Fn: func(context.Context, int, string) {},
|
fn: func(context.Context) {},
|
||||||
FnCtx: func(context.Context, int, string) {},
|
err: nil,
|
||||||
Err: ErrUnexpectedInput,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 input 0 given",
|
name: "no input 1 given",
|
||||||
Input: map[string]reflect.Type{
|
input: map[string]reflect.Type{},
|
||||||
|
fn: func(context.Context, int) {},
|
||||||
|
err: ErrUnexpectedInput,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no input 2 given",
|
||||||
|
input: map[string]reflect.Type{},
|
||||||
|
fn: func(context.Context, int, string) {},
|
||||||
|
err: ErrUnexpectedInput,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1 input 0 given",
|
||||||
|
input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(int(0)),
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func(context.Context) {},
|
fn: func(context.Context) {},
|
||||||
FnCtx: func(context.Context) {},
|
err: ErrMissingHandlerInputArgument,
|
||||||
Err: ErrMissingHandlerInputArgument,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 input non-struct given",
|
name: "1 input non-struct given",
|
||||||
Input: map[string]reflect.Type{
|
input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(int(0)),
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func(context.Context, int) {},
|
fn: func(context.Context, int) {},
|
||||||
FnCtx: func(context.Context, int) {},
|
err: ErrMissingParamArgument,
|
||||||
Err: ErrMissingParamArgument,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "unexported input",
|
name: "unexported input",
|
||||||
Input: map[string]reflect.Type{
|
input: map[string]reflect.Type{
|
||||||
"test1": reflect.TypeOf(int(0)),
|
"test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func(context.Context, struct{}) {},
|
fn: func(context.Context, struct{}) {},
|
||||||
FnCtx: func(context.Context, struct{}) {},
|
err: ErrUnexportedName,
|
||||||
Err: ErrUnexportedName,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 input empty struct given",
|
name: "1 input empty struct given",
|
||||||
Input: map[string]reflect.Type{
|
input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(int(0)),
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func(context.Context, struct{}) {},
|
fn: func(context.Context, struct{}) {},
|
||||||
FnCtx: func(context.Context, struct{}) {},
|
err: ErrMissingConfigArgument,
|
||||||
Err: ErrMissingConfigArgument,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 input invalid given",
|
name: "1 input invalid given",
|
||||||
Input: map[string]reflect.Type{
|
input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(int(0)),
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func(context.Context, struct{ Test1 string }) {},
|
fn: func(context.Context, struct{ Test1 string }) {},
|
||||||
FnCtx: func(context.Context, struct{ Test1 string }) {},
|
err: ErrWrongParamTypeFromConfig,
|
||||||
Err: ErrWrongParamTypeFromConfig,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 input valid given",
|
name: "1 input valid given",
|
||||||
Input: map[string]reflect.Type{
|
input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(int(0)),
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func(context.Context, struct{ Test1 int }) {},
|
fn: func(context.Context, struct{ Test1 int }) {},
|
||||||
FnCtx: func(context.Context, struct{ Test1 int }) {},
|
err: nil,
|
||||||
Err: nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 input ptr empty struct given",
|
name: "1 input ptr empty struct given",
|
||||||
Input: map[string]reflect.Type{
|
input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(new(int)),
|
"Test1": reflect.TypeOf(new(int)),
|
||||||
},
|
},
|
||||||
Fn: func(context.Context, struct{}) {},
|
fn: func(context.Context, struct{}) {},
|
||||||
FnCtx: func(context.Context, struct{}) {},
|
err: ErrMissingConfigArgument,
|
||||||
Err: ErrMissingConfigArgument,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 input ptr invalid given",
|
name: "1 input ptr invalid given",
|
||||||
Input: map[string]reflect.Type{
|
input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(new(int)),
|
"Test1": reflect.TypeOf(new(int)),
|
||||||
},
|
},
|
||||||
Fn: func(context.Context, struct{ Test1 string }) {},
|
fn: func(context.Context, struct{ Test1 string }) {},
|
||||||
FnCtx: func(context.Context, struct{ Test1 string }) {},
|
err: ErrWrongParamTypeFromConfig,
|
||||||
Err: ErrWrongParamTypeFromConfig,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 input ptr invalid ptr type given",
|
name: "1 input ptr invalid ptr type given",
|
||||||
Input: map[string]reflect.Type{
|
input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(new(int)),
|
"Test1": reflect.TypeOf(new(int)),
|
||||||
},
|
},
|
||||||
Fn: func(context.Context, struct{ Test1 *string }) {},
|
fn: func(context.Context, struct{ Test1 *string }) {},
|
||||||
FnCtx: func(context.Context, struct{ Test1 *string }) {},
|
err: ErrWrongParamTypeFromConfig,
|
||||||
Err: ErrWrongParamTypeFromConfig,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 input ptr valid given",
|
name: "1 input ptr valid given",
|
||||||
Input: map[string]reflect.Type{
|
input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(new(int)),
|
"Test1": reflect.TypeOf(new(int)),
|
||||||
},
|
},
|
||||||
Fn: func(context.Context, struct{ Test1 *int }) {},
|
fn: func(context.Context, struct{ Test1 *int }) {},
|
||||||
FnCtx: func(context.Context, struct{ Test1 *int }) {},
|
err: nil,
|
||||||
Err: nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 valid string",
|
name: "1 valid string",
|
||||||
Input: map[string]reflect.Type{
|
input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(string("")),
|
"Test1": reflect.TypeOf(string("")),
|
||||||
},
|
},
|
||||||
Fn: func(context.Context, struct{ Test1 string }) {},
|
fn: func(context.Context, struct{ Test1 string }) {},
|
||||||
FnCtx: func(context.Context, struct{ Test1 string }) {},
|
err: nil,
|
||||||
Err: nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 valid uint",
|
name: "1 valid uint",
|
||||||
Input: map[string]reflect.Type{
|
input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(uint(0)),
|
"Test1": reflect.TypeOf(uint(0)),
|
||||||
},
|
},
|
||||||
Fn: func(context.Context, struct{ Test1 uint }) {},
|
fn: func(context.Context, struct{ Test1 uint }) {},
|
||||||
FnCtx: func(context.Context, struct{ Test1 uint }) {},
|
err: nil,
|
||||||
Err: nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 valid float64",
|
name: "1 valid float64",
|
||||||
Input: map[string]reflect.Type{
|
input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(float64(0)),
|
"Test1": reflect.TypeOf(float64(0)),
|
||||||
},
|
},
|
||||||
Fn: func(context.Context, struct{ Test1 float64 }) {},
|
fn: func(context.Context, struct{ Test1 float64 }) {},
|
||||||
FnCtx: func(context.Context, struct{ Test1 float64 }) {},
|
err: nil,
|
||||||
Err: nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 valid []byte",
|
name: "1 valid []byte",
|
||||||
Input: map[string]reflect.Type{
|
input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf([]byte("")),
|
"Test1": reflect.TypeOf([]byte("")),
|
||||||
},
|
},
|
||||||
Fn: func(context.Context, struct{ Test1 []byte }) {},
|
fn: func(context.Context, struct{ Test1 []byte }) {},
|
||||||
FnCtx: func(context.Context, struct{ Test1 []byte }) {},
|
err: nil,
|
||||||
Err: nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 valid []rune",
|
name: "1 valid []rune",
|
||||||
Input: map[string]reflect.Type{
|
input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf([]rune("")),
|
"Test1": reflect.TypeOf([]rune("")),
|
||||||
},
|
},
|
||||||
Fn: func(context.Context, struct{ Test1 []rune }) {},
|
fn: func(context.Context, struct{ Test1 []rune }) {},
|
||||||
FnCtx: func(context.Context, struct{ Test1 []rune }) {},
|
err: nil,
|
||||||
Err: nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 valid *string",
|
name: "1 valid *string",
|
||||||
Input: map[string]reflect.Type{
|
input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(new(string)),
|
"Test1": reflect.TypeOf(new(string)),
|
||||||
},
|
},
|
||||||
Fn: func(context.Context, struct{ Test1 *string }) {},
|
fn: func(context.Context, struct{ Test1 *string }) {},
|
||||||
FnCtx: func(context.Context, struct{ Test1 *string }) {},
|
err: nil,
|
||||||
Err: nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 valid *uint",
|
name: "1 valid *uint",
|
||||||
Input: map[string]reflect.Type{
|
input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(new(uint)),
|
"Test1": reflect.TypeOf(new(uint)),
|
||||||
},
|
},
|
||||||
Fn: func(context.Context, struct{ Test1 *uint }) {},
|
fn: func(context.Context, struct{ Test1 *uint }) {},
|
||||||
FnCtx: func(context.Context, struct{ Test1 *uint }) {},
|
err: nil,
|
||||||
Err: nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 valid *float64",
|
name: "1 valid *float64",
|
||||||
Input: map[string]reflect.Type{
|
input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(new(float64)),
|
"Test1": reflect.TypeOf(new(float64)),
|
||||||
},
|
},
|
||||||
Fn: func(context.Context, struct{ Test1 *float64 }) {},
|
fn: func(context.Context, struct{ Test1 *float64 }) {},
|
||||||
FnCtx: func(context.Context, struct{ Test1 *float64 }) {},
|
err: nil,
|
||||||
Err: nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 valid *[]byte",
|
name: "1 valid *[]byte",
|
||||||
Input: map[string]reflect.Type{
|
input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(new([]byte)),
|
"Test1": reflect.TypeOf(new([]byte)),
|
||||||
},
|
},
|
||||||
Fn: func(context.Context, struct{ Test1 *[]byte }) {},
|
fn: func(context.Context, struct{ Test1 *[]byte }) {},
|
||||||
FnCtx: func(context.Context, struct{ Test1 *[]byte }) {},
|
err: nil,
|
||||||
Err: nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 valid *[]rune",
|
name: "1 valid *[]rune",
|
||||||
Input: map[string]reflect.Type{
|
input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(new([]rune)),
|
"Test1": reflect.TypeOf(new([]rune)),
|
||||||
},
|
},
|
||||||
Fn: func(context.Context, struct{ Test1 *[]rune }) {},
|
fn: func(context.Context, struct{ Test1 *[]rune }) {},
|
||||||
FnCtx: func(context.Context, struct{ Test1 *[]rune }) {},
|
err: nil,
|
||||||
Err: nil,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tcase := range tcases {
|
for _, tc := range tt {
|
||||||
t.Run(tcase.Name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// mock spec
|
// mock spec
|
||||||
s := Signature{
|
s := Signature{
|
||||||
Input: tcase.Input,
|
Input: tc.input,
|
||||||
Output: nil,
|
Output: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.ValidateInput(reflect.TypeOf(tcase.FnCtx))
|
err := s.ValidateInput(reflect.TypeOf(tc.fn))
|
||||||
if err == nil && tcase.Err != nil {
|
if err == nil && tc.err != nil {
|
||||||
t.Errorf("expected an error: '%s'", tcase.Err.Error())
|
t.Fatalf("expected an error: '%s'", tc.err.Error())
|
||||||
t.FailNow()
|
|
||||||
}
|
}
|
||||||
if err != nil && tcase.Err == nil {
|
if err != nil && tc.err == nil {
|
||||||
t.Errorf("unexpected error: '%s'", err.Error())
|
t.Fatalf("unexpected error: '%s'", err.Error())
|
||||||
t.FailNow()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil && tcase.Err != nil {
|
if err != nil && tc.err != nil {
|
||||||
if !errors.Is(err, tcase.Err) {
|
if !errors.Is(err, tc.err) {
|
||||||
t.Errorf("expected the error <%s> got <%s>", tcase.Err, err)
|
t.Fatalf("expected the error <%s> got <%s>", tc.err, err)
|
||||||
t.FailNow()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOutputCheck(t *testing.T) {
|
func TestOutputValidation(t *testing.T) {
|
||||||
tcases := []struct {
|
tt := []struct {
|
||||||
Output map[string]reflect.Type
|
name string
|
||||||
Fn interface{}
|
output map[string]reflect.Type
|
||||||
Err error
|
fn interface{}
|
||||||
|
err error
|
||||||
}{
|
}{
|
||||||
// no input -> missing api.Err
|
|
||||||
{
|
{
|
||||||
Output: map[string]reflect.Type{},
|
name: "no output missing err",
|
||||||
Fn: func(context.Context) {},
|
output: map[string]reflect.Type{},
|
||||||
Err: ErrMissingHandlerOutputArgument,
|
fn: func() {},
|
||||||
|
err: ErrMissingHandlerErrorArgument,
|
||||||
},
|
},
|
||||||
// no input -> with last type not api.Err
|
|
||||||
{
|
{
|
||||||
Output: map[string]reflect.Type{},
|
name: "no output invalid err",
|
||||||
Fn: func(context.Context) bool { return true },
|
output: map[string]reflect.Type{},
|
||||||
Err: ErrMissingHandlerErrorArgument,
|
fn: func() bool { return true },
|
||||||
|
err: ErrInvalidHandlerErrorArgument,
|
||||||
},
|
},
|
||||||
// no input -> with api.Err
|
|
||||||
{
|
{
|
||||||
Output: map[string]reflect.Type{},
|
name: "1 output none required",
|
||||||
Fn: func(context.Context) api.Err { return api.ErrSuccess },
|
output: map[string]reflect.Type{},
|
||||||
Err: nil,
|
fn: func(context.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
||||||
|
err: nil,
|
||||||
},
|
},
|
||||||
// no input -> missing context.Context
|
|
||||||
{
|
{
|
||||||
Output: map[string]reflect.Type{},
|
name: "no output 1 required",
|
||||||
Fn: func(context.Context) api.Err { return api.ErrSuccess },
|
output: map[string]reflect.Type{
|
||||||
Err: ErrMissingHandlerContextArgument,
|
|
||||||
},
|
|
||||||
// no input -> invlaid context.Context type
|
|
||||||
{
|
|
||||||
Output: map[string]reflect.Type{},
|
|
||||||
Fn: func(context.Context, int) api.Err { return api.ErrSuccess },
|
|
||||||
Err: ErrMissingHandlerContextArgument,
|
|
||||||
},
|
|
||||||
// func can have output if not specified
|
|
||||||
{
|
|
||||||
Output: map[string]reflect.Type{},
|
|
||||||
Fn: func(context.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
|
||||||
Err: nil,
|
|
||||||
},
|
|
||||||
// missing output struct in func
|
|
||||||
{
|
|
||||||
Output: map[string]reflect.Type{
|
|
||||||
"Test1": reflect.TypeOf(int(0)),
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func() api.Err { return api.ErrSuccess },
|
fn: func() api.Err { return api.ErrSuccess },
|
||||||
Err: ErrWrongOutputArgumentType,
|
err: ErrMissingHandlerOutputArgument,
|
||||||
},
|
},
|
||||||
// output not a pointer
|
|
||||||
{
|
{
|
||||||
Output: map[string]reflect.Type{
|
name: "invalid int output",
|
||||||
|
output: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(int(0)),
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func() (int, api.Err) { return 0, api.ErrSuccess },
|
fn: func() (int, api.Err) { return 0, api.ErrSuccess },
|
||||||
Err: ErrWrongOutputArgumentType,
|
err: ErrWrongOutputArgumentType,
|
||||||
},
|
},
|
||||||
// output not a pointer to struct
|
|
||||||
{
|
{
|
||||||
Output: map[string]reflect.Type{
|
name: "invalid int ptr output",
|
||||||
|
output: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(int(0)),
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func() (*int, api.Err) { return nil, api.ErrSuccess },
|
fn: func() (*int, api.Err) { return nil, api.ErrSuccess },
|
||||||
Err: ErrWrongOutputArgumentType,
|
err: ErrWrongOutputArgumentType,
|
||||||
},
|
},
|
||||||
// unexported param name
|
|
||||||
{
|
{
|
||||||
Output: map[string]reflect.Type{
|
name: "invalid struct output",
|
||||||
|
output: map[string]reflect.Type{
|
||||||
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
|
},
|
||||||
|
fn: func() (struct{ Test1 int }, api.Err) { return struct{ Test1 int }{Test1: 1}, api.ErrSuccess },
|
||||||
|
err: ErrWrongOutputArgumentType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unexported param",
|
||||||
|
output: map[string]reflect.Type{
|
||||||
"test1": reflect.TypeOf(int(0)),
|
"test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
fn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
||||||
Err: ErrUnexportedName,
|
err: ErrUnexportedName,
|
||||||
},
|
},
|
||||||
// output field missing
|
|
||||||
{
|
{
|
||||||
Output: map[string]reflect.Type{
|
name: "missing output param",
|
||||||
|
output: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(int(0)),
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
fn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
||||||
Err: ErrMissingConfigArgument,
|
err: ErrMissingConfigArgument,
|
||||||
},
|
},
|
||||||
// output field invalid type
|
|
||||||
{
|
{
|
||||||
Output: map[string]reflect.Type{
|
name: "invalid output param",
|
||||||
|
output: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(int(0)),
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func() (*struct{ Test1 string }, api.Err) { return nil, api.ErrSuccess },
|
fn: func() (*struct{ Test1 string }, api.Err) { return nil, api.ErrSuccess },
|
||||||
Err: ErrWrongParamTypeFromConfig,
|
err: ErrWrongParamTypeFromConfig,
|
||||||
},
|
},
|
||||||
// output field valid type
|
|
||||||
{
|
{
|
||||||
Output: map[string]reflect.Type{
|
name: "valid param",
|
||||||
|
output: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(int(0)),
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func() (*struct{ Test1 int }, api.Err) { return nil, api.ErrSuccess },
|
fn: func() (*struct{ Test1 int }, api.Err) { return nil, api.ErrSuccess },
|
||||||
Err: nil,
|
err: nil,
|
||||||
},
|
},
|
||||||
// ignore type check on nil type
|
|
||||||
{
|
{
|
||||||
Output: map[string]reflect.Type{
|
name: "2 valid params",
|
||||||
|
output: map[string]reflect.Type{
|
||||||
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
|
"Test2": reflect.TypeOf(string("")),
|
||||||
|
},
|
||||||
|
fn: func() (*struct {
|
||||||
|
Test1 int
|
||||||
|
Test2 string
|
||||||
|
}, api.Err) {
|
||||||
|
return nil, api.ErrSuccess
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil type ignore typecheck",
|
||||||
|
output: map[string]reflect.Type{
|
||||||
"Test1": nil,
|
"Test1": nil,
|
||||||
},
|
},
|
||||||
Fn: func() (*struct{ Test1 int }, api.Err) { return nil, api.ErrSuccess },
|
fn: func() (*struct{ Test1 int }, api.Err) { return nil, api.ErrSuccess },
|
||||||
Err: nil,
|
err: nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tcase := range tcases {
|
for _, tc := range tt {
|
||||||
t.Run(fmt.Sprintf("case.%d", i), func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// mock spec
|
// mock spec
|
||||||
s := Signature{
|
s := Signature{
|
||||||
Input: nil,
|
Input: nil,
|
||||||
Output: tcase.Output,
|
Output: tc.output,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.ValidateOutput(reflect.TypeOf(tcase.Fn))
|
err := s.ValidateOutput(reflect.TypeOf(tc.fn))
|
||||||
if err == nil && tcase.Err != nil {
|
if !errors.Is(err, tc.err) {
|
||||||
t.Errorf("expected an error: '%s'", tcase.Err.Error())
|
t.Fatalf("expected the error <%s> got <%s>", tc.err, err)
|
||||||
t.FailNow()
|
}
|
||||||
}
|
})
|
||||||
if err != nil && tcase.Err == nil {
|
}
|
||||||
t.Errorf("unexpected error: '%s'", err.Error())
|
}
|
||||||
t.FailNow()
|
|
||||||
}
|
func TestServiceValidation(t *testing.T) {
|
||||||
|
|
||||||
if err != nil && tcase.Err != nil {
|
tt := []struct {
|
||||||
if !errors.Is(err, tcase.Err) {
|
name string
|
||||||
t.Errorf("expected the error <%s> got <%s>", tcase.Err, err)
|
in []*config.Parameter
|
||||||
t.FailNow()
|
out []*config.Parameter
|
||||||
}
|
fn interface{}
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing context",
|
||||||
|
fn: func() {},
|
||||||
|
err: ErrMissingHandlerContextArgument,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid context",
|
||||||
|
fn: func(int) {},
|
||||||
|
err: ErrInvalidHandlerContextArgument,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing error",
|
||||||
|
fn: func(context.Context) {},
|
||||||
|
err: ErrMissingHandlerErrorArgument,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid error",
|
||||||
|
fn: func(context.Context) int { return 1 },
|
||||||
|
err: ErrInvalidHandlerErrorArgument,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no in no out",
|
||||||
|
fn: func(context.Context) api.Err { return api.ErrSuccess },
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unamed in",
|
||||||
|
in: []*config.Parameter{
|
||||||
|
{
|
||||||
|
Rename: "", // should be ignored
|
||||||
|
GoType: reflect.TypeOf(int(0)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fn: func(context.Context) api.Err { return api.ErrSuccess },
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing in",
|
||||||
|
in: []*config.Parameter{
|
||||||
|
{
|
||||||
|
Rename: "Test1",
|
||||||
|
GoType: reflect.TypeOf(int(0)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fn: func(context.Context) api.Err { return api.ErrSuccess },
|
||||||
|
err: ErrMissingHandlerInputArgument,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid in",
|
||||||
|
in: []*config.Parameter{
|
||||||
|
{
|
||||||
|
Rename: "Test1",
|
||||||
|
GoType: reflect.TypeOf(int(0)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fn: func(context.Context, struct{ Test1 int }) api.Err { return api.ErrSuccess },
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "optional in not ptr",
|
||||||
|
in: []*config.Parameter{
|
||||||
|
{
|
||||||
|
Rename: "Test1",
|
||||||
|
GoType: reflect.TypeOf(int(0)),
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fn: func(context.Context, struct{ Test1 int }) api.Err { return api.ErrSuccess },
|
||||||
|
err: ErrWrongParamTypeFromConfig,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid optional in",
|
||||||
|
in: []*config.Parameter{
|
||||||
|
{
|
||||||
|
Rename: "Test1",
|
||||||
|
GoType: reflect.TypeOf(int(0)),
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fn: func(context.Context, struct{ Test1 *int }) api.Err { return api.ErrSuccess },
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "unamed out",
|
||||||
|
out: []*config.Parameter{
|
||||||
|
{
|
||||||
|
Rename: "", // should be ignored
|
||||||
|
GoType: reflect.TypeOf(int(0)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fn: func(context.Context) api.Err { return api.ErrSuccess },
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing out struct",
|
||||||
|
out: []*config.Parameter{
|
||||||
|
{
|
||||||
|
Rename: "Test1",
|
||||||
|
GoType: reflect.TypeOf(int(0)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fn: func(context.Context) api.Err { return api.ErrSuccess },
|
||||||
|
err: ErrMissingHandlerOutputArgument,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid out struct type",
|
||||||
|
out: []*config.Parameter{
|
||||||
|
{
|
||||||
|
Rename: "Test1",
|
||||||
|
GoType: reflect.TypeOf(int(0)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fn: func(context.Context) (int, api.Err) { return 0, api.ErrSuccess },
|
||||||
|
err: ErrWrongOutputArgumentType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing out",
|
||||||
|
out: []*config.Parameter{
|
||||||
|
{
|
||||||
|
Rename: "Test1",
|
||||||
|
GoType: reflect.TypeOf(int(0)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fn: func(context.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
||||||
|
err: ErrMissingConfigArgument,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid out",
|
||||||
|
out: []*config.Parameter{
|
||||||
|
{
|
||||||
|
Rename: "Test1",
|
||||||
|
GoType: reflect.TypeOf(int(0)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fn: func(context.Context) (*struct{ Test1 int }, api.Err) { return nil, api.ErrSuccess },
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "optional out not ptr",
|
||||||
|
out: []*config.Parameter{
|
||||||
|
{
|
||||||
|
Rename: "Test1",
|
||||||
|
GoType: reflect.TypeOf(int(0)),
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fn: func(context.Context) (*struct{ Test1 *int }, api.Err) { return nil, api.ErrSuccess },
|
||||||
|
err: ErrWrongParamTypeFromConfig,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
service := config.Service{
|
||||||
|
Input: make(map[string]*config.Parameter),
|
||||||
|
Output: make(map[string]*config.Parameter),
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill service with arguments
|
||||||
|
if tc.in != nil && len(tc.in) > 0 {
|
||||||
|
for i, in := range tc.in {
|
||||||
|
service.Input[fmt.Sprintf("%d", i)] = in
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tc.out != nil && len(tc.out) > 0 {
|
||||||
|
for i, out := range tc.out {
|
||||||
|
service.Output[fmt.Sprintf("%d", i)] = out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s := BuildSignature(service)
|
||||||
|
|
||||||
|
err := s.ValidateInput(reflect.TypeOf(tc.fn))
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, tc.err) {
|
||||||
|
t.Fatalf("expected the error <%s> got <%s>", tc.err, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = s.ValidateOutput(reflect.TypeOf(tc.fn))
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, tc.err) {
|
||||||
|
t.Fatalf("expected the error <%s> got <%s>", tc.err, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// no error encountered but expected 1
|
||||||
|
if tc.err != nil {
|
||||||
|
t.Fatalf("expected an error <%v>", tc.err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue