mirror of https://github.com/VinGarcia/ksql.git
405 lines
11 KiB
Go
405 lines
11 KiB
Go
package ksql
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"testing"
|
|
|
|
tt "github.com/vingarcia/ksql/internal/testtools"
|
|
"github.com/vingarcia/ksql/sqldialect"
|
|
)
|
|
|
|
func TestScanArgError(t *testing.T) {
|
|
err := ScanArgError{
|
|
ColumnIndex: 12,
|
|
Err: io.EOF,
|
|
}
|
|
|
|
tt.AssertErrContains(t, err, "input attribute", "index 12", "EOF")
|
|
}
|
|
|
|
func TestConfigSetDefaultValues(t *testing.T) {
|
|
config := Config{}
|
|
config.SetDefaultValues()
|
|
|
|
tt.AssertEqual(t, config, Config{
|
|
MaxOpenConns: 1,
|
|
})
|
|
}
|
|
|
|
func TestNewAdapterWith(t *testing.T) {
|
|
t.Run("should build new instances correctly", func(t *testing.T) {
|
|
for _, dialect := range sqldialect.SupportedDialects {
|
|
db, err := NewWithAdapter(
|
|
DBAdapter(nil),
|
|
dialect,
|
|
)
|
|
|
|
tt.AssertNoErr(t, err)
|
|
tt.AssertEqual(t, db.dialect, dialect)
|
|
}
|
|
})
|
|
|
|
t.Run("should report invalid dialects correctly", func(t *testing.T) {
|
|
_, err := NewWithAdapter(
|
|
DBAdapter(nil),
|
|
nil,
|
|
)
|
|
|
|
tt.AssertErrContains(t, err, "expected a valid", "Provider", "nil")
|
|
})
|
|
}
|
|
|
|
func TestClose(t *testing.T) {
|
|
t.Run("should close the adapter if it implements the io.Closer interface", func(t *testing.T) {
|
|
c := DB{
|
|
db: struct {
|
|
DBAdapter
|
|
io.Closer
|
|
}{
|
|
DBAdapter: mockDBAdapter{},
|
|
Closer: mockCloser{
|
|
CloseFn: func() error {
|
|
return nil
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
err := c.Close()
|
|
tt.AssertNoErr(t, err)
|
|
})
|
|
|
|
t.Run("should exit normally if the adapter does not implement the io.Closer interface", func(t *testing.T) {
|
|
c := DB{
|
|
db: mockDBAdapter{},
|
|
}
|
|
|
|
err := c.Close()
|
|
tt.AssertNoErr(t, err)
|
|
})
|
|
|
|
t.Run("should report an error if the adapter.Close() returns one", func(t *testing.T) {
|
|
c := DB{
|
|
db: struct {
|
|
DBAdapter
|
|
io.Closer
|
|
}{
|
|
DBAdapter: mockDBAdapter{},
|
|
Closer: mockCloser{
|
|
CloseFn: func() error {
|
|
return fmt.Errorf("fakeCloseErrMsg")
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
err := c.Close()
|
|
tt.AssertErrContains(t, err, "fakeCloseErrMsg")
|
|
})
|
|
}
|
|
|
|
func TestInjectLogger(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
tests := []struct {
|
|
desc string
|
|
methodCall func(ctx context.Context, db Provider) error
|
|
queryErr error
|
|
|
|
expectLoggedQueryToContain []string
|
|
expectLoggedParams map[interface{}]bool
|
|
expectLoggedErrToContain []string
|
|
}{
|
|
{
|
|
desc: "should work for the Query function",
|
|
methodCall: func(ctx context.Context, db Provider) error {
|
|
var row []struct {
|
|
Count int `ksql:"count"`
|
|
}
|
|
return db.Query(ctx, &row, `FROM users WHERE type = $1 AND age < $2`, "fakeType", 42)
|
|
},
|
|
|
|
expectLoggedQueryToContain: []string{"SELECT", "count", "type = $1"},
|
|
expectLoggedParams: map[interface{}]bool{"fakeType": true, 42: true},
|
|
},
|
|
{
|
|
desc: "should work for the Query function when an error is returned",
|
|
methodCall: func(ctx context.Context, db Provider) error {
|
|
var row []struct {
|
|
Count int `ksql:"count"`
|
|
}
|
|
return db.Query(ctx, &row, `FROM users WHERE type = $1 AND age < $2`, "fakeType", 42)
|
|
},
|
|
queryErr: errors.New("fakeErrMsg"),
|
|
|
|
expectLoggedQueryToContain: []string{"SELECT", "count", "type = $1"},
|
|
expectLoggedParams: map[interface{}]bool{"fakeType": true, 42: true},
|
|
expectLoggedErrToContain: []string{"fakeErrMsg"},
|
|
},
|
|
{
|
|
desc: "should work for the QueryOne function",
|
|
methodCall: func(ctx context.Context, db Provider) error {
|
|
var row struct {
|
|
Count int `ksql:"count"`
|
|
}
|
|
return db.QueryOne(ctx, &row, `FROM users WHERE type = $1 AND age < $2`, "fakeType", 42)
|
|
},
|
|
|
|
expectLoggedQueryToContain: []string{"SELECT", "count", "type = $1"},
|
|
expectLoggedParams: map[interface{}]bool{"fakeType": true, 42: true},
|
|
},
|
|
{
|
|
desc: "should work for the QueryOne function when an error is returned",
|
|
methodCall: func(ctx context.Context, db Provider) error {
|
|
var row struct {
|
|
Count int `ksql:"count"`
|
|
}
|
|
return db.QueryOne(ctx, &row, `FROM users WHERE type = $1 AND age < $2`, "fakeType", 42)
|
|
},
|
|
queryErr: errors.New("fakeErrMsg"),
|
|
|
|
expectLoggedQueryToContain: []string{"SELECT", "count", "type = $1"},
|
|
expectLoggedParams: map[interface{}]bool{"fakeType": true, 42: true},
|
|
expectLoggedErrToContain: []string{"fakeErrMsg"},
|
|
},
|
|
{
|
|
desc: "should work for the QueryChunks function",
|
|
methodCall: func(ctx context.Context, db Provider) error {
|
|
type Row struct {
|
|
Count int `ksql:"count"`
|
|
}
|
|
return db.QueryChunks(ctx, ChunkParser{
|
|
Query: `FROM users WHERE type = $1 AND age < $2`,
|
|
Params: []interface{}{"fakeType", 42},
|
|
ChunkSize: 100,
|
|
ForEachChunk: func(row []Row) error {
|
|
return nil
|
|
},
|
|
})
|
|
},
|
|
|
|
expectLoggedQueryToContain: []string{"SELECT", "count", "type = $1"},
|
|
expectLoggedParams: map[interface{}]bool{"fakeType": true, 42: true},
|
|
},
|
|
{
|
|
desc: "should work for the QueryChunks function when an error is returned",
|
|
methodCall: func(ctx context.Context, db Provider) error {
|
|
type Row struct {
|
|
Count int `ksql:"count"`
|
|
}
|
|
return db.QueryChunks(ctx, ChunkParser{
|
|
Query: `FROM users WHERE type = $1 AND age < $2`,
|
|
Params: []interface{}{"fakeType", 42},
|
|
ChunkSize: 100,
|
|
ForEachChunk: func(row []Row) error {
|
|
return nil
|
|
},
|
|
})
|
|
},
|
|
queryErr: errors.New("fakeErrMsg"),
|
|
|
|
expectLoggedQueryToContain: []string{"SELECT", "count", "type = $1"},
|
|
expectLoggedParams: map[interface{}]bool{"fakeType": true, 42: true},
|
|
expectLoggedErrToContain: []string{"fakeErrMsg"},
|
|
},
|
|
{
|
|
desc: "should work for the Insert function",
|
|
methodCall: func(ctx context.Context, db Provider) error {
|
|
fakeRecord := struct {
|
|
ID int `ksql:"id"`
|
|
Count int `ksql:"count"`
|
|
}{
|
|
ID: 42,
|
|
Count: 43,
|
|
}
|
|
return db.Insert(ctx, NewTable("fakeTable"), &fakeRecord)
|
|
},
|
|
|
|
expectLoggedQueryToContain: []string{"INSERT", "fakeTable", `"id"`},
|
|
expectLoggedParams: map[interface{}]bool{42: true, 43: true},
|
|
},
|
|
{
|
|
desc: "should work for the Insert function when an error is returned",
|
|
methodCall: func(ctx context.Context, db Provider) error {
|
|
fakeRecord := struct {
|
|
ID int `ksql:"id"`
|
|
Count int `ksql:"count"`
|
|
}{
|
|
ID: 42,
|
|
Count: 43,
|
|
}
|
|
return db.Insert(ctx, NewTable("fakeTable"), &fakeRecord)
|
|
},
|
|
queryErr: errors.New("fakeErrMsg"),
|
|
|
|
expectLoggedQueryToContain: []string{"INSERT", "fakeTable", `"id"`},
|
|
expectLoggedParams: map[interface{}]bool{42: true, 43: true},
|
|
expectLoggedErrToContain: []string{"fakeErrMsg"},
|
|
},
|
|
{
|
|
desc: "should work for the Patch function",
|
|
methodCall: func(ctx context.Context, db Provider) error {
|
|
fakeRecord := struct {
|
|
ID int `ksql:"id"`
|
|
Count int `ksql:"count"`
|
|
}{
|
|
ID: 42,
|
|
Count: 43,
|
|
}
|
|
return db.Patch(ctx, NewTable("fakeTable"), &fakeRecord)
|
|
},
|
|
|
|
expectLoggedQueryToContain: []string{"UPDATE", "fakeTable", `"id"`},
|
|
expectLoggedParams: map[interface{}]bool{42: true, 43: true},
|
|
},
|
|
{
|
|
desc: "should work for the Patch function when an error is returned",
|
|
methodCall: func(ctx context.Context, db Provider) error {
|
|
fakeRecord := struct {
|
|
ID int `ksql:"id"`
|
|
Count int `ksql:"count"`
|
|
}{
|
|
ID: 42,
|
|
Count: 43,
|
|
}
|
|
return db.Patch(ctx, NewTable("fakeTable"), &fakeRecord)
|
|
},
|
|
queryErr: errors.New("fakeErrMsg"),
|
|
|
|
expectLoggedQueryToContain: []string{"UPDATE", "fakeTable", `"id"`},
|
|
expectLoggedParams: map[interface{}]bool{42: true, 43: true},
|
|
expectLoggedErrToContain: []string{"fakeErrMsg"},
|
|
},
|
|
{
|
|
desc: "should work for the Delete function",
|
|
methodCall: func(ctx context.Context, db Provider) error {
|
|
fakeRecord := struct {
|
|
ID int `ksql:"id"`
|
|
Count int `ksql:"count"`
|
|
}{
|
|
ID: 42,
|
|
Count: 43,
|
|
}
|
|
return db.Delete(ctx, NewTable("fakeTable"), &fakeRecord)
|
|
},
|
|
|
|
expectLoggedQueryToContain: []string{"DELETE", "fakeTable", `"id"`},
|
|
expectLoggedParams: map[interface{}]bool{42: true},
|
|
},
|
|
{
|
|
desc: "should work for the Delete function when an error is returned",
|
|
methodCall: func(ctx context.Context, db Provider) error {
|
|
fakeRecord := struct {
|
|
ID int `ksql:"id"`
|
|
Count int `ksql:"count"`
|
|
}{
|
|
ID: 42,
|
|
Count: 43,
|
|
}
|
|
return db.Delete(ctx, NewTable("fakeTable"), &fakeRecord)
|
|
},
|
|
queryErr: errors.New("fakeErrMsg"),
|
|
|
|
expectLoggedQueryToContain: []string{"DELETE", "fakeTable", `"id"`},
|
|
expectLoggedParams: map[interface{}]bool{42: true},
|
|
expectLoggedErrToContain: []string{"fakeErrMsg"},
|
|
},
|
|
{
|
|
desc: "should work for the Exec function",
|
|
methodCall: func(ctx context.Context, db Provider) error {
|
|
_, err := db.Exec(ctx, `DELETE FROM fakeTable WHERE type = $1 OR age >= $2`, "fakeType", 142)
|
|
return err
|
|
},
|
|
|
|
expectLoggedQueryToContain: []string{"DELETE", "fakeTable", "type", "age"},
|
|
expectLoggedParams: map[interface{}]bool{"fakeType": true, 142: true},
|
|
},
|
|
{
|
|
desc: "should work for the Exec function when an error is returned",
|
|
methodCall: func(ctx context.Context, db Provider) error {
|
|
_, err := db.Exec(ctx, `DELETE FROM fakeTable WHERE type = $1 OR age >= $2`, "fakeType", 142)
|
|
return err
|
|
},
|
|
queryErr: errors.New("fakeErrMsg"),
|
|
|
|
expectLoggedQueryToContain: []string{"DELETE", "fakeTable", "type", "age"},
|
|
expectLoggedParams: map[interface{}]bool{"fakeType": true, 142: true},
|
|
expectLoggedErrToContain: []string{"fakeErrMsg"},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
var inputQuery string
|
|
var inputParams []interface{}
|
|
numRows := 1
|
|
c := DB{
|
|
dialect: sqldialect.SupportedDialects["postgres"],
|
|
db: mockDBAdapter{
|
|
QueryContextFn: func(ctx context.Context, query string, params ...interface{}) (Rows, error) {
|
|
inputQuery = query
|
|
inputParams = params
|
|
|
|
return mockRows{
|
|
ScanFn: func(args ...interface{}) error {
|
|
return nil
|
|
},
|
|
// Make sure this mock will return a single row
|
|
// for the purposes of this test:
|
|
NextFn: func() bool {
|
|
numRows--
|
|
return numRows >= 0
|
|
},
|
|
ColumnsFn: func() ([]string, error) { return []string{"count"}, nil },
|
|
}, test.queryErr
|
|
},
|
|
ExecContextFn: func(ctx context.Context, query string, params ...interface{}) (Result, error) {
|
|
inputQuery = query
|
|
inputParams = params
|
|
|
|
return mockResult{
|
|
// Make sure this mock will return a single row
|
|
// for the purposes of this test:
|
|
RowsAffectedFn: func() (int64, error) {
|
|
return 1, nil
|
|
},
|
|
}, test.queryErr
|
|
},
|
|
},
|
|
}
|
|
|
|
var loggedQuery string
|
|
var loggedParams []interface{}
|
|
var loggedErr error
|
|
ctx := InjectLogger(ctx, func(ctx context.Context, values LogValues) {
|
|
loggedQuery = values.Query
|
|
loggedParams = values.Params
|
|
loggedErr = values.Err
|
|
})
|
|
|
|
err := test.methodCall(ctx, c)
|
|
if test.expectLoggedErrToContain != nil {
|
|
tt.AssertErrContains(t, err, test.expectLoggedErrToContain...)
|
|
tt.AssertErrContains(t, loggedErr, test.expectLoggedErrToContain...)
|
|
} else {
|
|
tt.AssertNoErr(t, err)
|
|
tt.AssertEqual(t, loggedErr, nil)
|
|
}
|
|
|
|
tt.AssertEqual(t, loggedQuery, inputQuery)
|
|
tt.AssertEqual(t, loggedParams, inputParams)
|
|
|
|
tt.AssertContains(t, loggedQuery, test.expectLoggedQueryToContain...)
|
|
|
|
paramsMap := map[interface{}]bool{}
|
|
for _, param := range loggedParams {
|
|
paramsMap[param] = true
|
|
}
|
|
tt.AssertEqual(t, paramsMap, test.expectLoggedParams)
|
|
})
|
|
}
|
|
}
|