Skip to content

Commit

Permalink
wip: unit tests for statements
Browse files Browse the repository at this point in the history
  • Loading branch information
jrauh01 committed Dec 4, 2024
1 parent 46a724a commit b4251d3
Show file tree
Hide file tree
Showing 2 changed files with 385 additions and 0 deletions.
337 changes: 337 additions & 0 deletions database/query_builder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,337 @@
package database

import (
"github.com/icinga/icinga-go-library/testutils"
"testing"
)

type MockEntity struct {
Entity
Id int
Name string
Age int
Email string
}

func TestInsertStatement(t *testing.T) {
tests := []testutils.TestCase[string, testutils.InsertStatementTestData]{
{
Name: "NoColumnsSet",
Expected: `INSERT INTO "mock_entity" ("age", "email", "id", "name") VALUES (:age, :email, :id, :name)`,
},
{
Name: "ColumnsSet",
Expected: `INSERT INTO "mock_entity" ("email", "id", "name") VALUES (:email, :id, :name)`,
Data: testutils.InsertStatementTestData{
Columns: []string{"id", "name", "email"},
},
},
{
Name: "ExcludedColumnsSet",
Expected: `INSERT INTO "mock_entity" ("age", "id", "name") VALUES (:age, :id, :name)`,
Data: testutils.InsertStatementTestData{
ExcludedColumns: []string{"email"},
},
},
{
Name: "ColumnsAndExcludedColumnsSet",
Expected: `INSERT INTO "mock_entity" ("id", "name") VALUES (:id, :name)`,
Data: testutils.InsertStatementTestData{
Columns: []string{"id", "name", "email"},
ExcludedColumns: []string{"email"},
},
},
{
Name: "OverrideTableName",
Expected: `INSERT INTO "custom_table_name" ("email", "id", "name") VALUES (:email, :id, :name)`,
Data: testutils.InsertStatementTestData{
Table: "custom_table_name",
Columns: []string{"id", "name", "email"},
},
},
//{
// Name: "InvalidColumnName",
// Data: testutils.InsertStatementTestData{
// Columns: []string{"id", "name", "email", "invalid_column"},
// ExcludedColumns: nil,
// },
// Error: testutils.ErrorIs(ErrInvalidColumnName),
//},
//{
}

for _, tst := range tests {
t.Run(tst.Name, tst.F(func(data testutils.InsertStatementTestData) (string, error) {
var actual string
var err error

stmt := NewInsertStatement(&MockEntity{}).
SetColumns(data.Columns...).
SetExcludedColumns(data.ExcludedColumns...)

if data.Table != "" {
stmt.Into(data.Table)
}

qb := NewTestQueryBuilder(MySQL)
actual = qb.InsertStatement(stmt)

return actual, err

}))
}
}

