Skip to content

Commit 00fdc3d

Browse files
committed
test(sqlite): add integration tests
1 parent 939b33c commit 00fdc3d

File tree

4 files changed

+263
-1
lines changed

4 files changed

+263
-1
lines changed

dbee/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/ClickHouse/clickhouse-go/v2 v2.20.0
1010
github.com/DATA-DOG/go-sqlmock v1.5.2
1111
github.com/databricks/databricks-sql-go v1.5.3
12+
github.com/docker/docker v27.1.1+incompatible
1213
github.com/go-sql-driver/mysql v1.7.1
1314
github.com/google/uuid v1.6.0
1415
github.com/jedib0t/go-pretty/v6 v6.5.8
@@ -59,7 +60,6 @@ require (
5960
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
6061
github.com/distribution/reference v0.6.0 // indirect
6162
github.com/dnephin/pflag v1.0.7 // indirect
62-
github.com/docker/docker v27.1.1+incompatible // indirect
6363
github.com/docker/go-connections v0.5.0 // indirect
6464
github.com/docker/go-units v0.5.0 // indirect
6565
github.com/dustin/go-humanize v1.0.1 // indirect
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package integration
2+
3+
import (
4+
"context"
5+
"log"
6+
"testing"
7+
8+
"github.com/kndndrj/nvim-dbee/dbee/core"
9+
th "github.com/kndndrj/nvim-dbee/dbee/tests/testhelpers"
10+
"github.com/stretchr/testify/assert"
11+
tsuite "github.com/stretchr/testify/suite"
12+
tc "github.com/testcontainers/testcontainers-go"
13+
)
14+
15+
// SQLiteTestSuite is the test suite for the sqlite adapter.
16+
type SQLiteTestSuite struct {
17+
tsuite.Suite
18+
ctr *th.SQLiteContainer
19+
ctx context.Context
20+
d *core.Connection
21+
}
22+
23+
func TestSQLiteTestSuite(t *testing.T) {
24+
tsuite.Run(t, new(SQLiteTestSuite))
25+
}
26+
27+
func (suite *SQLiteTestSuite) SetupSuite() {
28+
suite.ctx = context.Background()
29+
tempDir := suite.T().TempDir()
30+
31+
params := &core.ConnectionParams{ID: "test-sqlite", Name: "test-sqlite"}
32+
ctr, err := th.NewSQLiteContainer(suite.ctx, params, tempDir)
33+
if err != nil {
34+
log.Fatal(err)
35+
}
36+
37+
suite.ctr, suite.d = ctr, ctr.Driver
38+
}
39+
40+
func (suite *SQLiteTestSuite) TeardownSuite() {
41+
tc.CleanupContainer(suite.T(), suite.ctr)
42+
}
43+
44+
func (suite *SQLiteTestSuite) TestShouldErrorInvalidQuery() {
45+
t := suite.T()
46+
47+
want := "syntax error"
48+
49+
call := suite.d.Execute("invalid sql", func(cs core.CallState, c *core.Call) {
50+
if cs == core.CallStateExecutingFailed {
51+
assert.ErrorContains(t, c.Err(), want)
52+
}
53+
})
54+
assert.NotNil(t, call)
55+
}
56+
57+
func (suite *SQLiteTestSuite) TestShouldCancelQuery() {
58+
t := suite.T()
59+
want := []core.CallState{core.CallStateExecuting, core.CallStateCanceled}
60+
61+
_, got, err := th.GetResultWithCancel(t, suite.d, "SELECT 1")
62+
assert.NoError(t, err)
63+
64+
assert.Equal(t, want, got)
65+
}
66+
67+
func (suite *SQLiteTestSuite) TestShouldReturnManyRows() {
68+
t := suite.T()
69+
70+
wantStates := []core.CallState{
71+
core.CallStateExecuting, core.CallStateRetrieving, core.CallStateArchived,
72+
}
73+
wantCols := []string{"id", "username"}
74+
wantRows := []core.Row{
75+
{int64(1), "john_doe"},
76+
{int64(2), "jane_smith"},
77+
{int64(3), "bob_wilson"},
78+
}
79+
80+
query := "SELECT id, username FROM test_table"
81+
82+
gotRows, gotCols, gotStates, err := th.GetResult(t, suite.d, query)
83+
assert.NoError(t, err)
84+
85+
assert.ElementsMatch(t, wantCols, gotCols)
86+
assert.ElementsMatch(t, wantStates, gotStates)
87+
assert.Equal(t, wantRows, gotRows)
88+
}
89+
90+
func (suite *SQLiteTestSuite) TestShouldReturnOneRow() {
91+
t := suite.T()
92+
93+
wantStates := []core.CallState{
94+
core.CallStateExecuting, core.CallStateRetrieving, core.CallStateArchived,
95+
}
96+
wantCols := []string{"id", "username"}
97+
wantRows := []core.Row{{int64(2), "jane_smith"}}
98+
99+
query := "SELECT id, username FROM test_view"
100+
101+
gotRows, gotCols, gotStates, err := th.GetResult(t, suite.d, query)
102+
assert.NoError(t, err)
103+
104+
assert.ElementsMatch(t, wantCols, gotCols)
105+
assert.ElementsMatch(t, wantStates, gotStates)
106+
assert.Equal(t, wantRows, gotRows)
107+
}
108+
109+
func (suite *SQLiteTestSuite) TestShouldReturnStructure() {
110+
t := suite.T()
111+
112+
var (
113+
wantSchema = "sqlite_schema"
114+
wantSomeTable = "test_table"
115+
wantSomeView = "test_view"
116+
)
117+
118+
structure, err := suite.d.GetStructure()
119+
assert.NoError(t, err)
120+
121+
gotSchemas := th.GetSchemas(t, structure)
122+
assert.Contains(t, gotSchemas, wantSchema)
123+
124+
gotTables := th.GetModels(t, structure, core.StructureTypeTable)
125+
assert.Contains(t, gotTables, wantSomeTable)
126+
127+
gotViews := th.GetModels(t, structure, core.StructureTypeView)
128+
assert.Contains(t, gotViews, wantSomeView)
129+
}
130+
131+
func (suite *SQLiteTestSuite) TestShouldReturnColumns() {
132+
t := suite.T()
133+
134+
want := []*core.Column{
135+
{Name: "id", Type: "INTEGER"},
136+
{Name: "username", Type: "TEXT"},
137+
{Name: "email", Type: "TEXT"},
138+
}
139+
140+
got, err := suite.d.GetColumns(&core.TableOptions{
141+
Table: "test_table",
142+
Schema: "sqlite_schema",
143+
Materialization: core.StructureTypeTable,
144+
})
145+
146+
assert.NoError(t, err)
147+
assert.Equal(t, want, got)
148+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
CREATE TABLE IF NOT EXISTS test_table (
2+
id INTEGER PRIMARY KEY,
3+
username TEXT,
4+
email TEXT
5+
);
6+
7+
INSERT INTO test_table (id, username, email) VALUES
8+
(1, 'john_doe', 'john@example.com'),
9+
(2, 'jane_smith', 'jane@example.com'),
10+
(3, 'bob_wilson', 'bob@example.com');
11+
12+
CREATE VIEW IF NOT EXISTS test_view AS
13+
SELECT id, username, email
14+
FROM test_table
15+
WHERE id = 2;
16+

dbee/tests/testhelpers/sqlite.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package testhelpers
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"path/filepath"
7+
"time"
8+
9+
"github.com/docker/docker/api/types/container"
10+
"github.com/kndndrj/nvim-dbee/dbee/adapters"
11+
"github.com/kndndrj/nvim-dbee/dbee/core"
12+
tc "github.com/testcontainers/testcontainers-go"
13+
"github.com/testcontainers/testcontainers-go/wait"
14+
)
15+
16+
type SQLiteContainer struct {
17+
tc.Container
18+
ConnURL string
19+
Driver *core.Connection
20+
TempDir string
21+
}
22+
23+
// NewSQLiteContainer creates a new sqlite container with
24+
// default adapter and connection. The params.URL is overwritten.
25+
// It uses a temporary directory (usually the test suite tempDir) to store the db file.
26+
// The tmpDir is then mounted to the container and all the dependencies are installed
27+
// in the container file, while still being able to connect to the db file in the host.
28+
func NewSQLiteContainer(ctx context.Context, params *core.ConnectionParams, tmpDir string) (*SQLiteContainer, error) {
29+
seedFile, err := GetTestDataFile("sqlite_seed.sql")
30+
if err != nil {
31+
return nil, err
32+
}
33+
34+
dbName, containerDBPath := "test.db", "/container/db"
35+
req := tc.ContainerRequest{
36+
Image: "alpine:3.21",
37+
Files: []tc.ContainerFile{
38+
{
39+
Reader: seedFile,
40+
ContainerFilePath: seedFile.Name(),
41+
FileMode: 0o755,
42+
},
43+
},
44+
HostConfigModifier: func(hc *container.HostConfig) {
45+
hc.Binds = append(hc.Binds, fmt.Sprintf("%s:%s", tmpDir, containerDBPath))
46+
},
47+
// the 'tail -f /dev/null' is a hack to keep the container running
48+
Cmd: []string{"sh", "-c", fmt.Sprintf(`
49+
apk add sqlite=3.48.0-r0 &&
50+
sqlite3 %s/%s < %s &&
51+
echo 'ready' &&
52+
tail -f /dev/null
53+
`, containerDBPath, dbName, seedFile.Name())},
54+
WaitingFor: wait.ForLog("ready").WithStartupTimeout(5 * time.Second),
55+
}
56+
57+
ctr, err := tc.GenericContainer(ctx, tc.GenericContainerRequest{
58+
ContainerRequest: req,
59+
ProviderType: GetContainerProvider(),
60+
Started: true,
61+
})
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
if params.Type == "" {
67+
params.Type = "sqlite"
68+
}
69+
70+
connURL := filepath.Join(tmpDir, dbName)
71+
if params.URL == "" {
72+
params.URL = connURL
73+
}
74+
75+
driver, err := adapters.NewConnection(params)
76+
if err != nil {
77+
return nil, err
78+
}
79+
80+
return &SQLiteContainer{
81+
Container: ctr,
82+
ConnURL: connURL,
83+
Driver: driver,
84+
TempDir: tmpDir,
85+
}, nil
86+
}
87+
88+
// NewDriver helper function to create a new driver with the connection URL.
89+
func (p *SQLiteContainer) NewDriver(params *core.ConnectionParams) (*core.Connection, error) {
90+
if params.URL == "" {
91+
params.URL = p.ConnURL
92+
}
93+
if params.Type == "" {
94+
params.Type = "sqlite"
95+
}
96+
97+
return adapters.NewConnection(params)
98+
}

0 commit comments

Comments
 (0)