mirror of https://github.com/VinGarcia/ksql.git
Reorganize files so the test helpers are grouped in the same pkg
parent
5b9b0dd00d
commit
20f49eb22b
|
@ -469,9 +469,10 @@ that we actually care about, so it's better not to use composite structs.
|
|||
|
||||
This library has a few helper functions for helping your tests:
|
||||
|
||||
- `ksql.FillStructWith(struct interface{}, dbRow map[string]interface{}) error`
|
||||
- `ksql.FillSliceWith(structSlice interface{}, dbRows []map[string]interface{}) error`
|
||||
- `ksql.StructToMap(struct interface{}) (map[string]interface{}, error)`
|
||||
- `structs.FillStructWith(struct interface{}, dbRow map[string]interface{}) error`
|
||||
- `structs.FillSliceWith(structSlice interface{}, dbRows []map[string]interface{}) error`
|
||||
- `structs.StructToMap(struct interface{}) (map[string]interface{}, error)`
|
||||
- `structs.CallFunctionWithRows(fn interface{}, rows []map[string]interface{}) (map[string]interface{}, error)`
|
||||
|
||||
If you want to see examples (we have examples for all the public functions) just
|
||||
read the example tests available on our [example service](./examples/example_service)
|
||||
|
|
|
@ -198,7 +198,7 @@ func TestStreamAllUsers(t *testing.T) {
|
|||
mockDB.EXPECT().QueryChunks(gomock.Any(), gomock.Any()).
|
||||
DoAndReturn(func(ctx context.Context, parser ksql.ChunkParser) error {
|
||||
// Chunk 1:
|
||||
err := ksql.CallFunctionWithRows(parser.ForEachChunk, []map[string]interface{}{
|
||||
err := structs.CallFunctionWithRows(parser.ForEachChunk, []map[string]interface{}{
|
||||
{
|
||||
"id": 1,
|
||||
"name": "fake name",
|
||||
|
@ -215,7 +215,7 @@ func TestStreamAllUsers(t *testing.T) {
|
|||
}
|
||||
|
||||
// Chunk 2:
|
||||
err = ksql.CallFunctionWithRows(parser.ForEachChunk, []map[string]interface{}{
|
||||
err = structs.CallFunctionWithRows(parser.ForEachChunk, []map[string]interface{}{
|
||||
{
|
||||
"id": 3,
|
||||
"name": "yet another fake name",
|
||||
|
|
34
ksql.go
34
ksql.go
|
@ -244,7 +244,7 @@ func (c DB) QueryChunks(
|
|||
parser ChunkParser,
|
||||
) error {
|
||||
fnValue := reflect.ValueOf(parser.ForEachChunk)
|
||||
chunkType, err := parseInputFunc(parser.ForEachChunk)
|
||||
chunkType, err := structs.ParseInputFunc(parser.ForEachChunk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -759,38 +759,6 @@ func (c DB) Transaction(ctx context.Context, fn func(SQLProvider) error) error {
|
|||
}
|
||||
}
|
||||
|
||||
var errType = reflect.TypeOf(new(error)).Elem()
|
||||
|
||||
func parseInputFunc(fn interface{}) (reflect.Type, error) {
|
||||
if fn == nil {
|
||||
return nil, fmt.Errorf("the ForEachChunk attribute is required and cannot be nil")
|
||||
}
|
||||
|
||||
t := reflect.TypeOf(fn)
|
||||
|
||||
if t.Kind() != reflect.Func {
|
||||
return nil, fmt.Errorf("the ForEachChunk callback must be a function")
|
||||
}
|
||||
if t.NumIn() != 1 {
|
||||
return nil, fmt.Errorf("the ForEachChunk callback must have 1 argument")
|
||||
}
|
||||
|
||||
if t.NumOut() != 1 {
|
||||
return nil, fmt.Errorf("the ForEachChunk callback must have a single return value")
|
||||
}
|
||||
|
||||
if t.Out(0) != errType {
|
||||
return nil, fmt.Errorf("the return value of the ForEachChunk callback must be of type error")
|
||||
}
|
||||
|
||||
argsType := t.In(0)
|
||||
if argsType.Kind() != reflect.Slice {
|
||||
return nil, fmt.Errorf("the argument of the ForEachChunk callback must a slice of structs")
|
||||
}
|
||||
|
||||
return argsType, nil
|
||||
}
|
||||
|
||||
type nopScanner struct{}
|
||||
|
||||
var nopScannerValue = reflect.ValueOf(&nopScanner{}).Interface()
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package structs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var errType = reflect.TypeOf(new(error)).Elem()
|
||||
|
||||
// ParseInputFunc is used exclusively for parsing
|
||||
// the ForEachChunk function used on the QueryChunks method.
|
||||
func ParseInputFunc(fn interface{}) (reflect.Type, error) {
|
||||
if fn == nil {
|
||||
return nil, fmt.Errorf("the ForEachChunk attribute is required and cannot be nil")
|
||||
}
|
||||
|
||||
t := reflect.TypeOf(fn)
|
||||
|
||||
if t.Kind() != reflect.Func {
|
||||
return nil, fmt.Errorf("the ForEachChunk callback must be a function")
|
||||
}
|
||||
if t.NumIn() != 1 {
|
||||
return nil, fmt.Errorf("the ForEachChunk callback must have 1 argument")
|
||||
}
|
||||
|
||||
if t.NumOut() != 1 {
|
||||
return nil, fmt.Errorf("the ForEachChunk callback must have a single return value")
|
||||
}
|
||||
|
||||
if t.Out(0) != errType {
|
||||
return nil, fmt.Errorf("the return value of the ForEachChunk callback must be of type error")
|
||||
}
|
||||
|
||||
argsType := t.In(0)
|
||||
if argsType.Kind() != reflect.Slice {
|
||||
return nil, fmt.Errorf("the argument of the ForEachChunk callback must a slice of structs")
|
||||
}
|
||||
|
||||
return argsType, nil
|
||||
}
|
|
@ -4,8 +4,6 @@ import (
|
|||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// StructInfo stores metainformation of the struct
|
||||
|
@ -120,56 +118,6 @@ func StructToMap(obj interface{}) (map[string]interface{}, error) {
|
|||
return m, nil
|
||||
}
|
||||
|
||||
// FillStructWith is meant to be used on unit tests to mock
|
||||
// the response from the database.
|
||||
//
|
||||
// The first argument is any struct you are passing to a ksql func,
|
||||
// and the second is a map representing a database row you want
|
||||
// to use to update this struct.
|
||||
func FillStructWith(record interface{}, dbRow map[string]interface{}) error {
|
||||
v := reflect.ValueOf(record)
|
||||
t := v.Type()
|
||||
|
||||
if t.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf(
|
||||
"FillStructWith: expected input to be a pointer to struct but got %T",
|
||||
record,
|
||||
)
|
||||
}
|
||||
|
||||
t = t.Elem()
|
||||
v = v.Elem()
|
||||
|
||||
if t.Kind() != reflect.Struct {
|
||||
return fmt.Errorf(
|
||||
"FillStructWith: expected input kind to be a struct but got %T",
|
||||
record,
|
||||
)
|
||||
}
|
||||
|
||||
info := getCachedTagInfo(tagInfoCache, t)
|
||||
for colName, rawSrc := range dbRow {
|
||||
fieldInfo := info.ByName(colName)
|
||||
if !fieldInfo.Valid {
|
||||
// Ignore columns not tagged with `ksql:"..."`
|
||||
continue
|
||||
}
|
||||
|
||||
src := NewPtrConverter(rawSrc)
|
||||
dest := v.Field(fieldInfo.Index)
|
||||
destType := t.Field(fieldInfo.Index).Type
|
||||
|
||||
destValue, err := src.Convert(destType)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("FillStructWith: error on field `%s`", colName))
|
||||
}
|
||||
|
||||
dest.Set(destValue)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PtrConverter was created to make it easier
|
||||
// to handle conversion between ptr and non ptr types, e.g.:
|
||||
//
|
||||
|
@ -251,49 +199,6 @@ func (p PtrConverter) Convert(destType reflect.Type) (reflect.Value, error) {
|
|||
return destValue, nil
|
||||
}
|
||||
|
||||
// FillSliceWith is meant to be used on unit tests to mock
|
||||
// the response from the database.
|
||||
//
|
||||
// The first argument is any slice of structs you are passing to a ksql func,
|
||||
// and the second is a slice of maps representing the database rows you want
|
||||
// to use to update this struct.
|
||||
func FillSliceWith(entities interface{}, dbRows []map[string]interface{}) error {
|
||||
sliceRef := reflect.ValueOf(entities)
|
||||
sliceType := sliceRef.Type()
|
||||
if sliceType.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf(
|
||||
"FillSliceWith: expected input to be a pointer to a slice of structs but got %v",
|
||||
sliceType,
|
||||
)
|
||||
}
|
||||
|
||||
structType, isSliceOfPtrs, err := DecodeAsSliceOfStructs(sliceType.Elem())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "FillSliceWith")
|
||||
}
|
||||
|
||||
slice := sliceRef.Elem()
|
||||
for idx, row := range dbRows {
|
||||
if slice.Len() <= idx {
|
||||
var elemValue reflect.Value
|
||||
elemValue = reflect.New(structType)
|
||||
if !isSliceOfPtrs {
|
||||
elemValue = elemValue.Elem()
|
||||
}
|
||||
slice = reflect.Append(slice, elemValue)
|
||||
}
|
||||
|
||||
err := FillStructWith(slice.Index(idx).Addr().Interface(), row)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "FillSliceWith")
|
||||
}
|
||||
}
|
||||
|
||||
sliceRef.Elem().Set(slice)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// This function collects only the names
|
||||
// that will be used from the input type.
|
||||
//
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
package structs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// FillStructWith is meant to be used on unit tests to mock
|
||||
// the response from the database.
|
||||
//
|
||||
// The first argument is any struct you are passing to a ksql func,
|
||||
// and the second is a map representing a database row you want
|
||||
// to use to update this struct.
|
||||
func FillStructWith(record interface{}, dbRow map[string]interface{}) error {
|
||||
v := reflect.ValueOf(record)
|
||||
t := v.Type()
|
||||
|
||||
if t.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf(
|
||||
"FillStructWith: expected input to be a pointer to struct but got %T",
|
||||
record,
|
||||
)
|
||||
}
|
||||
|
||||
t = t.Elem()
|
||||
v = v.Elem()
|
||||
|
||||
if t.Kind() != reflect.Struct {
|
||||
return fmt.Errorf(
|
||||
"FillStructWith: expected input kind to be a struct but got %T",
|
||||
record,
|
||||
)
|
||||
}
|
||||
|
||||
info := GetTagInfo(t)
|
||||
for colName, rawSrc := range dbRow {
|
||||
fieldInfo := info.ByName(colName)
|
||||
if !fieldInfo.Valid {
|
||||
// Ignore columns not tagged with `ksql:"..."`
|
||||
continue
|
||||
}
|
||||
|
||||
src := NewPtrConverter(rawSrc)
|
||||
dest := v.Field(fieldInfo.Index)
|
||||
destType := t.Field(fieldInfo.Index).Type
|
||||
|
||||
destValue, err := src.Convert(destType)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("FillStructWith: error on field `%s`", colName))
|
||||
}
|
||||
|
||||
dest.Set(destValue)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FillSliceWith is meant to be used on unit tests to mock
|
||||
// the response from the database.
|
||||
//
|
||||
// The first argument is any slice of structs you are passing to a ksql func,
|
||||
// and the second is a slice of maps representing the database rows you want
|
||||
// to use to update this struct.
|
||||
func FillSliceWith(entities interface{}, dbRows []map[string]interface{}) error {
|
||||
sliceRef := reflect.ValueOf(entities)
|
||||
sliceType := sliceRef.Type()
|
||||
if sliceType.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf(
|
||||
"FillSliceWith: expected input to be a pointer to a slice of structs but got %v",
|
||||
sliceType,
|
||||
)
|
||||
}
|
||||
|
||||
structType, isSliceOfPtrs, err := DecodeAsSliceOfStructs(sliceType.Elem())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "FillSliceWith")
|
||||
}
|
||||
|
||||
slice := sliceRef.Elem()
|
||||
for idx, row := range dbRows {
|
||||
if slice.Len() <= idx {
|
||||
var elemValue reflect.Value
|
||||
elemValue = reflect.New(structType)
|
||||
if !isSliceOfPtrs {
|
||||
elemValue = elemValue.Elem()
|
||||
}
|
||||
slice = reflect.Append(slice, elemValue)
|
||||
}
|
||||
|
||||
err := FillStructWith(slice.Index(idx).Addr().Interface(), row)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "FillSliceWith")
|
||||
}
|
||||
}
|
||||
|
||||
sliceRef.Elem().Set(slice)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CallFunctionWithRows was created for helping test the QueryChunks method
|
||||
func CallFunctionWithRows(fn interface{}, rows []map[string]interface{}) error {
|
||||
fnValue := reflect.ValueOf(fn)
|
||||
chunkType, err := ParseInputFunc(fn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chunk := reflect.MakeSlice(chunkType, 0, len(rows))
|
||||
|
||||
// Create a pointer to a slice (required by FillSliceWith)
|
||||
chunkPtr := reflect.New(chunkType)
|
||||
chunkPtr.Elem().Set(chunk)
|
||||
|
||||
err = FillSliceWith(chunkPtr.Interface(), rows)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err, _ = fnValue.Call([]reflect.Value{chunkPtr.Elem()})[0].Interface().(error)
|
||||
|
||||
return err
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package ksql
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/vingarcia/ksql/structs"
|
||||
)
|
||||
|
||||
// CallFunctionWithRows was created for helping test the QueryChunks method
|
||||
func CallFunctionWithRows(fn interface{}, rows []map[string]interface{}) error {
|
||||
fnValue := reflect.ValueOf(fn)
|
||||
chunkType, err := parseInputFunc(fn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chunk := reflect.MakeSlice(chunkType, 0, len(rows))
|
||||
|
||||
// Create a pointer to a slice (required by FillSliceWith)
|
||||
chunkPtr := reflect.New(chunkType)
|
||||
chunkPtr.Elem().Set(chunk)
|
||||
|
||||
err = structs.FillSliceWith(chunkPtr.Interface(), rows)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err, _ = fnValue.Call([]reflect.Value{chunkPtr.Elem()})[0].Interface().(error)
|
||||
|
||||
return err
|
||||
}
|
Loading…
Reference in New Issue