aicra/internal/dynfunc/signature_test.go

571 lines
13 KiB
Go

package dynfunc
import (
"context"
"errors"
"fmt"
"reflect"
"testing"
"github.com/xdrm-io/aicra/api"
"github.com/xdrm-io/aicra/internal/config"
)
func TestInputValidation(t *testing.T) {
tt := []struct {
name string
input map[string]reflect.Type
fn interface{}
err error
}{
{
name: "missing context",
input: map[string]reflect.Type{},
fn: func() {},
err: ErrMissingHandlerContextArgument,
},
{
name: "invalid context",
input: map[string]reflect.Type{},
fn: func(int) {},
err: ErrInvalidHandlerContextArgument,
},
{
name: "no input 0 given",
input: map[string]reflect.Type{},
fn: func(context.Context) {},
err: nil,
},
{
name: "no input 1 given",
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)),
},
fn: func(context.Context) {},
err: ErrMissingHandlerInputArgument,
},
{
name: "1 input non-struct given",
input: map[string]reflect.Type{
"Test1": reflect.TypeOf(int(0)),
},
fn: func(context.Context, int) {},
err: ErrMissingParamArgument,
},
{
name: "unexported input",
input: map[string]reflect.Type{
"test1": reflect.TypeOf(int(0)),
},
fn: func(context.Context, struct{}) {},
err: ErrUnexportedName,
},
{
name: "1 input empty struct given",
input: map[string]reflect.Type{
"Test1": reflect.TypeOf(int(0)),
},
fn: func(context.Context, struct{}) {},
err: ErrMissingConfigArgument,
},
{
name: "1 input invalid given",
input: map[string]reflect.Type{
"Test1": reflect.TypeOf(int(0)),
},
fn: func(context.Context, struct{ Test1 string }) {},
err: ErrWrongParamTypeFromConfig,
},
{
name: "1 input valid given",
input: map[string]reflect.Type{
"Test1": reflect.TypeOf(int(0)),
},
fn: func(context.Context, struct{ Test1 int }) {},
err: nil,
},
{
name: "1 input ptr empty struct given",
input: map[string]reflect.Type{
"Test1": reflect.TypeOf(new(int)),
},
fn: func(context.Context, struct{}) {},
err: ErrMissingConfigArgument,
},
{
name: "1 input ptr invalid given",
input: map[string]reflect.Type{
"Test1": reflect.TypeOf(new(int)),
},
fn: func(context.Context, struct{ Test1 string }) {},
err: ErrWrongParamTypeFromConfig,
},
{
name: "1 input ptr invalid ptr type given",
input: map[string]reflect.Type{
"Test1": reflect.TypeOf(new(int)),
},
fn: func(context.Context, struct{ Test1 *string }) {},
err: ErrWrongParamTypeFromConfig,
},
{
name: "1 input ptr valid given",
input: map[string]reflect.Type{
"Test1": reflect.TypeOf(new(int)),
},
fn: func(context.Context, struct{ Test1 *int }) {},
err: nil,
},
{
name: "1 valid string",
input: map[string]reflect.Type{
"Test1": reflect.TypeOf(string("")),
},
fn: func(context.Context, struct{ Test1 string }) {},
err: nil,
},
{
name: "1 valid uint",
input: map[string]reflect.Type{
"Test1": reflect.TypeOf(uint(0)),
},
fn: func(context.Context, struct{ Test1 uint }) {},
err: nil,
},
{
name: "1 valid float64",
input: map[string]reflect.Type{
"Test1": reflect.TypeOf(float64(0)),
},
fn: func(context.Context, struct{ Test1 float64 }) {},
err: nil,
},
{
name: "1 valid []byte",
input: map[string]reflect.Type{
"Test1": reflect.TypeOf([]byte("")),
},
fn: func(context.Context, struct{ Test1 []byte }) {},
err: nil,
},
{
name: "1 valid []rune",
input: map[string]reflect.Type{
"Test1": reflect.TypeOf([]rune("")),
},
fn: func(context.Context, struct{ Test1 []rune }) {},
err: nil,
},
{
name: "1 valid *string",
input: map[string]reflect.Type{
"Test1": reflect.TypeOf(new(string)),
},
fn: func(context.Context, struct{ Test1 *string }) {},
err: nil,
},
{
name: "1 valid *uint",
input: map[string]reflect.Type{
"Test1": reflect.TypeOf(new(uint)),
},
fn: func(context.Context, struct{ Test1 *uint }) {},
err: nil,
},
{
name: "1 valid *float64",
input: map[string]reflect.Type{
"Test1": reflect.TypeOf(new(float64)),
},
fn: func(context.Context, struct{ Test1 *float64 }) {},
err: nil,
},
{
name: "1 valid *[]byte",
input: map[string]reflect.Type{
"Test1": reflect.TypeOf(new([]byte)),
},
fn: func(context.Context, struct{ Test1 *[]byte }) {},
err: nil,
},
{
name: "1 valid *[]rune",
input: map[string]reflect.Type{
"Test1": reflect.TypeOf(new([]rune)),
},
fn: func(context.Context, struct{ Test1 *[]rune }) {},
err: nil,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
// mock spec
s := Signature{
Input: tc.input,
Output: nil,
}
err := s.ValidateInput(reflect.TypeOf(tc.fn))
if err == nil && tc.err != nil {
t.Fatalf("expected an error: '%s'", tc.err.Error())
}
if err != nil && tc.err == nil {
t.Fatalf("unexpected error: '%s'", err.Error())
}
if err != nil && tc.err != nil {
if !errors.Is(err, tc.err) {
t.Fatalf("expected the error <%s> got <%s>", tc.err, err)
}
}
})
}
}
func TestOutputValidation(t *testing.T) {
tt := []struct {
name string
output map[string]reflect.Type
fn interface{}
err error
}{
{
name: "no output missing err",
output: map[string]reflect.Type{},
fn: func() {},
err: ErrMissingHandlerErrorArgument,
},
{
name: "no output invalid err",
output: map[string]reflect.Type{},
fn: func() bool { return true },
err: ErrInvalidHandlerErrorArgument,
},
{
name: "1 output none required",
output: map[string]reflect.Type{},
fn: func(context.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess },
err: nil,
},
{
name: "no output 1 required",
output: map[string]reflect.Type{
"Test1": reflect.TypeOf(int(0)),
},
fn: func() api.Err { return api.ErrSuccess },
err: ErrMissingHandlerOutputArgument,
},
{
name: "invalid int output",
output: map[string]reflect.Type{
"Test1": reflect.TypeOf(int(0)),
},
fn: func() (int, api.Err) { return 0, api.ErrSuccess },
err: ErrWrongOutputArgumentType,
},
{
name: "invalid int ptr output",
output: map[string]reflect.Type{
"Test1": reflect.TypeOf(int(0)),
},
fn: func() (*int, api.Err) { return nil, api.ErrSuccess },
err: ErrWrongOutputArgumentType,
},
{
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)),
},
fn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess },
err: ErrUnexportedName,
},
{
name: "missing output param",
output: map[string]reflect.Type{
"Test1": reflect.TypeOf(int(0)),
},
fn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess },
err: ErrMissingConfigArgument,
},
{
name: "invalid output param",
output: map[string]reflect.Type{
"Test1": reflect.TypeOf(int(0)),
},
fn: func() (*struct{ Test1 string }, api.Err) { return nil, api.ErrSuccess },
err: ErrWrongParamTypeFromConfig,
},
{
name: "valid param",
output: map[string]reflect.Type{
"Test1": reflect.TypeOf(int(0)),
},
fn: func() (*struct{ Test1 int }, api.Err) { return nil, api.ErrSuccess },
err: nil,
},
{
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,
},
fn: func() (*struct{ Test1 int }, api.Err) { return nil, api.ErrSuccess },
err: nil,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
// mock spec
s := Signature{
Input: nil,
Output: tc.output,
}
err := s.ValidateOutput(reflect.TypeOf(tc.fn))
if !errors.Is(err, tc.err) {
t.Fatalf("expected the error <%s> got <%s>", tc.err, err)
}
})
}
}
func TestServiceValidation(t *testing.T) {
tt := []struct {
name string
in []*config.Parameter
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)
}
})
}
}