util/test: add helper funcs #212

Merged
vtolstov merged 1 commits from test into master 2023-04-19 00:33:30 +03:00
6 changed files with 302 additions and 53 deletions

View File

@ -1,25 +0,0 @@
package test
import (
"fmt"
"strings"
"testing"
"github.com/DATA-DOG/go-sqlmock"
)
func Test_NewSQLRowsFromFile(t *testing.T) {
db, c, err := sqlmock.New()
if err != nil {
t.Fatal(err)
}
defer db.Close()
rows, err := NewSQLRowsFromFile(c, "testdata/Call.csv")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(fmt.Sprintf("%#+v", rows), `cols:[]string{"DepAgrId", "DepAgrNum", "DepAgrDate", "DepAgrCloseDate", "AccCur", "MainFinaccNum", "MainFinaccName", "MainFinaccId", "MainFinaccOpenDt", "DepAgrStatus", "MainFinaccBal", "DepartCode", "CardAccId"}`) {
t.Fatal("invalid cols after import csv")
}
}

View File

@ -1,28 +1,63 @@
package test package test
import ( import (
"bufio"
"bytes"
"context"
"encoding/csv" "encoding/csv"
"errors" "errors"
"fmt"
"io"
"os" "os"
"path" "path"
"path/filepath"
"strings" "strings"
"github.com/DATA-DOG/go-sqlmock" "github.com/DATA-DOG/go-sqlmock"
"go.unistack.org/micro/v4/client" "go.unistack.org/micro/v4/client"
"go.unistack.org/micro/v4/codec" "go.unistack.org/micro/v4/codec"
"golang.org/x/sync/errgroup"
) )
func getExt(name string) string {
ext := filepath.Ext(name)
if len(ext) > 0 && ext[0] == '.' {
ext = ext[1:]
}
return ext
}
func getNameWithoutExt(name string) string {
return strings.TrimSuffix(name, filepath.Ext(name))
}
var ErrUnknownContentType = errors.New("unknown content type") var ErrUnknownContentType = errors.New("unknown content type")
type Extension struct { type Extension struct {
Ext []string Ext []string
} }
var ExtToTypes = map[string][]string{ var (
"json": {"application/json", "application/grpc+json"}, ExtToTypes = map[string][]string{
"yaml": {"application/yaml", "application/yml", "text/yaml", "text/yml"}, "json": {"application/json", "application/grpc+json"},
"yml": {"application/yaml", "application/yml", "text/yaml", "text/yml"}, "yaml": {"application/yaml", "application/yml", "text/yaml", "text/yml"},
"proto": {"application/grpc", "application/grpc+proto", "application/proto"}, "yml": {"application/yaml", "application/yml", "text/yaml", "text/yml"},
"proto": {"application/grpc", "application/grpc+proto", "application/proto"},
}
DefaultExts = []string{"csv", "json", "yaml", "yml", "proto"}
)
func clientCall(ctx context.Context, c client.Client, req client.Request, rsp interface{}) error {
return nil
}
func NewResponseFromFile(rspfile string) (*codec.Frame, error) {
rspbuf, err := os.ReadFile(rspfile)
if err != nil {
return nil, err
}
return &codec.Frame{Data: rspbuf}, nil
} }
func NewRequestFromFile(c client.Client, reqfile string) (client.Request, error) { func NewRequestFromFile(c client.Client, reqfile string) (client.Request, error) {
@ -32,10 +67,7 @@ func NewRequestFromFile(c client.Client, reqfile string) (client.Request, error)
} }
endpoint := path.Base(path.Dir(reqfile)) endpoint := path.Base(path.Dir(reqfile))
ext := path.Ext(reqfile) ext := getExt(reqfile)
if len(ext) > 0 && ext[0] == '.' {
ext = ext[1:]
}
var ct string var ct string
if cts, ok := ExtToTypes[ext]; ok { if cts, ok := ExtToTypes[ext]; ok {
@ -56,26 +88,189 @@ func NewRequestFromFile(c client.Client, reqfile string) (client.Request, error)
return req, nil return req, nil
} }
func NewSQLRowsFromFile(c sqlmock.Sqlmock, file string) (*sqlmock.Rows, error) { func SQLFromFile(m sqlmock.Sqlmock, name string) error {
fp, err := os.Open(file) fp, err := os.Open(name)
if err != nil { if err != nil {
return nil, err return err
} }
defer fp.Close() defer fp.Close()
return SQLFromReader(m, fp)
r := csv.NewReader(fp) }
r.Comma = '\t'
r.Comment = '#' func SQLFromBytes(m sqlmock.Sqlmock, buf []byte) error {
return SQLFromReader(m, bytes.NewReader(buf))
records, err := r.ReadAll() }
if err != nil {
return nil, err func SQLFromString(m sqlmock.Sqlmock, buf string) error {
} return SQLFromReader(m, strings.NewReader(buf))
}
rows := c.NewRows(records[0])
for idx := 1; idx < len(records); idx++ { func SQLFromReader(m sqlmock.Sqlmock, r io.Reader) error {
rows.FromCSVString(strings.Join(records[idx], ";")) var rows *sqlmock.Rows
} var exp *sqlmock.ExpectedQuery
br := bufio.NewReader(r)
return rows, nil
for {
s, err := br.ReadString('\n')
if err != nil && err != io.EOF {
return err
} else if err == io.EOF && len(s) == 0 {
return nil
}
if s[0] != '#' {
r := csv.NewReader(strings.NewReader(s))
r.Comma = ','
var records [][]string
records, err = r.ReadAll()
if err != nil {
return err
}
if rows == nil {
rows = m.NewRows(records[0])
} else {
for idx := 0; idx < len(records); idx++ {
rows.FromCSVString(strings.Join(records[idx], ","))
}
}
continue
}
if rows != nil {
exp.WillReturnRows(rows)
rows = nil
}
switch {
case strings.HasPrefix(strings.ToLower(s[2:]), "begin"):
m.ExpectBegin()
case strings.HasPrefix(strings.ToLower(s[2:]), "commit"):
m.ExpectCommit()
case strings.HasPrefix(strings.ToLower(s[2:]), "rollback"):
m.ExpectRollback()
case strings.HasPrefix(strings.ToLower(s[2:]), "exec "):
m.ExpectExec(s[2+len("exec "):])
case strings.HasPrefix(strings.ToLower(s[2:]), "query "):
exp = m.ExpectQuery(s[2+len("query "):])
}
}
}
func RunWithClientExpectResults(ctx context.Context, c client.Client, m sqlmock.Sqlmock, dir string, exts []string) error {
tcases, err := getFiles(dir, exts)
if err != nil {
return err
}
g, gctx := errgroup.WithContext(ctx)
if !strings.Contains(dir, "parallel") {
g.SetLimit(1)
}
for _, tcase := range tcases {
for _, dbfile := range tcase.dbfiles {
if err = SQLFromFile(m, dbfile); err != nil {
return err
}
}
for idx := 0; idx < len(tcase.reqfiles); idx++ {
g.TryGo(func() error {
req, err := NewRequestFromFile(c, tcase.reqfiles[idx])
if err != nil {
return err
}
rsp, err := NewResponseFromFile(tcase.rspfiles[idx])
if err != nil {
return err
}
data := &codec.Frame{}
err = c.Call(gctx, req, data, client.WithContentType(req.ContentType()))
if err != nil {
return err
}
if !bytes.Equal(rsp.Data, data.Data) {
return fmt.Errorf("rsp not equal test %s != %s", rsp.Data, data.Data)
}
return nil
})
}
}
return g.Wait()
}
func RunWithClientExpectErrors(ctx context.Context, c client.Client, dir string) error {
g, gctx := errgroup.WithContext(ctx)
if !strings.Contains(dir, "parallel") {
g.SetLimit(1)
}
_ = gctx
g.TryGo(func() error {
// rsp := &codec.Frame{}
// return c.Call(ctx, req, rsp, client.WithContentType(req.ContentType()))
return nil
})
return g.Wait()
}
type Case struct {
dbfiles []string
reqfiles []string
rspfiles []string
}
func getFiles(dir string, exts []string) ([]Case, error) {
var tcases []Case
entries, err := os.ReadDir(dir)
if len(entries) == 0 && err != nil {
return tcases, err
}
if exts == nil {
exts = DefaultExts
}
var dirs []string
var dbfiles, reqfiles, rspfiles []string
for _, entry := range entries {
if entry.IsDir() {
dirs = append(dirs, filepath.Join(dir, entry.Name()))
continue
}
if info, err := entry.Info(); err != nil {
return tcases, err
} else if !info.Mode().IsRegular() {
continue
}
for _, ext := range exts {
if getExt(entry.Name()) == ext {
name := getNameWithoutExt(entry.Name())
switch {
case strings.HasSuffix(name, "_db"):
dbfiles = append(dbfiles, filepath.Join(dir, entry.Name()))
case strings.HasSuffix(name, "_req"):
reqfiles = append(reqfiles, filepath.Join(dir, entry.Name()))
case strings.HasSuffix(name, "_rsp"):
rspfiles = append(rspfiles, filepath.Join(dir, entry.Name()))
}
}
}
}
if len(reqfiles) > 0 && len(rspfiles) > 0 {
tcases = append(tcases, Case{dbfiles: dbfiles, reqfiles: reqfiles, rspfiles: rspfiles})
}
for _, dir = range dirs {
ntcases, err := getFiles(dir, exts)
if len(ntcases) == 0 && err != nil {
return tcases, err
} else if len(ntcases) == 0 {
continue
}
tcases = append(tcases, ntcases...)
}
return tcases, nil
} }

