Skip to content

Commit 615d872

Browse files
authored
Merge pull request #403 from machbase/wip-test-covarage
Add comprehensive tests for bridge and TQL modules
1 parent f3c0cd3 commit 615d872

7 files changed

Lines changed: 1172 additions & 0 deletions

File tree

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package connector
2+
3+
import (
4+
"context"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/machbase/neo-server/v8/mods/bridge/connector/sqlite"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func closeBridgedDatabase(t *testing.T, db any) {
13+
t.Helper()
14+
bridged, ok := db.(*BridgedDatabase)
15+
if !ok || bridged == nil || bridged.db == nil {
16+
return
17+
}
18+
require.NoError(t, bridged.db.Close())
19+
}
20+
21+
func resetDatabasesForTest(t *testing.T) {
22+
t.Helper()
23+
databasesLock.Lock()
24+
prev := databases
25+
databases = map[string]*BridgedDatabase{}
26+
databasesLock.Unlock()
27+
t.Cleanup(func() {
28+
for _, db := range databases {
29+
if db != nil && db.db != nil {
30+
require.NoError(t, db.db.Close())
31+
}
32+
}
33+
databasesLock.Lock()
34+
databases = prev
35+
databasesLock.Unlock()
36+
})
37+
}
38+
39+
func sqliteDataSource(t *testing.T) string {
40+
t.Helper()
41+
return "file:" + filepath.Join(t.TempDir(), "connector.db") + "?cache=shared"
42+
}
43+
44+
func TestNewCachesAndConnectsSqlite(t *testing.T) {
45+
resetDatabasesForTest(t)
46+
47+
ctx := context.Background()
48+
name := "sqlite," + sqliteDataSource(t)
49+
50+
first, err := New(name)
51+
require.NoError(t, err)
52+
require.NotNil(t, first)
53+
t.Cleanup(func() { closeBridgedDatabase(t, first) })
54+
55+
second, err := New(name)
56+
require.NoError(t, err)
57+
require.Same(t, first, second)
58+
59+
bridged := first.(*BridgedDatabase)
60+
conn, err := bridged.Connect(ctx)
61+
require.NoError(t, err)
62+
require.NoError(t, conn.Close())
63+
64+
_, err = bridged.Ping(ctx)
65+
require.NoError(t, err)
66+
67+
ok, reason, err := bridged.UserAuth(ctx, "user", "password")
68+
require.NoError(t, err)
69+
require.True(t, ok)
70+
require.Empty(t, reason)
71+
72+
_, err = New("unknown,dsn")
73+
require.EqualError(t, err, "unknown database type: unknown,dsn")
74+
}
75+
76+
func TestNewWithDataSourceAndSetDatabase(t *testing.T) {
77+
resetDatabasesForTest(t)
78+
79+
dataSource := sqliteDataSource(t)
80+
db, opts, err := NewWithDataSource("sqlite", dataSource)
81+
require.NoError(t, err)
82+
require.NotNil(t, db)
83+
require.Empty(t, opts)
84+
t.Cleanup(func() { closeBridgedDatabase(t, db) })
85+
86+
_, _, err = NewWithDataSource("postgresql", "postgres://user:pass@127.0.0.1/db")
87+
require.NoError(t, err)
88+
89+
_, _, err = NewWithDataSource("mysql", "user:pass@tcp(127.0.0.1:3306)/db")
90+
require.NoError(t, err)
91+
92+
_, _, err = NewWithDataSource("mssql", "sqlserver://user:pass@127.0.0.1:1433?database=db")
93+
require.NoError(t, err)
94+
95+
_, _, err = NewWithDataSource("unknown", "dsn")
96+
require.EqualError(t, err, "unknown database type: unknown")
97+
98+
sqlDB, err := sqlite.Connect(dataSource)
99+
require.NoError(t, err)
100+
t.Cleanup(func() { sqlDB.Close() })
101+
102+
SetDatabase("preloaded", sqlDB, "sqlite", dataSource)
103+
preloaded, err := New("preloaded")
104+
require.NoError(t, err)
105+
require.NotNil(t, preloaded)
106+
107+
require.PanicsWithValue(t, "db is nil", func() {
108+
SetDatabase("panic", nil, "sqlite", dataSource)
109+
})
110+
}
111+
112+
func TestBridgedDatabasePingFailure(t *testing.T) {
113+
db, err := sqlite.Connect(sqliteDataSource(t))
114+
require.NoError(t, err)
115+
require.NoError(t, db.Close())
116+
117+
bridged := &BridgedDatabase{db: db}
118+
_, err = bridged.Ping(context.Background())
119+
require.Error(t, err)
120+
}

