aicra/builder_test.go

440 lines
9.5 KiB
Go

package aicra
import (
"context"
"errors"
"net/http"
"strings"
"testing"
"github.com/xdrm-io/aicra/api"
"github.com/xdrm-io/aicra/internal/dynfunc"
"github.com/xdrm-io/aicra/validator"
)
func addBuiltinTypes(b *Builder) error {
if err := b.Validate(validator.AnyType{}); err != nil {
return err
}
if err := b.Validate(validator.BoolType{}); err != nil {
return err
}
if err := b.Validate(validator.FloatType{}); err != nil {
return err
}
if err := b.Validate(validator.IntType{}); err != nil {
return err
}
if err := b.Validate(validator.StringType{}); err != nil {
return err
}
if err := b.Validate(validator.UintType{}); err != nil {
return err
}
return nil
}
func TestAddType(t *testing.T) {
t.Parallel()
builder := &Builder{}
err := builder.Validate(validator.BoolType{})
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
err = builder.Setup(strings.NewReader("[]"))
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
err = builder.Validate(validator.FloatType{})
if err != errLateType {
t.Fatalf("expected <%v> got <%v>", errLateType, err)
}
}
func TestSetupNoType(t *testing.T) {
t.Parallel()
builder := &Builder{}
err := builder.Setup(strings.NewReader("[]"))
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
}
func TestSetupTwice(t *testing.T) {
t.Parallel()
builder := &Builder{}
err := builder.Setup(strings.NewReader("[]"))
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
// double Setup() must fail
err = builder.Setup(strings.NewReader("[]"))
if err != errAlreadySetup {
t.Fatalf("expected error %v, got %v", errAlreadySetup, err)
}
}
func TestBindBeforeSetup(t *testing.T) {
t.Parallel()
builder := &Builder{}
// binding before Setup() must fail
err := builder.Bind(http.MethodGet, "/path", func() {})
if err != errNotSetup {
t.Fatalf("expected error %v, got %v", errNotSetup, err)
}
}
func TestBindUnknownService(t *testing.T) {
t.Parallel()
builder := &Builder{}
err := builder.Setup(strings.NewReader("[]"))
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
err = builder.Bind(http.MethodGet, "/path", func() {})
if !errors.Is(err, errUnknownService) {
t.Fatalf("expected error %v, got %v", errUnknownService, err)
}
}
func TestBindInvalidHandler(t *testing.T) {
t.Parallel()
builder := &Builder{}
err := builder.Setup(strings.NewReader(`[
{
"method": "GET",
"path": "/path",
"scope": [[]],
"info": "info",
"in": {},
"out": {}
}
]`))
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
err = builder.Bind(http.MethodGet, "/path", func() {})
if err == nil {
t.Fatalf("expected an error")
}
if !errors.Is(err, dynfunc.ErrMissingHandlerContextArgument) {
t.Fatalf("expected a dynfunc.Err got %v", err)
}
}
func TestBindGet(t *testing.T) {
t.Parallel()
builder := &Builder{}
err := builder.Setup(strings.NewReader(`[
{
"method": "GET",
"path": "/path",
"scope": [[]],
"info": "info",
"in": {},
"out": {}
},
{
"method": "POST",
"path": "/path",
"scope": [[]],
"info": "info",
"in": {},
"out": {}
},
{
"method": "PUT",
"path": "/path",
"scope": [[]],
"info": "info",
"in": {},
"out": {}
},
{
"method": "DELETE",
"path": "/path",
"scope": [[]],
"info": "info",
"in": {},
"out": {}
}
]`))
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
err = builder.Get("/path", func(context.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess })
if err != nil {
t.Fatalf("unexpected error %v", err)
}
err = builder.Post("/path", func(context.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess })
if err != nil {
t.Fatalf("unexpected error %v", err)
}
err = builder.Put("/path", func(context.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess })
if err != nil {
t.Fatalf("unexpected error %v", err)
}
err = builder.Delete("/path", func(context.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess })
if err != nil {
t.Fatalf("unexpected error %v", err)
}
}
func TestUnhandledService(t *testing.T) {
t.Parallel()
builder := &Builder{}
err := builder.Setup(strings.NewReader(`[
{
"method": "GET",
"path": "/path",
"scope": [[]],
"info": "info",
"in": {},
"out": {}
},
{
"method": "POST",
"path": "/path",
"scope": [[]],
"info": "info",
"in": {},
"out": {}
}
]`))
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
err = builder.Get("/path", func(context.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess })
if err != nil {
t.Fatalf("unexpected error %v", err)
}
_, err = builder.Build()
if !errors.Is(err, errMissingHandler) {
t.Fatalf("expected a %v error, got %v", errMissingHandler, err)
}
}
func TestBind(t *testing.T) {
t.Parallel()
tcases := []struct {
Name string
Config string
HandlerMethod string
HandlerPath string
HandlerFn interface{} // not bound if nil
BindErr error
BuildErr error
}{
{
Name: "none required none provided",
Config: "[]",
HandlerMethod: "",
HandlerPath: "",
HandlerFn: nil,
BindErr: nil,
BuildErr: nil,
},
{
Name: "none required 1 provided",
Config: "[]",
HandlerMethod: "",
HandlerPath: "",
HandlerFn: func(context.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess },
BindErr: errUnknownService,
BuildErr: nil,
},
{
Name: "1 required none provided",
Config: `[
{
"method": "GET",
"path": "/path",
"scope": [[]],
"info": "info",
"in": {},
"out": {}
}
]`,
HandlerMethod: "",
HandlerPath: "",
HandlerFn: nil,
BindErr: nil,
BuildErr: errMissingHandler,
},
{
Name: "1 required wrong method provided",
Config: `[
{
"method": "GET",
"path": "/path",
"scope": [[]],
"info": "info",
"in": {},
"out": {}
}
]`,
HandlerMethod: http.MethodPost,
HandlerPath: "/path",
HandlerFn: func(context.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess },
BindErr: errUnknownService,
BuildErr: errMissingHandler,
},
{
Name: "1 required wrong path provided",
Config: `[
{
"method": "GET",
"path": "/path",
"scope": [[]],
"info": "info",
"in": {},
"out": {}
}
]`,
HandlerMethod: http.MethodGet,
HandlerPath: "/paths",
HandlerFn: func(context.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess },
BindErr: errUnknownService,
BuildErr: errMissingHandler,
},
{
Name: "1 required valid provided",
Config: `[
{
"method": "GET",
"path": "/path",
"scope": [[]],
"info": "info",
"in": {},
"out": {}
}
]`,
HandlerMethod: http.MethodGet,
HandlerPath: "/path",
HandlerFn: func(context.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess },
BindErr: nil,
BuildErr: nil,
},
{
Name: "1 required with int",
Config: `[
{
"method": "GET",
"path": "/path",
"scope": [[]],
"info": "info",
"in": {
"id": { "info": "info", "type": "int", "name": "Name" }
},
"out": {}
}
]`,
HandlerMethod: http.MethodGet,
HandlerPath: "/path",
HandlerFn: func(context.Context, struct{ Name int }) (*struct{}, api.Err) { return nil, api.ErrSuccess },
BindErr: nil,
BuildErr: nil,
},
{
Name: "1 required with uint",
Config: `[
{
"method": "GET",
"path": "/path",
"scope": [[]],
"info": "info",
"in": {
"id": { "info": "info", "type": "uint", "name": "Name" }
},
"out": {}
}
]`,
HandlerMethod: http.MethodGet,
HandlerPath: "/path",
HandlerFn: func(context.Context, struct{ Name uint }) (*struct{}, api.Err) { return nil, api.ErrSuccess },
BindErr: nil,
BuildErr: nil,
},
{
Name: "1 required with string",
Config: `[
{
"method": "GET",
"path": "/path",
"scope": [[]],
"info": "info",
"in": {
"id": { "info": "info", "type": "string", "name": "Name" }
},
"out": {}
}
]`,
HandlerMethod: http.MethodGet,
HandlerPath: "/path",
HandlerFn: func(context.Context, struct{ Name string }) (*struct{}, api.Err) { return nil, api.ErrSuccess },
BindErr: nil,
BuildErr: nil,
},
{
Name: "1 required with bool",
Config: `[
{
"method": "GET",
"path": "/path",
"scope": [[]],
"info": "info",
"in": {
"id": { "info": "info", "type": "bool", "name": "Name" }
},
"out": {}
}
]`,
HandlerMethod: http.MethodGet,
HandlerPath: "/path",
HandlerFn: func(context.Context, struct{ Name bool }) (*struct{}, api.Err) { return nil, api.ErrSuccess },
BindErr: nil,
BuildErr: nil,
},
}
for _, tcase := range tcases {
t.Run(tcase.Name, func(t *testing.T) {
t.Parallel()
builder := &Builder{}
if err := addBuiltinTypes(builder); err != nil {
t.Fatalf("add built-in types: %s", err)
}
err := builder.Setup(strings.NewReader(tcase.Config))
if err != nil {
t.Fatalf("setup: unexpected error <%v>", err)
}
if tcase.HandlerFn != nil {
err := builder.Bind(tcase.HandlerMethod, tcase.HandlerPath, tcase.HandlerFn)
if !errors.Is(err, tcase.BindErr) {
t.Fatalf("bind: expected <%v> got <%v>", tcase.BindErr, err)
}
}
_, err = builder.Build()
if !errors.Is(err, tcase.BuildErr) {
t.Fatalf("build: expected <%v> got <%v>", tcase.BuildErr, err)
}
})
}
}