test: cover dynfunc signature to 100%

This commit is contained in:
Adrien Marquès 2021-06-21 22:46:04 +02:00
parent 178d9a8eee
commit ad178781ac
Signed by: xdrm-brackets
GPG Key ID: D75243CA236D825E
4 changed files with 420 additions and 240 deletions

View File

@ -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")
) )

View File

@ -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{

View File

@ -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
} }

View File

@ -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)
} }
}) })
} }