72
util/test/test_test.go Normal file
View File

@ -0,0 +1,72 @@
package test
import (
"context"
"testing"
"github.com/DATA-DOG/go-sqlmock"
)
func Test_SQLFromFile(t *testing.T) {
ctx := context.TODO()
db, c, err := sqlmock.New()
if err != nil {
t.Fatal(err)
}
defer db.Close()
if err = SQLFromFile(c, "testdata/result/01_firstcase/Call_db.csv"); err != nil {
t.Fatal(err)
}
tx, err := db.BeginTx(ctx, nil)
if err != nil {
t.Fatal(err)
}
rows, err := tx.QueryContext(ctx, "select * from test;")
if err != nil {
t.Fatal(err)
}
for rows.Next() {
var id int64
var name string
err = rows.Scan(&id, &name)
if err != nil {
t.Fatal(err)
}
if id != 1 || name != "test" {
t.Fatalf("invalid rows %v %v", id, name)
}
}
if err = rows.Close(); err != nil {
t.Fatal(err)
}
if err = rows.Err(); err != nil {
t.Fatal(err)
}
if err = tx.Commit(); err != nil {
t.Fatal(err)
}
if err = c.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func Test_getFiles(t *testing.T) {
files, err := getFiles("testdata/", nil)
if err != nil {
t.Fatal(err)
}
if len(files) == 0 {
t.Fatalf("no files matching")
}
if n := len(files); n != 1 {
t.Fatalf("invalid number of test cases %d", n)
}
}

View File

@ -0,0 +1,5 @@
# begin
# query select \* from test;
id,name
1,test
# commit
1 # begin
2 # query select \* from test;
3 id,name
4 1,test
5 # commit

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1 @@
{}