Skip to content

Commit b4251d3

Browse files
committed
wip: unit tests for statements
1 parent 46a724a commit b4251d3

File tree

2 files changed

+385
-0
lines changed

2 files changed

+385
-0
lines changed

database/query_builder_test.go

Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
package database
2+
3+
import (
4+
"github.com/icinga/icinga-go-library/testutils"
5+
"testing"
6+
)
7+
8+
type MockEntity struct {
9+
Entity
10+
Id int
11+
Name string
12+
Age int
13+
Email string
14+
}
15+
16+
func TestInsertStatement(t *testing.T) {
17+
tests := []testutils.TestCase[string, testutils.InsertStatementTestData]{
18+
{
19+
Name: "NoColumnsSet",
20+
Expected: `INSERT INTO "mock_entity" ("age", "email", "id", "name") VALUES (:age, :email, :id, :name)`,
21+
},
22+
{
23+
Name: "ColumnsSet",
24+
Expected: `INSERT INTO "mock_entity" ("email", "id", "name") VALUES (:email, :id, :name)`,
25+
Data: testutils.InsertStatementTestData{
26+
Columns: []string{"id", "name", "email"},
27+
},
28+
},
29+
{
30+
Name: "ExcludedColumnsSet",
31+
Expected: `INSERT INTO "mock_entity" ("age", "id", "name") VALUES (:age, :id, :name)`,
32+
Data: testutils.InsertStatementTestData{
33+
ExcludedColumns: []string{"email"},
34+
},
35+
},
36+
{
37+
Name: "ColumnsAndExcludedColumnsSet",
38+
Expected: `INSERT INTO "mock_entity" ("id", "name") VALUES (:id, :name)`,
39+
Data: testutils.InsertStatementTestData{
40+
Columns: []string{"id", "name", "email"},
41+
ExcludedColumns: []string{"email"},
42+
},
43+
},
44+
{
45+
Name: "OverrideTableName",
46+
Expected: `INSERT INTO "custom_table_name" ("email", "id", "name") VALUES (:email, :id, :name)`,
47+
Data: testutils.InsertStatementTestData{
48+
Table: "custom_table_name",
49+
Columns: []string{"id", "name", "email"},
50+
},
51+
},
52+
//{
53+
// Name: "InvalidColumnName",
54+
// Data: testutils.InsertStatementTestData{
55+
// Columns: []string{"id", "name", "email", "invalid_column"},
56+
// ExcludedColumns: nil,
57+
// },
58+
// Error: testutils.ErrorIs(ErrInvalidColumnName),
59+
//},
60+
//{
61+
}
62+
63+
for _, tst := range tests {
64+
t.Run(tst.Name, tst.F(func(data testutils.InsertStatementTestData) (string, error) {
65+
var actual string
66+
var err error
67+
68+
stmt := NewInsertStatement(&MockEntity{}).
69+
SetColumns(data.Columns...).
70+
SetExcludedColumns(data.ExcludedColumns...)
71+
72+
if data.Table != "" {
73+
stmt.Into(data.Table)
74+
}
75+
76+
qb := NewTestQueryBuilder(MySQL)
77+
actual = qb.InsertStatement(stmt)
78+
79+
return actual, err
80+
81+
}))
82+
}
83+
}
84+
85+
func TestInsertIgnoreStatement(t *testing.T) {
86+
tests := []testutils.TestCase[string, testutils.InsertIgnoreStatementTestData]{
87+
{
88+
Name: "NoColumnsSet_MySQL",
89+
Expected: `INSERT IGNORE INTO "mock_entity" ("age", "email", "id", "name") VALUES (:age, :email, :id, :name)`,
90+
Data: testutils.InsertIgnoreStatementTestData{
91+
Driver: MySQL,
92+
},
93+
},
94+
{
95+
Name: "ColumnsSet_MySQL",
96+
Expected: `INSERT IGNORE INTO "mock_entity" ("email", "id", "name") VALUES (:email, :id, :name)`,
97+
Data: testutils.InsertIgnoreStatementTestData{
98+
Driver: MySQL,
99+
Columns: []string{"id", "name", "email"},
100+
},
101+
},
102+
{
103+
Name: "ExcludedColumnsSet_MySQL",
104+
Expected: `INSERT IGNORE INTO "mock_entity" ("age", "id", "name") VALUES (:age, :id, :name)`,
105+
Data: testutils.InsertIgnoreStatementTestData{
106+
Driver: MySQL,
107+
ExcludedColumns: []string{"email"},
108+
},
109+
},
110+
{
111+
Name: "ColumnsAndExcludedColumnsSet_MySQL",
112+
Expected: `INSERT IGNORE INTO "mock_entity" ("id", "name") VALUES (:id, :name)`,
113+
Data: testutils.InsertIgnoreStatementTestData{
114+
Driver: MySQL,
115+
Columns: []string{"id", "name", "email"},
116+
ExcludedColumns: []string{"email"},
117+
},
118+
},
119+
{
120+
Name: "OverrideTableName_MySQL",
121+
Expected: `INSERT IGNORE INTO "custom_table_name" ("email", "id", "name") VALUES (:email, :id, :name)`,
122+
Data: testutils.InsertIgnoreStatementTestData{
123+
Driver: MySQL,
124+
Table: "custom_table_name",
125+
Columns: []string{"id", "name", "email"},
126+
},
127+
},
128+
{
129+
Name: "NoColumnsSet_PostgreSQL",
130+
Expected: `INSERT INTO "mock_entity" ("age", "email", "id", "name") VALUES (:age, :email, :id, :name) ON CONFLICT DO NOTHING`,
131+
Data: testutils.InsertIgnoreStatementTestData{
132+
Driver: PostgreSQL,
133+
},
134+
},
135+
{
136+
Name: "ColumnsSet_PostgreSQL",
137+
Expected: `INSERT INTO "mock_entity" ("email", "id", "name") VALUES (:email, :id, :name) ON CONFLICT DO NOTHING`,
138+
Data: testutils.InsertIgnoreStatementTestData{
139+
Driver: PostgreSQL,
140+
Columns: []string{"id", "name", "email"},
141+
},
142+
},
143+
{
144+
Name: "ExcludedColumnsSet_PostgreSQL",
145+
Expected: `INSERT INTO "mock_entity" ("age", "id", "name") VALUES (:age, :id, :name) ON CONFLICT DO NOTHING`,
146+
Data: testutils.InsertIgnoreStatementTestData{
147+
Driver: PostgreSQL,
148+
ExcludedColumns: []string{"email"},
149+
},
150+
},
151+
{
152+
Name: "ColumnsAndExcludedColumnsSet_PostgreSQL",
153+
Expected: `INSERT INTO "mock_entity" ("id", "name") VALUES (:id, :name) ON CONFLICT DO NOTHING`,
154+
Data: testutils.InsertIgnoreStatementTestData{
155+
Driver: PostgreSQL,
156+
Columns: []string{"id", "name", "email"},
157+
ExcludedColumns: []string{"email"},
158+
},
159+
},
160+
{
161+
Name: "OverrideTableName_PostgreSQL",
162+
Expected: `INSERT INTO "custom_table_name" ("email", "id", "name") VALUES (:email, :id, :name) ON CONFLICT DO NOTHING`,
163+
Data: testutils.InsertIgnoreStatementTestData{
164+
Driver: PostgreSQL,
165+
Table: "custom_table_name",
166+
Columns: []string{"id", "name", "email"},
167+
ExcludedColumns: nil,
168+
},
169+
},
170+
{
171+
Name: "UnsupportedDriver",
172+
Error: testutils.ErrorIs(ErrUnsupportedDriver),
173+
Data: testutils.InsertIgnoreStatementTestData{
174+
Driver: "abcxyz", // Unsupported driver
175+
Columns: []string{"id", "name", "email"},
176+
ExcludedColumns: nil,
177+
},
178+
},
179+
}
180+
181+
for _, tst := range tests {
182+
t.Run(tst.Name, tst.F(func(data testutils.InsertIgnoreStatementTestData) (string, error) {
183+
var actual string
184+
var err error
185+
186+
stmt := NewInsertStatement(&MockEntity{}).
187+
SetColumns(data.Columns...).
188+
SetExcludedColumns(data.ExcludedColumns...)
189+
190+
if data.Table != "" {
191+
stmt.Into(data.Table)
192+
}
193+
194+
qb := NewTestQueryBuilder(data.Driver)
195+
actual, err = qb.InsertIgnoreStatement(stmt)
196+
197+
return actual, err
198+
199+
}))
200+
}
201+
}
202+
203+
func TestInsertSelectStatement(t *testing.T) {
204+
tests := []testutils.TestCase[string, testutils.InsertSelectStatementTestData]{
205+
{
206+
Name: "ColumnsSet",
207+
Expected: `INSERT INTO "mock_entity" ("email", "id", "name") SELECT "email", "id", "name" FROM "mock_entity" WHERE id = :id`,
208+
Data: testutils.InsertSelectStatementTestData{
209+
Columns: []string{"id", "name", "email"},
210+
Select: NewSelectStatement(&MockEntity{}).SetColumns("id", "name", "email").SetWhere("id = :id"),
211+
},
212+
},
213+
{
214+
Name: "ExcludedColumnsSet",
215+
Expected: `INSERT INTO "mock_entity" ("age", "id", "name") SELECT "age", "id", "name" FROM "mock_entity" WHERE id = :id`,
216+
Data: testutils.InsertSelectStatementTestData{
217+
ExcludedColumns: []string{"email"},
218+
Select: NewSelectStatement(&MockEntity{}).SetExcludedColumns("email").SetWhere("id = :id"),
219+
},
220+
},
221+
{
222+
Name: "ColumnsAndExcludedColumnsSet",
223+
Expected: `INSERT INTO "mock_entity" ("id", "name") SELECT "id", "name" FROM "mock_entity" WHERE id = :id`,
224+
Data: testutils.InsertSelectStatementTestData{
225+
Columns: []string{"id", "name", "email"},
226+
ExcludedColumns: []string{"email"},
227+
Select: NewSelectStatement(&MockEntity{}).SetColumns("id", "name", "email").SetExcludedColumns("email").SetWhere("id = :id"),
228+
},
229+
},
230+
{
231+
Name: "OverrideTableName",
232+
Expected: `INSERT INTO "custom_table_name" ("email", "id", "name") SELECT "email", "id", "name" FROM "mock_entity" WHERE id = :id`,
233+
Data: testutils.InsertSelectStatementTestData{
234+
Table: "custom_table_name",
235+
Columns: []string{"id", "name", "email"},
236+
Select: NewSelectStatement(&MockEntity{}).SetColumns("id", "name", "email").SetWhere("id = :id"),
237+
},
238+
},
239+
{
240+
Name: "SelectStatementMissing",
241+
Error: testutils.ErrorIs(ErrMissingStatementPart),
242+
Data: testutils.InsertSelectStatementTestData{},
243+
},
244+
//{
245+
// Name: "InvalidColumnName",
246+
// Data: testutils.InsertStatementTestData{
247+
// Columns: []string{"id", "name", "email", "invalid_column"},
248+
// ExcludedColumns: nil,
249+
// },
250+
// Error: testutils.ErrorIs(ErrInvalidColumnName),
251+
//},
252+
}
253+
254+
for _, tst := range tests {
255+
t.Run(tst.Name, tst.F(func(data testutils.InsertSelectStatementTestData) (string, error) {
256+
var actual string
257+
var err error
258+
259+
stmt := NewInsertSelectStatement(&MockEntity{}).
260+
SetColumns(data.Columns...).
261+
SetExcludedColumns(data.ExcludedColumns...)
262+
263+
if data.Select != nil {
264+
stmt.SetSelect(data.Select.(SelectStatement))
265+
}
266+
267+
if data.Table != "" {
268+
stmt.Into(data.Table)
269+
}
270+
271+
qb := NewTestQueryBuilder(MySQL)
272+
actual, err = qb.InsertSelectStatement(stmt)
273+
274+
return actual, err
275+
276+
}))
277+
}
278+
}
279+
280+
func TestUpdateStatement(t *testing.T) {
281+
tests := []testutils.TestCase[string, testutils.UpdateStatementTestData]{
282+
{
283+
Name: "NoWhereSet",
284+
Error: testutils.ErrorIs(ErrMissingStatementPart),
285+
},
286+
{
287+
Name: "ColumnsSet",
288+
Expected: `UPDATE "mock_entity" SET "email" = :email, "name" = :name WHERE id = :id`,
289+
Data: testutils.UpdateStatementTestData{
290+
Columns: []string{"name", "email"},
291+
Where: "id = :id",
292+
},
293+
},
294+
{
295+
Name: "ExcludedColumnsSet",
296+
Expected: `UPDATE "mock_entity" SET "email" = :email, "name" = :name WHERE id = :id`,
297+
Data: testutils.UpdateStatementTestData{
298+
ExcludedColumns: []string{"id", "age"},
299+
Where: "id = :id",
300+
},
301+
},
302+
{
303+
Name: "OverrideTableName",
304+
Expected: `UPDATE "custom_table_name" SET "email" = :email, "id" = :id, "name" = :name WHERE id = :id`,
305+
Data: testutils.UpdateStatementTestData{
306+
Table: "custom_table_name",
307+
Columns: []string{"id", "name", "email"},
308+
Where: "id = :id",
309+
},
310+
},
311+
}
312+
313+
for _, tst := range tests {
314+
t.Run(tst.Name, tst.F(func(data testutils.UpdateStatementTestData) (string, error) {
315+
var actual string
316+
var err error
317+
318+
stmt := NewUpdateStatement(&MockEntity{}).
319+
SetColumns(data.Columns...).
320+
SetExcludedColumns(data.ExcludedColumns...)
321+
322+
if data.Where != "" {
323+
stmt.SetWhere(data.Where)
324+
}
325+
326+
if data.Table != "" {
327+
stmt.SetTable(data.Table)
328+
}
329+
330+
qb := NewTestQueryBuilder(MySQL)
331+
actual, err = qb.UpdateStatement(stmt)
332+
333+
return actual, err
334+
335+
}))
336+
}
337+
}