func TestInsertIgnoreStatement(t *testing.T) {
tests := []testutils.TestCase[string, testutils.InsertIgnoreStatementTestData]{
{
Name: "NoColumnsSet_MySQL",
Expected: `INSERT IGNORE INTO "mock_entity" ("age", "email", "id", "name") VALUES (:age, :email, :id, :name)`,
Data: testutils.InsertIgnoreStatementTestData{
Driver: MySQL,
},
},
{
Name: "ColumnsSet_MySQL",
Expected: `INSERT IGNORE INTO "mock_entity" ("email", "id", "name") VALUES (:email, :id, :name)`,
Data: testutils.InsertIgnoreStatementTestData{
Driver: MySQL,
Columns: []string{"id", "name", "email"},
},
},
{
Name: "ExcludedColumnsSet_MySQL",
Expected: `INSERT IGNORE INTO "mock_entity" ("age", "id", "name") VALUES (:age, :id, :name)`,
Data: testutils.InsertIgnoreStatementTestData{
Driver: MySQL,
ExcludedColumns: []string{"email"},
},
},
{
Name: "ColumnsAndExcludedColumnsSet_MySQL",
Expected: `INSERT IGNORE INTO "mock_entity" ("id", "name") VALUES (:id, :name)`,
Data: testutils.InsertIgnoreStatementTestData{
Driver: MySQL,
Columns: []string{"id", "name", "email"},
ExcludedColumns: []string{"email"},
},
},
{
Name: "OverrideTableName_MySQL",
Expected: `INSERT IGNORE INTO "custom_table_name" ("email", "id", "name") VALUES (:email, :id, :name)`,
Data: testutils.InsertIgnoreStatementTestData{
Driver: MySQL,
Table: "custom_table_name",
Columns: []string{"id", "name", "email"},
},
},
{
Name: "NoColumnsSet_PostgreSQL",
Expected: `INSERT INTO "mock_entity" ("age", "email", "id", "name") VALUES (:age, :email, :id, :name) ON CONFLICT DO NOTHING`,
Data: testutils.InsertIgnoreStatementTestData{
Driver: PostgreSQL,
},
},
{
Name: "ColumnsSet_PostgreSQL",
Expected: `INSERT INTO "mock_entity" ("email", "id", "name") VALUES (:email, :id, :name) ON CONFLICT DO NOTHING`,
Data: testutils.InsertIgnoreStatementTestData{
Driver: PostgreSQL,
Columns: []string{"id", "name", "email"},
},
},
{
Name: "ExcludedColumnsSet_PostgreSQL",
Expected: `INSERT INTO "mock_entity" ("age", "id", "name") VALUES (:age, :id, :name) ON CONFLICT DO NOTHING`,
Data: testutils.InsertIgnoreStatementTestData{
Driver: PostgreSQL,
ExcludedColumns: []string{"email"},
},
},
{
Name: "ColumnsAndExcludedColumnsSet_PostgreSQL",
Expected: `INSERT INTO "mock_entity" ("id", "name") VALUES (:id, :name) ON CONFLICT DO NOTHING`,
Data: testutils.InsertIgnoreStatementTestData{
Driver: PostgreSQL,
Columns: []string{"id", "name", "email"},
ExcludedColumns: []string{"email"},
},
},
{
Name: "OverrideTableName_PostgreSQL",
Expected: `INSERT INTO "custom_table_name" ("email", "id", "name") VALUES (:email, :id, :name) ON CONFLICT DO NOTHING`,
Data: testutils.InsertIgnoreStatementTestData{
Driver: PostgreSQL,
Table: "custom_table_name",
Columns: []string{"id", "name", "email"},
ExcludedColumns: nil,
},
},
{
Name: "UnsupportedDriver",
Error: testutils.ErrorIs(ErrUnsupportedDriver),
Data: testutils.InsertIgnoreStatementTestData{
Driver: "abcxyz", // Unsupported driver
Columns: []string{"id", "name", "email"},
ExcludedColumns: nil,
},
},
}

for _, tst := range tests {
t.Run(tst.Name, tst.F(func(data testutils.InsertIgnoreStatementTestData) (string, error) {
var actual string
var err error

stmt := NewInsertStatement(&MockEntity{}).
SetColumns(data.Columns...).
SetExcludedColumns(data.ExcludedColumns...)

if data.Table != "" {
stmt.Into(data.Table)
}

qb := NewTestQueryBuilder(data.Driver)
actual, err = qb.InsertIgnoreStatement(stmt)

return actual, err

}))
}
}