mods/bridge/internal/base_test.go

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package internal
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"path/filepath"
7+
"testing"
8+
"time"
9+
10+
"github.com/machbase/neo-client/api"
11+
_ "github.com/mattn/go-sqlite3"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func openTestConn(t *testing.T) *sql.Conn {
16+
t.Helper()
17+
db, err := sql.Open("sqlite3", "file:"+filepath.Join(t.TempDir(), "internal.db")+"?cache=shared")
18+
require.NoError(t, err)
19+
t.Cleanup(func() { db.Close() })
20+
21+
conn, err := db.Conn(context.Background())
22+
require.NoError(t, err)
23+
t.Cleanup(func() { conn.Close() })
24+
return conn
25+
}
26+
27+
func TestConnAndRows(t *testing.T) {
28+
ctx := context.Background()
29+
conn := NewConn(openTestConn(t))
30+
31+
require.Panics(t, func() {
32+
conn.Prepare(ctx, "select 1")
33+
})
34+
35+
result := conn.Exec(ctx, `CREATE TABLE example(id INTEGER PRIMARY KEY, name TEXT, created_at DATETIME)`)
36+
require.NoError(t, result.Err())
37+
require.EqualValues(t, 0, result.RowsAffected())
38+
require.Empty(t, result.Message())
39+
40+
insertedAt := time.Date(2024, 1, 2, 3, 4, 5, 0, time.UTC)
41+
result = conn.Exec(ctx, `INSERT INTO example(id, name, created_at) VALUES(?, ?, ?)`, 1, "alpha", insertedAt)
42+
require.NoError(t, result.Err())
43+
require.EqualValues(t, 1, result.RowsAffected())
44+
45+
rows, err := conn.Query(ctx, `SELECT id, name FROM example ORDER BY id`)
46+
require.NoError(t, err)
47+
defer rows.Close()
48+
require.True(t, rows.IsFetchable())
49+
require.Equal(t, int64(0), rows.RowsAffected())
50+
require.Equal(t, "success", rows.Message())
51+
52+
cols, err := rows.Columns()
53+
require.NoError(t, err)
54+
require.Len(t, cols.Names(), 2)
55+
require.Equal(t, api.DataTypeInt64, cols[0].DataType)
56+
require.Equal(t, api.DataTypeString, cols[1].DataType)
57+
58+
require.True(t, rows.Next())
59+
var id int64
60+
var name string
61+
require.NoError(t, rows.Scan(&id, &name))
62+
require.EqualValues(t, 1, id)
63+
require.Equal(t, "alpha", name)
64+
require.False(t, rows.Next())
65+
require.NoError(t, rows.Err())
66+
67+
row := conn.QueryRow(ctx, `SELECT id, name, created_at FROM example WHERE id = ?`, 1)
68+
require.NoError(t, row.Err())
69+
rowCols, err := row.Columns()
70+
require.NoError(t, err)
71+
require.Len(t, rowCols.Names(), 3)
72+
73+
var rowID int64
74+
var rowName string
75+
var rowCreatedAt time.Time
76+
require.NoError(t, row.Scan(&rowID, &rowName, &rowCreatedAt))
77+
require.EqualValues(t, 1, rowID)
78+
require.Equal(t, "alpha", rowName)
79+
require.True(t, insertedAt.Equal(rowCreatedAt))
80+
require.Equal(t, int64(0), row.RowsAffected())
81+
require.Empty(t, row.Message())
82+
83+
missing := conn.QueryRow(ctx, `SELECT id FROM example WHERE id = ?`, 999)
84+
require.EqualError(t, missing.Err(), sql.ErrNoRows.Error())
85+
86+
row = conn.QueryRow(ctx, `SELECT id FROM example WHERE id = ?`, 1)
87+
require.Error(t, row.Scan(&rowID, &rowName))
88+
89+
_, err = conn.Appender(ctx, "example")
90+
require.EqualError(t, err, api.ErrNotImplemented("Appender").Error())
91+
92+
_, err = conn.Explain(ctx, `SELECT * FROM example`, false)
93+
require.EqualError(t, err, api.ErrNotImplemented("Explain").Error())
94+
95+
require.NoError(t, conn.Close())
96+
}
97+
98+
func TestResultAndScanTypeHelpers(t *testing.T) {
99+
errResult := &Result{err: sql.ErrConnDone}
100+
require.EqualError(t, errResult.Err(), sql.ErrConnDone.Error())
101+
require.Equal(t, sql.ErrConnDone.Error(), errResult.Message())
102+
103+
cases := map[string]api.DataType{
104+
"bool": api.DataTypeBoolean,
105+
"sql.NullBool": api.DataTypeBoolean,
106+
"int8": api.DataTypeInt16,
107+
"sql.NullByte": api.DataTypeInt16,
108+
"int16": api.DataTypeInt16,
109+
"sql.NullInt16": api.DataTypeInt16,
110+
"int32": api.DataTypeInt32,
111+
"sql.NullInt32": api.DataTypeInt32,
112+
"int64": api.DataTypeInt64,
113+
"sql.NullInt64": api.DataTypeInt64,
114+
"float32": api.DataTypeFloat32,
115+
"float64": api.DataTypeFloat64,
116+
"sql.NullFloat64": api.DataTypeFloat64,
117+
"string": api.DataTypeString,
118+
"sql.NullString": api.DataTypeString,
119+
"time.Time": api.DataTypeDatetime,
120+
"sql.NullTime": api.DataTypeDatetime,
121+
"[]byte": api.DataTypeBinary,
122+
"sql.RawBytes": api.DataTypeBinary,
123+
"*interface {}": api.DataTypeString,
124+
"unknown": api.DataTypeAny,
125+
}
126+
for input, want := range cases {
127+
require.Equal(t, want, scanTypeToDataType(input))
128+
}
129+
130+
base := &SqlBridgeBase{}
131+
require.IsType(t, new(bool), base.NewScanType("sql.NullBool", ""))
132+
require.IsType(t, new(uint8), base.NewScanType("sql.NullByte", ""))
133+
require.IsType(t, new(float64), base.NewScanType("sql.NullFloat64", ""))
134+
require.IsType(t, new(int16), base.NewScanType("sql.NullInt16", ""))
135+
require.IsType(t, new(int32), base.NewScanType("sql.NullInt32", ""))
136+
require.IsType(t, new(int64), base.NewScanType("sql.NullInt64", ""))
137+
require.IsType(t, new(string), base.NewScanType("sql.NullString", ""))
138+
require.IsType(t, new(sql.NullTime), base.NewScanType("sql.NullTime", ""))
139+
require.IsType(t, new([]byte), base.NewScanType("sql.RawBytes", ""))
140+
require.IsType(t, new([]byte), base.NewScanType("[]uint8", ""))
141+
require.IsType(t, new(bool), base.NewScanType("bool", ""))
142+
require.IsType(t, new(int32), base.NewScanType("int32", ""))
143+
require.IsType(t, new(int64), base.NewScanType("int64", ""))
144+
require.IsType(t, new(string), base.NewScanType("string", ""))
145+
require.IsType(t, new(time.Time), base.NewScanType("time.Time", ""))
146+
require.Nil(t, base.NewScanType("unknown", ""))
147+
148+
nullBool := &sql.NullBool{Bool: true, Valid: true}
149+
nullByte := &sql.NullByte{Byte: 2, Valid: true}
150+
nullFloat := &sql.NullFloat64{Float64: 1.25, Valid: true}
151+
nullInt16 := &sql.NullInt16{Int16: 16, Valid: true}
152+
nullInt32 := &sql.NullInt32{Int32: 32, Valid: true}
153+
nullInt64 := &sql.NullInt64{Int64: 64, Valid: true}
154+
nullString := &sql.NullString{String: "text", Valid: true}
155+
nullTime := &sql.NullTime{Time: time.Unix(0, 10), Valid: true}
156+
raw := sql.RawBytes("bytes")
157+
158+
normalized := base.NormalizeType([]any{
159+
raw,
160+
nullBool,
161+
nullByte,
162+
nullFloat,
163+
nullInt16,
164+
nullInt32,
165+
nullInt64,
166+
nullString,
167+
nullTime,
168+
&sql.NullString{},
169+
})
170+
require.Equal(t, []byte("bytes"), normalized[0])
171+
require.Equal(t, true, normalized[1])
172+
require.EqualValues(t, 2, normalized[2])
173+
require.Equal(t, 1.25, normalized[3])
174+
require.EqualValues(t, 16, normalized[4])
175+
require.EqualValues(t, 32, normalized[5])
176+
require.EqualValues(t, 64, normalized[6])
177+
require.Equal(t, "text", normalized[7])
178+
require.Equal(t, nullTime.Time, normalized[8])
179+
require.Nil(t, normalized[9])
180+
181+
wrapped := base.Conn(openTestConn(t))
182+
require.IsType(t, &Conn{}, wrapped)
183+
}

0 commit comments

Comments
 (0)