testutils/testutils.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,54 @@ type ConfigTestData struct {
5151
Env map[string]string
5252
}
5353

54+
type InsertStatementTestData struct {
55+
Table string
56+
Columns []string
57+
ExcludedColumns []string
58+
}
59+
60+
type InsertIgnoreStatementTestData struct {
61+
Driver string
62+
Table string
63+
Columns []string
64+
ExcludedColumns []string
65+
}
66+
67+
type InsertSelectStatementTestData struct {
68+
Table string
69+
Columns []string
70+
ExcludedColumns []string
71+
72+
// Should be SelectStatement but cannot because of import cycle
73+
Select any
74+
}
75+
76+
type UpdateStatementTestData struct {
77+
Table string
78+
Columns []string
79+
ExcludedColumns []string
80+
Where string
81+
}
82+
83+
type UpsertStatementTestData struct {
84+
Driver string
85+
Table string
86+
Columns []string
87+
ExcludedColumns []string
88+
}
89+
90+
type DeleteStatementTestData struct {
91+
Table string
92+
Where string
93+
}
94+
95+
type SelectStatementTestData struct {
96+
Table string
97+
Columns []string
98+
ExcludedColumns []string
99+
Where string
100+
}
101+
54102
// ErrorAs returns a function that checks if the error is of a specific type T.
55103
// This is useful for verifying that an error matches a particular interface or concrete type.
56104
func ErrorAs[T error]() func(t *testing.T, err error) {

0 commit comments

Comments
 (0)