func TestInsertSelectStatement(t *testing.T) {
tests := []testutils.TestCase[string, testutils.InsertSelectStatementTestData]{
{
Name: "ColumnsSet",
Expected: `INSERT INTO "mock_entity" ("email", "id", "name") SELECT "email", "id", "name" FROM "mock_entity" WHERE id = :id`,
Data: testutils.InsertSelectStatementTestData{
Columns: []string{"id", "name", "email"},
Select: NewSelectStatement(&MockEntity{}).SetColumns("id", "name", "email").SetWhere("id = :id"),
},
},
{
Name: "ExcludedColumnsSet",
Expected: `INSERT INTO "mock_entity" ("age", "id", "name") SELECT "age", "id", "name" FROM "mock_entity" WHERE id = :id`,
Data: testutils.InsertSelectStatementTestData{
ExcludedColumns: []string{"email"},
Select: NewSelectStatement(&MockEntity{}).SetExcludedColumns("email").SetWhere("id = :id"),
},
},
{
Name: "ColumnsAndExcludedColumnsSet",
Expected: `INSERT INTO "mock_entity" ("id", "name") SELECT "id", "name" FROM "mock_entity" WHERE id = :id`,
Data: testutils.InsertSelectStatementTestData{
Columns: []string{"id", "name", "email"},
ExcludedColumns: []string{"email"},
Select: NewSelectStatement(&MockEntity{}).SetColumns("id", "name", "email").SetExcludedColumns("email").SetWhere("id = :id"),
},
},
{
Name: "OverrideTableName",
Expected: `INSERT INTO "custom_table_name" ("email", "id", "name") SELECT "email", "id", "name" FROM "mock_entity" WHERE id = :id`,
Data: testutils.InsertSelectStatementTestData{
Table: "custom_table_name",
Columns: []string{"id", "name", "email"},
Select: NewSelectStatement(&MockEntity{}).SetColumns("id", "name", "email").SetWhere("id = :id"),
},
},
{
Name: "SelectStatementMissing",
Error: testutils.ErrorIs(ErrMissingStatementPart),
Data: testutils.InsertSelectStatementTestData{},
},
//{
// Name: "InvalidColumnName",
// Data: testutils.InsertStatementTestData{
// Columns: []string{"id", "name", "email", "invalid_column"},
// ExcludedColumns: nil,
// },
// Error: testutils.ErrorIs(ErrInvalidColumnName),
//},
}

for _, tst := range tests {
t.Run(tst.Name, tst.F(func(data testutils.InsertSelectStatementTestData) (string, error) {
var actual string
var err error

stmt := NewInsertSelectStatement(&MockEntity{}).
SetColumns(data.Columns...).
SetExcludedColumns(data.ExcludedColumns...)

if data.Select != nil {
stmt.SetSelect(data.Select.(SelectStatement))
}

if data.Table != "" {
stmt.Into(data.Table)
}

qb := NewTestQueryBuilder(MySQL)
actual, err = qb.InsertSelectStatement(stmt)

return actual, err

}))
}
}

func TestUpdateStatement(t *testing.T) {
tests := []testutils.TestCase[string, testutils.UpdateStatementTestData]{
{
Name: "NoWhereSet",
Error: testutils.ErrorIs(ErrMissingStatementPart),
},
{
Name: "ColumnsSet",
Expected: `UPDATE "mock_entity" SET "email" = :email, "name" = :name WHERE id = :id`,
Data: testutils.UpdateStatementTestData{
Columns: []string{"name", "email"},
Where: "id = :id",
},
},
{
Name: "ExcludedColumnsSet",
Expected: `UPDATE "mock_entity" SET "email" = :email, "name" = :name WHERE id = :id`,
Data: testutils.UpdateStatementTestData{
ExcludedColumns: []string{"id", "age"},
Where: "id = :id",
},
},
{
Name: "OverrideTableName",
Expected: `UPDATE "custom_table_name" SET "email" = :email, "id" = :id, "name" = :name WHERE id = :id`,
Data: testutils.UpdateStatementTestData{
Table: "custom_table_name",
Columns: []string{"id", "name", "email"},
Where: "id = :id",
},
},
}

for _, tst := range tests {
t.Run(tst.Name, tst.F(func(data testutils.UpdateStatementTestData) (string, error) {
var actual string
var err error

stmt := NewUpdateStatement(&MockEntity{}).
SetColumns(data.Columns...).
SetExcludedColumns(data.ExcludedColumns...)

if data.Where != "" {
stmt.SetWhere(data.Where)
}

if data.Table != "" {
stmt.SetTable(data.Table)
}

qb := NewTestQueryBuilder(MySQL)
actual, err = qb.UpdateStatement(stmt)

return actual, err

}))
}
}
48 changes: 48 additions & 0 deletions testutils/testutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,54 @@ type ConfigTestData struct {
Env map[string]string
}

type InsertStatementTestData struct {
Table string
Columns []string
ExcludedColumns []string
}

type InsertIgnoreStatementTestData struct {
Driver string
Table string
Columns []string
ExcludedColumns []string
}

type InsertSelectStatementTestData struct {
Table string
Columns []string
ExcludedColumns []string

// Should be SelectStatement but cannot because of import cycle
Select any
}

type UpdateStatementTestData struct {
Table string
Columns []string
ExcludedColumns []string
Where string
}

type UpsertStatementTestData struct {
Driver string
Table string
Columns []string
ExcludedColumns []string
}

type DeleteStatementTestData struct {
Table string
Where string
}

type SelectStatementTestData struct {
Table string
Columns []string
ExcludedColumns []string
Where string
}

// ErrorAs returns a function that checks if the error is of a specific type T.
// This is useful for verifying that an error matches a particular interface or concrete type.
func ErrorAs[T error]() func(t *testing.T, err error) {
Expand Down

0 comments on commit b4251d3

Please sign in to comment.