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:
|
This library has a few helper functions for helping your tests:
|
||||||
|
|
||||||
- `ksql.FillStructWith(struct interface{}, dbRow map[string]interface{}) error`
|
- `structs.FillStructWith(struct interface{}, dbRow map[string]interface{}) error`
|
||||||
- `ksql.FillSliceWith(structSlice interface{}, dbRows []map[string]interface{}) error`
|
- `structs.FillSliceWith(structSlice interface{}, dbRows []map[string]interface{}) error`
|
||||||
- `ksql.StructToMap(struct interface{}) (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
|
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)
|
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()).
|
mockDB.EXPECT().QueryChunks(gomock.Any(), gomock.Any()).
|
||||||
DoAndReturn(func(ctx context.Context, parser ksql.ChunkParser) error {
|
DoAndReturn(func(ctx context.Context, parser ksql.ChunkParser) error {
|
||||||
// Chunk 1:
|
// Chunk 1:
|
||||||
err := ksql.CallFunctionWithRows(parser.ForEachChunk, []map[string]interface{}{
|
err := structs.CallFunctionWithRows(parser.ForEachChunk, []map[string]interface{}{
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"name": "fake name",
|
"name": "fake name",
|
||||||
|
@ -215,7 +215,7 @@ func TestStreamAllUsers(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chunk 2:
|
// Chunk 2:
|
||||||
err = ksql.CallFunctionWithRows(parser.ForEachChunk, []map[string]interface{}{
|
err = structs.CallFunctionWithRows(parser.ForEachChunk, []map[string]interface{}{
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"name": "yet another fake name",
|
"name": "yet another fake name",
|
||||||
|
|
34
ksql.go
34
ksql.go
|
@ -244,7 +244,7 @@ func (c DB) QueryChunks(
|
||||||
parser ChunkParser,
|
parser ChunkParser,
|
||||||
) error {
|
) error {
|
||||||
fnValue := reflect.ValueOf(parser.ForEachChunk)
|
fnValue := reflect.ValueOf(parser.ForEachChunk)
|
||||||
chunkType, err := parseInputFunc(parser.ForEachChunk)
|
chunkType, err := structs.ParseInputFunc(parser.ForEachChunk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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{}
|
type nopScanner struct{}
|
||||||
|
|
||||||
var nopScannerValue = reflect.ValueOf(&nopScanner{}).Interface()
|
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"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// StructInfo stores metainformation of the struct
|
// StructInfo stores metainformation of the struct
|
||||||
|
@ -120,56 +118,6 @@ func StructToMap(obj interface{}) (map[string]interface{}, error) {
|
||||||
return m, nil
|
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
|
// PtrConverter was created to make it easier
|
||||||
// to handle conversion between ptr and non ptr types, e.g.:
|
// 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
|
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
|
// This function collects only the names
|
||||||
// that will be used from the input type.
|
// 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