From 4b37adc905b91b2de2aa50a5312e542f1c5a56ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Garcia?= Date: Sat, 27 Aug 2022 12:16:46 -0300 Subject: [PATCH] Add more tests to ksql.Query() --- internal/structs/structs.go | 6 +-- internal_mocks.go | 38 ++++++++++++++- test_adapters.go | 92 +++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 4 deletions(-) diff --git a/internal/structs/structs.go b/internal/structs/structs.go index 8f7c3db..fcf556c 100644 --- a/internal/structs/structs.go +++ b/internal/structs/structs.go @@ -93,11 +93,11 @@ func GetTagInfo(key reflect.Type) (StructInfo, error) { func getCachedTagInfo(tagInfoCache *sync.Map, key reflect.Type) (StructInfo, error) { if data, found := tagInfoCache.Load(key); found { - if info, ok := data.(StructInfo); !ok { + info, ok := data.(StructInfo) + if !ok { return StructInfo{}, fmt.Errorf("invalid cache entry, expected type StructInfo, found %T", data) - } else { - return info, nil } + return info, nil } info, err := getTagNames(key) diff --git a/internal_mocks.go b/internal_mocks.go index 2fae9b9..013e3ac 100644 --- a/internal_mocks.go +++ b/internal_mocks.go @@ -1,6 +1,8 @@ package ksql -import "context" +import ( + "context" +) // mockTxBeginner mocks the ksql.TxBeginner interface type mockTxBeginner struct { @@ -26,6 +28,40 @@ func (m mockDBAdapter) QueryContext(ctx context.Context, query string, args ...i return m.QueryContextFn(ctx, query, args...) } +type mockRows struct { + ScanFn func(...interface{}) error + CloseFn func() error + NextFn func() bool + ErrFn func() error + ColumnsFn func() ([]string, error) +} + +func (m mockRows) Scan(values ...interface{}) error { + return m.ScanFn(values...) +} + +func (m mockRows) Close() error { + if m.CloseFn == nil { + return nil + } + return m.CloseFn() +} + +func (m mockRows) Next() bool { + return m.NextFn() +} + +func (m mockRows) Err() error { + if m.ErrFn == nil { + return nil + } + return m.ErrFn() +} + +func (m mockRows) Columns() ([]string, error) { + return m.ColumnsFn() +} + // mockTx mocks the ksql.Tx interface type mockTx struct { DBAdapter diff --git a/test_adapters.go b/test_adapters.go index 8beb629..517526a 100644 --- a/test_adapters.go +++ b/test_adapters.go @@ -403,6 +403,23 @@ func QueryTest( tt.AssertErrContains(t, err, "error running query") }) + t.Run("should report error if the TagInfoCache returns an error", func(t *testing.T) { + db, closer := newDBAdapter(t) + defer closer.Close() + + ctx := context.Background() + c := newTestDB(db, driver) + + // Provoque an error by sending an invalid struct: + var users []struct { + ID int `ksql:"id"` + // Private names cannot have ksql tags: + badPrivateField string `ksql:"name"` + } + err := c.Query(ctx, &users, `SELECT * FROM users`) + tt.AssertErrContains(t, err, "badPrivateField") + }) + t.Run("should report error if using nested struct and the query starts with SELECT", func(t *testing.T) { db, closer := newDBAdapter(t) defer closer.Close() @@ -471,6 +488,81 @@ func QueryTest( err := c.Query(ctx, &rows, `FROM users u JOIN posts p ON u.id = p.user_id`) tt.AssertErrContains(t, err, "same ksql tag name", "invalid_repeated_name") }) + + t.Run("should report error if DBAdapter.Scan() returns an error", func(t *testing.T) { + db := mockDBAdapter{ + QueryContextFn: func(ctx context.Context, query string, args ...interface{}) (Rows, error) { + return mockRows{ + ColumnsFn: func() ([]string, error) { + return []string{"id", "name", "age", "address"}, nil + }, + NextFn: func() bool { return true }, + ScanFn: func(values ...interface{}) error { + return errors.New("fakeScanErr") + }, + }, nil + }, + } + + ctx := context.Background() + c := newTestDB(db, driver) + + var users []user + err := c.Query(ctx, &users, `SELECT * FROM users`) + tt.AssertErrContains(t, err, "fakeScanErr") + }) + + t.Run("should report error if DBAdapter.Err() returns an error", func(t *testing.T) { + db := mockDBAdapter{ + QueryContextFn: func(ctx context.Context, query string, args ...interface{}) (Rows, error) { + return mockRows{ + ColumnsFn: func() ([]string, error) { + return []string{"id", "name", "age", "address"}, nil + }, + NextFn: func() bool { return false }, + ScanFn: func(values ...interface{}) error { + return nil + }, + ErrFn: func() error { + return errors.New("fakeErrMsg") + }, + }, nil + }, + } + + ctx := context.Background() + c := newTestDB(db, driver) + + var users []user + err := c.Query(ctx, &users, `SELECT * FROM users`) + tt.AssertErrContains(t, err, "fakeErrMsg") + }) + + t.Run("should report error if DBAdapter.Close() returns an error", func(t *testing.T) { + db := mockDBAdapter{ + QueryContextFn: func(ctx context.Context, query string, args ...interface{}) (Rows, error) { + return mockRows{ + ColumnsFn: func() ([]string, error) { + return []string{"id", "name", "age", "address"}, nil + }, + NextFn: func() bool { return false }, + ScanFn: func(values ...interface{}) error { + return nil + }, + CloseFn: func() error { + return errors.New("fakeCloseErr") + }, + }, nil + }, + } + + ctx := context.Background() + c := newTestDB(db, driver) + + var users []user + err := c.Query(ctx, &users, `SELECT * FROM users`) + tt.AssertErrContains(t, err, "fakeCloseErr") + }) }) }) }