chore: move the go code into a subfolder

This commit is contained in:
Florian Forster
2025-08-05 15:20:32 -07:00
parent 4ad22ba456
commit cd2921de26
2978 changed files with 373 additions and 300 deletions

View File

@@ -0,0 +1,423 @@
package repository
import (
"database/sql/driver"
"fmt"
"strconv"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/jinzhu/gorm"
db_mock "github.com/zitadel/zitadel/internal/database/mock"
"github.com/zitadel/zitadel/internal/domain"
)
var (
expectedGetByID = `SELECT \* FROM "%s" WHERE \(%s = \$1\) LIMIT 1`
expectedGetByQuery = `SELECT \* FROM "%s" WHERE \(LOWER\(%s\) %s LOWER\(\$1\)\) LIMIT 1`
expectedGetByQueryCaseSensitive = `SELECT \* FROM "%s" WHERE \(%s %s \$1\) LIMIT 1`
expectedSave = `UPDATE "%s" SET "test" = \$1 WHERE "%s"."%s" = \$2`
expectedRemove = `DELETE FROM "%s" WHERE \(%s = \$1\)`
expectedRemoveByKeys = func(i int, table string) string {
sql := fmt.Sprintf(`DELETE FROM "%s"`, table)
sql += ` WHERE \(%s = \$1\)`
for j := 1; j < i; j++ {
sql = sql + ` AND \(%s = \$` + strconv.Itoa(j+1) + `\)`
}
return sql
}
expectedRemoveByObject = `DELETE FROM "%s" WHERE "%s"."%s" = \$1`
expectedRemoveByObjectMultiplePK = `DELETE FROM "%s" WHERE "%s"."%s" = \$1 AND "%s"."%s" = \$2`
expectedTruncate = `TRUNCATE %s;`
expectedSearch = `SELECT \* FROM "%s" OFFSET 0`
expectedSearchCount = `SELECT count\(\*\) FROM "%s"`
expectedSearchLimit = `SELECT \* FROM "%s" LIMIT %v OFFSET 0`
expectedSearchLimitCount = `SELECT count\(\*\) FROM "%s"`
expectedSearchOffset = `SELECT \* FROM "%s" OFFSET %v`
expectedSearchOffsetCount = `SELECT count\(\*\) FROM "%s"`
expectedSearchSorting = `SELECT \* FROM "%s" ORDER BY %s %s OFFSET 0`
expectedSearchSortingCount = `SELECT count\(\*\) FROM "%s"`
expectedSearchQuery = `SELECT \* FROM "%s" WHERE \(LOWER\(%s\) %s LOWER\(\$1\)\) OFFSET 0`
expectedSearchQueryCount = `SELECT count\(\*\) FROM "%s" WHERE \(LOWER\(%s\) %s LOWER\(\$1\)\)`
expectedSearchQueryAllParams = `SELECT \* FROM "%s" WHERE \(LOWER\(%s\) %s LOWER\(\$1\)\) ORDER BY %s %s LIMIT %v OFFSET %v`
expectedSearchQueryAllParamCount = `SELECT count\(\*\) FROM "%s" WHERE \(LOWER\(%s\) %s LOWER\(\$1\)\)`
)
type TestSearchRequest struct {
limit uint64
offset uint64
sortingColumn ColumnKey
asc bool
queries []SearchQuery
}
func (req TestSearchRequest) GetLimit() uint64 {
return req.limit
}
func (req TestSearchRequest) GetOffset() uint64 {
return req.offset
}
func (req TestSearchRequest) GetSortingColumn() ColumnKey {
return req.sortingColumn
}
func (req TestSearchRequest) GetAsc() bool {
return req.asc
}
func (req TestSearchRequest) GetQueries() []SearchQuery {
return req.queries
}
type TestSearchQuery struct {
key TestSearchKey
method domain.SearchMethod
value string
}
func (req TestSearchQuery) GetKey() ColumnKey {
return req.key
}
func (req TestSearchQuery) GetMethod() domain.SearchMethod {
return req.method
}
func (req TestSearchQuery) GetValue() interface{} {
return req.value
}
type TestSearchKey int32
const (
TestSearchKey_UNDEFINED TestSearchKey = iota
TestSearchKey_TEST
TestSearchKey_ID
)
func (key TestSearchKey) ToColumnName() string {
switch TestSearchKey(key) {
case TestSearchKey_TEST:
return "test"
case TestSearchKey_ID:
return "id"
default:
return ""
}
}
type Test struct {
ID string `json:"-" gorm:"column:primary_id;primary_key"`
Test string `json:"test" gorm:"column:test"`
}
type TestMultiplePK struct {
TestID string `gorm:"column:testId;primary_key"`
HodorID string `gorm:"column:hodorId;primary_key"`
Test string `gorm:"column:test"`
}
type dbMock struct {
db *gorm.DB
mock sqlmock.Sqlmock
}
func (db *dbMock) close() {
db.db.Close()
}
func mockDB(t *testing.T) *dbMock {
mockDB := dbMock{}
db, mock, err := sqlmock.New(sqlmock.ValueConverterOption(new(db_mock.TypeConverter)))
if err != nil {
t.Fatalf("error occurred while creating stub db %v", err)
}
mockDB.mock = mock
mockDB.db, err = gorm.Open("postgres", db)
if err != nil {
t.Fatalf("error occurred while connecting to stub db: %v", err)
}
mockDB.mock.MatchExpectationsInOrder(true)
return &mockDB
}
func (db *dbMock) expectBegin(err error) *dbMock {
if err != nil {
db.mock.ExpectBegin().WillReturnError(err)
} else {
db.mock.ExpectBegin()
}
return db
}
func (db *dbMock) expectCommit(err error) *dbMock {
if err != nil {
db.mock.ExpectCommit().WillReturnError(err)
} else {
db.mock.ExpectCommit()
}
return db
}
func (db *dbMock) expectRollback(err error) *dbMock {
if err != nil {
db.mock.ExpectRollback().WillReturnError(err)
} else {
db.mock.ExpectRollback()
}
return db
}
func (db *dbMock) expectGetByID(table, key, value string) *dbMock {
query := fmt.Sprintf(expectedGetByID, table, key)
db.mock.ExpectQuery(query).
WithArgs(value).
WillReturnRows(db.mock.NewRows([]string{key}).
AddRow(key))
return db
}
func (db *dbMock) expectGetByIDErr(table, key, value string, err error) *dbMock {
query := fmt.Sprintf(expectedGetByID, table, key)
db.mock.ExpectQuery(query).
WithArgs(value).
WillReturnError(err)
return db
}
func (db *dbMock) expectGetByQuery(table, key, method, value string) *dbMock {
query := fmt.Sprintf(expectedGetByQuery, table, key, method)
db.mock.ExpectQuery(query).
WithArgs(value).
WillReturnRows(db.mock.NewRows([]string{key}).
AddRow(key))
return db
}
func (db *dbMock) expectGetByQueryCaseSensitive(table, key, method, value string) *dbMock {
query := fmt.Sprintf(expectedGetByQueryCaseSensitive, table, key, method)
db.mock.ExpectQuery(query).
WithArgs(value).
WillReturnRows(db.mock.NewRows([]string{key}).
AddRow(key))
return db
}
func (db *dbMock) expectGetByQueryErr(table, key, method, value string, err error) *dbMock {
query := fmt.Sprintf(expectedGetByQuery, table, key, method)
db.mock.ExpectQuery(query).
WithArgs(value).
WillReturnError(err)
return db
}
func (db *dbMock) expectSave(table string, object Test) *dbMock {
query := fmt.Sprintf(expectedSave, table, table, "primary_id")
db.mock.ExpectExec(query).
WithArgs(object.Test, object.ID).
WillReturnResult(sqlmock.NewResult(1, 1))
return db
}
func (db *dbMock) expectSaveErr(table string, object Test, err error) *dbMock {
query := fmt.Sprintf(expectedSave, table, table, "id")
db.mock.ExpectExec(query).
WithArgs(object.Test, object.ID).
WillReturnError(err)
return db
}
func (db *dbMock) expectRemove(table, key, value string) *dbMock {
query := fmt.Sprintf(expectedRemove, table, key)
db.mock.ExpectExec(query).
WithArgs(value).
WillReturnResult(sqlmock.NewResult(1, 1))
return db
}
func (db *dbMock) expectRemoveKeys(table string, keys ...Key) *dbMock {
keyNames := make([]interface{}, len(keys))
keyValues := make([]driver.Value, len(keys))
for i, key := range keys {
keyNames[i] = key.Key.ToColumnName()
keyValues[i] = key.Value
}
query := fmt.Sprintf(expectedRemoveByKeys(len(keys), table), keyNames...)
db.mock.ExpectExec(query).
WithArgs(keyValues...).
WillReturnResult(sqlmock.NewResult(1, 1))
return db
}
func (db *dbMock) expectRemoveByObject(table string, object Test) *dbMock {
query := fmt.Sprintf(expectedRemoveByObject, table, table, "primary_id")
db.mock.ExpectExec(query).
WithArgs(object.ID).
WillReturnResult(sqlmock.NewResult(1, 1))
return db
}
func (db *dbMock) expectRemoveByObjectMultiplePKs(table string, object TestMultiplePK) *dbMock {
query := fmt.Sprintf(expectedRemoveByObjectMultiplePK, table, table, "testId", table, "hodorId")
db.mock.ExpectExec(query).
WithArgs(object.TestID, object.HodorID).
WillReturnResult(sqlmock.NewResult(1, 1))
return db
}
func (db *dbMock) expectRemoveErr(table, key, value string, err error) *dbMock {
query := fmt.Sprintf(expectedRemove, table, key)
db.mock.ExpectExec(query).
WithArgs(value).
WillReturnError(err)
return db
}
func (db *dbMock) expectTruncate(table string) *dbMock {
query := fmt.Sprintf(expectedTruncate, table)
db.mock.ExpectExec(query).
WillReturnResult(sqlmock.NewResult(1, 1))
return db
}
func (db *dbMock) expectTruncateErr(table string, err error) *dbMock {
query := fmt.Sprintf(expectedTruncate, table)
db.mock.ExpectExec(query).
WillReturnError(err)
return db
}
func (db *dbMock) expectGetSearchRequestNoParams(table string, resultAmount, total int) *dbMock {
query := fmt.Sprintf(expectedSearch, table)
queryCount := fmt.Sprintf(expectedSearchCount, table)
rows := db.mock.NewRows([]string{"id"})
for i := 0; i < resultAmount; i++ {
rows.AddRow(fmt.Sprintf("hodor-%d", i))
}
db.mock.ExpectQuery(queryCount).
WillReturnRows(db.mock.NewRows([]string{"count"}).AddRow(total))
db.mock.ExpectQuery(query).
WillReturnRows(rows)
return db
}
func (db *dbMock) expectGetSearchRequestWithLimit(table string, limit, resultAmount, total int) *dbMock {
query := fmt.Sprintf(expectedSearchLimit, table, limit)
queryCount := fmt.Sprintf(expectedSearchLimitCount, table)
rows := db.mock.NewRows([]string{"id"})
for i := 0; i < resultAmount; i++ {
rows.AddRow(fmt.Sprintf("hodor-%d", i))
}
db.mock.ExpectQuery(queryCount).
WillReturnRows(db.mock.NewRows([]string{"count"}).AddRow(total))
db.mock.ExpectQuery(query).
WillReturnRows(rows)
return db
}
func (db *dbMock) expectGetSearchRequestWithOffset(table string, offset, resultAmount, total int) *dbMock {
query := fmt.Sprintf(expectedSearchOffset, table, offset)
queryCount := fmt.Sprintf(expectedSearchOffsetCount, table)
rows := db.mock.NewRows([]string{"id"})
for i := 0; i < resultAmount; i++ {
rows.AddRow(fmt.Sprintf("hodor-%d", i))
}
db.mock.ExpectQuery(queryCount).
WillReturnRows(db.mock.NewRows([]string{"count"}).AddRow(total))
db.mock.ExpectQuery(query).
WillReturnRows(rows)
return db
}
func (db *dbMock) expectGetSearchRequestWithSorting(table, sorting string, sortingColumn ColumnKey, resultAmount, total int) *dbMock {
query := fmt.Sprintf(expectedSearchSorting, table, sortingColumn.ToColumnName(), sorting)
queryCount := fmt.Sprintf(expectedSearchSortingCount, table)
rows := db.mock.NewRows([]string{"id"})
for i := 0; i < resultAmount; i++ {
rows.AddRow(fmt.Sprintf("hodor-%d", i))
}
db.mock.ExpectQuery(queryCount).
WillReturnRows(db.mock.NewRows([]string{"count"}).AddRow(total))
db.mock.ExpectQuery(query).
WillReturnRows(rows)
return db
}
func (db *dbMock) expectGetSearchRequestWithSearchQuery(table, key, method, value string, resultAmount, total int) *dbMock {
query := fmt.Sprintf(expectedSearchQuery, table, key, method)
queryCount := fmt.Sprintf(expectedSearchQueryCount, table, key, method)
rows := db.mock.NewRows([]string{"id"})
for i := 0; i < resultAmount; i++ {
rows.AddRow(fmt.Sprintf("hodor-%d", i))
}
db.mock.ExpectQuery(queryCount).
WithArgs(value).
WillReturnRows(db.mock.NewRows([]string{"count"}).AddRow(total))
db.mock.ExpectQuery(query).
WithArgs(value).
WillReturnRows(rows)
return db
}
func (db *dbMock) expectGetSearchRequestWithAllParams(table, key, method, value, sorting string, sortingColumn ColumnKey, limit, offset, resultAmount, total int) *dbMock {
query := fmt.Sprintf(expectedSearchQueryAllParams, table, key, method, sortingColumn.ToColumnName(), sorting, limit, offset)
queryCount := fmt.Sprintf(expectedSearchQueryAllParamCount, table, key, method)
rows := db.mock.NewRows([]string{"id"})
for i := 0; i < resultAmount; i++ {
rows.AddRow(fmt.Sprintf("hodor-%d", i))
}
db.mock.ExpectQuery(queryCount).
WithArgs(value).
WillReturnRows(db.mock.NewRows([]string{"count"}).AddRow(total))
db.mock.ExpectQuery(query).
WithArgs(value).
WillReturnRows(rows)
return db
}
func (db *dbMock) expectGetSearchRequestErr(table string, resultAmount, total int, err error) *dbMock {
query := fmt.Sprintf(expectedSearch, table)
queryCount := fmt.Sprintf(expectedSearchCount, table)
rows := db.mock.NewRows([]string{"id"})
for i := 0; i < resultAmount; i++ {
rows.AddRow(fmt.Sprintf("hodor-%d", i))
}
db.mock.ExpectQuery(queryCount).
WillReturnRows(db.mock.NewRows([]string{"count"}).AddRow(total))
db.mock.ExpectQuery(query).
WillReturnError(err)
return db
}

View File

@@ -0,0 +1,142 @@
package repository
import (
"fmt"
"github.com/jinzhu/gorm"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/zerrors"
)
type SearchRequest interface {
GetLimit() uint64
GetOffset() uint64
GetSortingColumn() ColumnKey
GetAsc() bool
GetQueries() []SearchQuery
}
type SearchQuery interface {
GetKey() ColumnKey
GetMethod() domain.SearchMethod
GetValue() interface{}
}
type ColumnKey interface {
ToColumnName() string
}
func PrepareSearchQuery(table string, request SearchRequest) func(db *gorm.DB, res interface{}) (uint64, error) {
return func(db *gorm.DB, res interface{}) (uint64, error) {
var count uint64 = 0
query := db.Table(table)
if column := request.GetSortingColumn(); column != nil {
order := "DESC"
if request.GetAsc() {
order = "ASC"
}
query = query.Order(fmt.Sprintf("%s %s", column.ToColumnName(), order))
}
for _, q := range request.GetQueries() {
var err error
query, err = SetQuery(query, q.GetKey(), q.GetValue(), q.GetMethod())
if err != nil {
return count, zerrors.ThrowInvalidArgument(err, "VIEW-KaGue", "query is invalid")
}
}
query = query.Count(&count)
if res == nil {
return count, nil
}
if request.GetLimit() != 0 {
query = query.Limit(request.GetLimit())
}
query = query.Offset(request.GetOffset())
err := query.Find(res).Error
if err != nil {
return count, zerrors.ThrowInternal(err, "VIEW-muSDK", "unable to find result")
}
return count, nil
}
}
func SetQuery(query *gorm.DB, key ColumnKey, value interface{}, method domain.SearchMethod) (*gorm.DB, error) {
column := key.ToColumnName()
if column == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "VIEW-7dz3w", "Column name missing")
}
switch method {
case domain.SearchMethodEquals:
query = query.Where(""+column+" = ?", value)
case domain.SearchMethodEqualsIgnoreCase:
valueText, ok := value.(string)
if !ok {
return nil, zerrors.ThrowInvalidArgument(nil, "VIEW-idu8e", "Equal ignore case only possible for strings")
}
query = query.Where("LOWER("+column+") = LOWER(?)", valueText)
case domain.SearchMethodStartsWith:
valueText, ok := value.(string)
if !ok {
return nil, zerrors.ThrowInvalidArgument(nil, "VIEW-SLj7s", "Starts with only possible for strings")
}
valueText = database.EscapeLikeWildcards(valueText)
query = query.Where(column+" LIKE ?", valueText+"%")
case domain.SearchMethodStartsWithIgnoreCase:
valueText, ok := value.(string)
if !ok {
return nil, zerrors.ThrowInvalidArgument(nil, "VIEW-eidus", "Starts with ignore case only possible for strings")
}
valueText = database.EscapeLikeWildcards(valueText)
query = query.Where("LOWER("+column+") LIKE LOWER(?)", valueText+"%")
case domain.SearchMethodEndsWith:
valueText, ok := value.(string)
if !ok {
return nil, zerrors.ThrowInvalidArgument(nil, "VIEW-Hswd3", "Ends with only possible for strings")
}
valueText = database.EscapeLikeWildcards(valueText)
query = query.Where(column+" LIKE ?", "%"+valueText)
case domain.SearchMethodEndsWithIgnoreCase:
valueText, ok := value.(string)
if !ok {
return nil, zerrors.ThrowInvalidArgument(nil, "VIEW-dAG31", "Ends with ignore case only possible for strings")
}
valueText = database.EscapeLikeWildcards(valueText)
query = query.Where("LOWER("+column+") LIKE LOWER(?)", "%"+valueText)
case domain.SearchMethodContains:
valueText, ok := value.(string)
if !ok {
return nil, zerrors.ThrowInvalidArgument(nil, "VIEW-3ids", "Contains with only possible for strings")
}
valueText = database.EscapeLikeWildcards(valueText)
query = query.Where(column+" LIKE ?", "%"+valueText+"%")
case domain.SearchMethodContainsIgnoreCase:
valueText, ok := value.(string)
if !ok {
return nil, zerrors.ThrowInvalidArgument(nil, "VIEW-eid73", "Contains with ignore case only possible for strings")
}
valueText = database.EscapeLikeWildcards(valueText)
query = query.Where("LOWER("+column+") LIKE LOWER(?)", "%"+valueText+"%")
case domain.SearchMethodNotEquals:
query = query.Where(""+column+" <> ?", value)
case domain.SearchMethodGreaterThan:
query = query.Where(column+" > ?", value)
case domain.SearchMethodLessThan:
query = query.Where(column+" < ?", value)
case domain.SearchMethodIsOneOf:
query = query.Where(column+" IN (?)", value)
case domain.SearchMethodListContains:
valueText, ok := value.(string)
if !ok {
return nil, zerrors.ThrowInvalidArgument(nil, "VIEW-Psois", "list contains only possible for strings")
}
query = query.Where("? <@ "+column, database.TextArray[string]{valueText})
default:
return nil, nil
}
return query, nil
}

View File

@@ -0,0 +1,460 @@
package repository
import (
"reflect"
"testing"
"github.com/jinzhu/gorm"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestPrepareSearchQuery(t *testing.T) {
type args struct {
table string
searchRequest SearchRequest
}
type res struct {
count uint64
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
db *dbMock
args args
res res
}{
{
"search with no params",
mockDB(t).
expectGetSearchRequestNoParams("TESTTABLE", 1, 1),
args{
table: "TESTTABLE",
searchRequest: TestSearchRequest{},
},
res{
count: 1,
wantErr: false,
},
},
{
"search with limit",
mockDB(t).
expectGetSearchRequestWithLimit("TESTTABLE", 2, 2, 5),
args{
table: "TESTTABLE",
searchRequest: TestSearchRequest{limit: 2},
},
res{
count: 5,
wantErr: false,
},
},
{
"search with offset",
mockDB(t).
expectGetSearchRequestWithOffset("TESTTABLE", 2, 2, 2),
args{
table: "TESTTABLE",
searchRequest: TestSearchRequest{offset: 2},
},
res{
count: 2,
wantErr: false,
},
},
{
"search with sorting asc",
mockDB(t).
expectGetSearchRequestWithSorting("TESTTABLE", "ASC", TestSearchKey_ID, 2, 2),
args{
table: "TESTTABLE",
searchRequest: TestSearchRequest{sortingColumn: TestSearchKey_ID, asc: true},
},
res{
count: 2,
wantErr: false,
},
},
{
"search with sorting asc",
mockDB(t).
expectGetSearchRequestWithSorting("TESTTABLE", "DESC", TestSearchKey_ID, 2, 2),
args{
table: "TESTTABLE",
searchRequest: TestSearchRequest{sortingColumn: TestSearchKey_ID},
},
res{
count: 2,
wantErr: false,
},
},
{
"search with search query",
mockDB(t).
expectGetSearchRequestWithSearchQuery("TESTTABLE", TestSearchKey_ID.ToColumnName(), "=", "AggregateID", 2, 2),
args{
table: "TESTTABLE",
searchRequest: TestSearchRequest{queries: []SearchQuery{TestSearchQuery{key: TestSearchKey_ID, method: domain.SearchMethodEqualsIgnoreCase, value: "AggregateID"}}},
},
res{
count: 2,
wantErr: false,
},
},
{
"search with all params",
mockDB(t).
expectGetSearchRequestWithAllParams("TESTTABLE", TestSearchKey_ID.ToColumnName(), "=", "AggregateID", "ASC", TestSearchKey_ID, 2, 2, 2, 5),
args{
table: "TESTTABLE",
searchRequest: TestSearchRequest{limit: 2, offset: 2, sortingColumn: TestSearchKey_ID, asc: true, queries: []SearchQuery{TestSearchQuery{key: TestSearchKey_ID, method: domain.SearchMethodEqualsIgnoreCase, value: "AggregateID"}}},
},
res{
count: 5,
wantErr: false,
},
},
{
"search db error",
mockDB(t).
expectGetSearchRequestErr("TESTTABLE", 1, 1, gorm.ErrUnaddressable),
args{
table: "TESTTABLE",
searchRequest: TestSearchRequest{},
},
res{
count: 1,
wantErr: true,
errFunc: zerrors.IsInternal,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res := &Test{}
getQuery := PrepareSearchQuery(tt.args.table, tt.args.searchRequest)
count, err := getQuery(tt.db.db, res)
if !tt.res.wantErr && err != nil {
t.Errorf("got wrong err should be nil: %v ", err)
}
if !tt.res.wantErr && count != tt.res.count {
t.Errorf("got wrong count: %v ", err)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
if err := tt.db.mock.ExpectationsWereMet(); !tt.res.wantErr && err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
tt.db.close()
})
}
}
func TestSetQuery(t *testing.T) {
query := mockDB(t).db.Select("test_field").Table("test_table")
exprPrefix := `(SELECT test_field FROM "test_table" WHERE `
type args struct {
key ColumnKey
value interface{}
method domain.SearchMethod
}
type want struct {
isErr func(t *testing.T, got error)
query *gorm.SqlExpr
}
tests := []struct {
name string
args args
want want
}{
{
name: "contains",
args: args{
key: TestSearchKey_TEST,
value: "asdf",
method: domain.SearchMethodContains,
},
want: want{
query: gorm.Expr(exprPrefix+"(test LIKE ?))", "%asdf%"),
},
},
{
name: "contains _ wildcard",
args: args{
key: TestSearchKey_TEST,
value: "as_df",
method: domain.SearchMethodContains,
},
want: want{
query: gorm.Expr(exprPrefix+"(test LIKE ?))", "%as\\_df%"),
},
},
{
name: "contains % wildcard",
args: args{
key: TestSearchKey_TEST,
value: "as%df",
method: domain.SearchMethodContains,
},
want: want{
query: gorm.Expr(exprPrefix+"(test LIKE ?))", "%as\\%df%"),
},
},
{
name: "contains % wildcard",
args: args{
key: TestSearchKey_TEST,
value: "a_s%d_f",
method: domain.SearchMethodContains,
},
want: want{
query: gorm.Expr(exprPrefix+"(test LIKE ?))", "%a\\_s\\%d\\_f%"),
},
},
{
name: "starts with",
args: args{
key: TestSearchKey_TEST,
value: "asdf",
method: domain.SearchMethodStartsWith,
},
want: want{
query: gorm.Expr(exprPrefix+"(test LIKE ?))", "asdf%"),
},
},
{
name: "starts with _ wildcard",
args: args{
key: TestSearchKey_TEST,
value: "as_df",
method: domain.SearchMethodStartsWith,
},
want: want{
query: gorm.Expr(exprPrefix+"(test LIKE ?))", "as\\_df%"),
},
},
{
name: "starts with % wildcard",
args: args{
key: TestSearchKey_TEST,
value: "as%df",
method: domain.SearchMethodStartsWith,
},
want: want{
query: gorm.Expr(exprPrefix+"(test LIKE ?))", "as\\%df%"),
},
},
{
name: "starts with % wildcard",
args: args{
key: TestSearchKey_TEST,
value: "a_s%d_f",
method: domain.SearchMethodStartsWith,
},
want: want{
query: gorm.Expr(exprPrefix+"(test LIKE ?))", "a\\_s\\%d\\_f%"),
},
},
{
name: "ends with",
args: args{
key: TestSearchKey_TEST,
value: "asdf",
method: domain.SearchMethodEndsWith,
},
want: want{
query: gorm.Expr(exprPrefix+"(test LIKE ?))", "%asdf"),
},
},
{
name: "ends with _ wildcard",
args: args{
key: TestSearchKey_TEST,
value: "as_df",
method: domain.SearchMethodEndsWith,
},
want: want{
query: gorm.Expr(exprPrefix+"(test LIKE ?))", "%as\\_df"),
},
},
{
name: "ends with % wildcard",
args: args{
key: TestSearchKey_TEST,
value: "as%df",
method: domain.SearchMethodEndsWith,
},
want: want{
query: gorm.Expr(exprPrefix+"(test LIKE ?))", "%as\\%df"),
},
},
{
name: "ends with % wildcard",
args: args{
key: TestSearchKey_TEST,
value: "a_s%d_f",
method: domain.SearchMethodEndsWith,
},
want: want{
query: gorm.Expr(exprPrefix+"(test LIKE ?))", "%a\\_s\\%d\\_f"),
},
},
{
name: "starts with ignore case",
args: args{
key: TestSearchKey_TEST,
value: "asdf",
method: domain.SearchMethodStartsWithIgnoreCase,
},
want: want{
query: gorm.Expr(exprPrefix+"(LOWER(test) LIKE LOWER(?)))", "asdf%"),
},
},
{
name: "starts with ignore case _ wildcard",
args: args{
key: TestSearchKey_TEST,
value: "as_df",
method: domain.SearchMethodStartsWithIgnoreCase,
},
want: want{
query: gorm.Expr(exprPrefix+"(LOWER(test) LIKE LOWER(?)))", "as\\_df%"),
},
},
{
name: "starts with ignore case % wildcard",
args: args{
key: TestSearchKey_TEST,
value: "as%df",
method: domain.SearchMethodStartsWithIgnoreCase,
},
want: want{
query: gorm.Expr(exprPrefix+"(LOWER(test) LIKE LOWER(?)))", "as\\%df%"),
},
},
{
name: "starts with ignore case % wildcard",
args: args{
key: TestSearchKey_TEST,
value: "a_s%d_f",
method: domain.SearchMethodStartsWithIgnoreCase,
},
want: want{
query: gorm.Expr(exprPrefix+"(LOWER(test) LIKE LOWER(?)))", "a\\_s\\%d\\_f%"),
},
},
{
name: "ends with ignore case",
args: args{
key: TestSearchKey_TEST,
value: "asdf",
method: domain.SearchMethodEndsWithIgnoreCase,
},
want: want{
query: gorm.Expr(exprPrefix+"(LOWER(test) LIKE LOWER(?)))", "%asdf"),
},
},
{
name: "ends with ignore case _ wildcard",
args: args{
key: TestSearchKey_TEST,
value: "as_df",
method: domain.SearchMethodEndsWithIgnoreCase,
},
want: want{
query: gorm.Expr(exprPrefix+"(LOWER(test) LIKE LOWER(?)))", "%as\\_df"),
},
},
{
name: "ends with ignore case % wildcard",
args: args{
key: TestSearchKey_TEST,
value: "as%df",
method: domain.SearchMethodEndsWithIgnoreCase,
},
want: want{
query: gorm.Expr(exprPrefix+"(LOWER(test) LIKE LOWER(?)))", "%as\\%df"),
},
},
{
name: "ends with ignore case % wildcard",
args: args{
key: TestSearchKey_TEST,
value: "a_s%d_f",
method: domain.SearchMethodEndsWithIgnoreCase,
},
want: want{
query: gorm.Expr(exprPrefix+"(LOWER(test) LIKE LOWER(?)))", "%a\\_s\\%d\\_f"),
},
},
{
name: "contains ignore case",
args: args{
key: TestSearchKey_TEST,
value: "asdf",
method: domain.SearchMethodContainsIgnoreCase,
},
want: want{
query: gorm.Expr(exprPrefix+"(LOWER(test) LIKE LOWER(?)))", "%asdf%"),
},
},
{
name: "contains ignore case _ wildcard",
args: args{
key: TestSearchKey_TEST,
value: "as_df",
method: domain.SearchMethodContainsIgnoreCase,
},
want: want{
query: gorm.Expr(exprPrefix+"(LOWER(test) LIKE LOWER(?)))", "%as\\_df%"),
},
},
{
name: "contains ignore case % wildcard",
args: args{
key: TestSearchKey_TEST,
value: "as%df",
method: domain.SearchMethodContainsIgnoreCase,
},
want: want{
query: gorm.Expr(exprPrefix+"(LOWER(test) LIKE LOWER(?)))", "%as\\%df%"),
},
},
{
name: "contains ignore case % wildcard",
args: args{
key: TestSearchKey_TEST,
value: "a_s%d_f",
method: domain.SearchMethodContainsIgnoreCase,
},
want: want{
query: gorm.Expr(exprPrefix+"(LOWER(test) LIKE LOWER(?)))", "%a\\_s\\%d\\_f%"),
},
},
}
for _, tt := range tests {
if tt.want.isErr == nil {
tt.want.isErr = func(t *testing.T, got error) {
if got == nil {
return
}
t.Errorf("no error expected got: %v", got)
}
}
t.Run(tt.name, func(t *testing.T) {
got, err := SetQuery(query, tt.args.key, tt.args.value, tt.args.method)
tt.want.isErr(t, err)
if !reflect.DeepEqual(got.SubQuery(), tt.want.query) {
t.Errorf("unexpected query: \nwant: %v\n got: %v", *tt.want.query, *got.SubQuery())
}
})
}
}

View File

@@ -0,0 +1,131 @@
package repository
import (
"errors"
"fmt"
"strings"
"github.com/jinzhu/gorm"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/zerrors"
)
func PrepareGetByQuery(table string, queries ...SearchQuery) func(db *gorm.DB, res interface{}) error {
return func(db *gorm.DB, res interface{}) error {
query := db.Table(table)
for _, q := range queries {
var err error
query, err = SetQuery(query, q.GetKey(), q.GetValue(), q.GetMethod())
if err != nil {
return zerrors.ThrowInvalidArgument(err, "VIEW-KaGue", "query is invalid")
}
}
err := query.Take(res).Error
if err == nil {
return nil
}
if errors.Is(err, gorm.ErrRecordNotFound) {
return zerrors.ThrowNotFound(err, "VIEW-hodc6", "object not found")
}
logging.LogWithFields("VIEW-Mg6la", "table ", table).WithError(err).Warn("get from cache error")
return zerrors.ThrowInternal(err, "VIEW-qJBg9", "cache error")
}
}
func PrepareBulkSave(table string) func(db *gorm.DB, objects ...interface{}) error {
return func(db *gorm.DB, objects ...interface{}) error {
db = db.Table(table)
db = db.Begin()
defer db.RollbackUnlessCommitted()
if err := db.Error; err != nil {
return zerrors.ThrowInternal(err, "REPOS-Fl0Is", "unable to begin")
}
for _, object := range objects {
err := db.Save(object).Error
if err != nil {
return zerrors.ThrowInternal(err, "VIEW-oJJSm", "unable to put object to view")
}
}
if err := db.Commit().Error; err != nil {
return zerrors.ThrowInternal(err, "REPOS-IfhUE", "unable to commit")
}
return nil
}
}
func PrepareSave(table string) func(db *gorm.DB, object interface{}) error {
return func(db *gorm.DB, object interface{}) error {
err := db.Table(table).Save(object).Error
if err != nil {
return zerrors.ThrowInternal(err, "VIEW-2m9fs", "unable to put object to view")
}
return nil
}
}
func PrepareSaveOnConflict(table string, conflictColumns, updateColumns []string) func(db *gorm.DB, object interface{}) error {
updates := make([]string, len(updateColumns))
for i, column := range updateColumns {
updates[i] = column + "=excluded." + column
}
onConflict := fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET %s", strings.Join(conflictColumns, ","), strings.Join(updates, ","))
return func(db *gorm.DB, object interface{}) error {
err := db.Table(table).Set("gorm:insert_option", onConflict).Save(object).Error
if err != nil {
return zerrors.ThrowInternal(err, "VIEW-AfC7G", "unable to put object to view")
}
return nil
}
}
func PrepareDeleteByKey(table string, key ColumnKey, id interface{}) func(db *gorm.DB) error {
return func(db *gorm.DB) error {
err := db.Table(table).
Where(fmt.Sprintf("%s = ?", key.ToColumnName()), id).
Delete(nil).
Error
if err != nil {
return zerrors.ThrowInternal(err, "VIEW-die73", "could not delete object")
}
return nil
}
}
func PrepareUpdateByKeys(table string, column ColumnKey, value interface{}, keys ...Key) func(db *gorm.DB) error {
return func(db *gorm.DB) error {
for _, key := range keys {
db = db.Table(table).
Where(fmt.Sprintf("%s = ?", key.Key.ToColumnName()), key.Value)
}
err := db.
Update(column.ToColumnName(), value).
Error
if err != nil {
return zerrors.ThrowInternal(err, "VIEW-ps099xj", "could not update object")
}
return nil
}
}
type Key struct {
Key ColumnKey
Value interface{}
}
func PrepareDeleteByKeys(table string, keys ...Key) func(db *gorm.DB) error {
return func(db *gorm.DB) error {
for _, key := range keys {
db = db.Table(table).
Where(fmt.Sprintf("%s = ?", key.Key.ToColumnName()), key.Value)
}
err := db.
Delete(nil).
Error
if err != nil {
return zerrors.ThrowInternal(err, "VIEW-die73", "could not delete object")
}
return nil
}
}

View File

@@ -0,0 +1,401 @@
package repository
import (
"testing"
"github.com/jinzhu/gorm"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestPrepareGetByQuery(t *testing.T) {
type args struct {
table string
searchQuery SearchQuery
}
type res struct {
result Test
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
db *dbMock
args args
res res
}{
{
"search with equals case insensitive",
mockDB(t).
expectGetByQuery("TESTTABLE", "test", "=", "VALUE"),
args{
table: "TESTTABLE",
searchQuery: TestSearchQuery{key: TestSearchKey_TEST, method: domain.SearchMethodEqualsIgnoreCase, value: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"search with equals case sensitive",
mockDB(t).
expectGetByQueryCaseSensitive("TESTTABLE", "test", "=", "VALUE"),
args{
table: "TESTTABLE",
searchQuery: TestSearchQuery{key: TestSearchKey_TEST, method: domain.SearchMethodEquals, value: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"search with startswith, case insensitive",
mockDB(t).
expectGetByQuery("TESTTABLE", "test", "LIKE", "VALUE%"),
args{
table: "TESTTABLE",
searchQuery: TestSearchQuery{key: TestSearchKey_TEST, method: domain.SearchMethodStartsWithIgnoreCase, value: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"search with startswith case sensitive",
mockDB(t).
expectGetByQueryCaseSensitive("TESTTABLE", "test", "LIKE", "VALUE%"),
args{
table: "TESTTABLE",
searchQuery: TestSearchQuery{key: TestSearchKey_TEST, method: domain.SearchMethodStartsWith, value: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"search with contains case insensitive",
mockDB(t).
expectGetByQuery("TESTTABLE", "test", "LIKE", "%VALUE%"),
args{
table: "TESTTABLE",
searchQuery: TestSearchQuery{key: TestSearchKey_TEST, method: domain.SearchMethodContainsIgnoreCase, value: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"search with contains case sensitive",
mockDB(t).
expectGetByQueryCaseSensitive("TESTTABLE", "test", "LIKE", "%VALUE%"),
args{
table: "TESTTABLE",
searchQuery: TestSearchQuery{key: TestSearchKey_TEST, method: domain.SearchMethodContains, value: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"search expect not found err",
mockDB(t).
expectGetByQueryErr("TESTTABLE", "test", "LIKE", "%VALUE%", gorm.ErrRecordNotFound),
args{
table: "TESTTABLE",
searchQuery: TestSearchQuery{key: TestSearchKey_TEST, method: domain.SearchMethodContainsIgnoreCase, value: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: true,
errFunc: zerrors.IsNotFound,
},
},
{
"search expect internal err",
mockDB(t).
expectGetByQueryErr("TESTTABLE", "test", "LIKE", "%VALUE%", gorm.ErrUnaddressable),
args{
table: "TESTTABLE",
searchQuery: TestSearchQuery{key: TestSearchKey_TEST, method: domain.SearchMethodContainsIgnoreCase, value: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: true,
errFunc: zerrors.IsInternal,
},
},
{
"search with invalid column",
mockDB(t).
expectGetByQuery("TESTTABLE", "", "=", "VALUE"),
args{
table: "TESTTABLE",
searchQuery: TestSearchQuery{key: TestSearchKey_UNDEFINED, method: domain.SearchMethodEqualsIgnoreCase, value: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: true,
errFunc: zerrors.IsErrorInvalidArgument,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res := &Test{}
getByQuery := PrepareGetByQuery(tt.args.table, tt.args.searchQuery)
err := getByQuery(tt.db.db, res)
if !tt.res.wantErr && err != nil {
t.Errorf("got wrong err should be nil: %v ", err)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
if err := tt.db.mock.ExpectationsWereMet(); !tt.res.wantErr && err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
tt.db.close()
})
}
}
func TestPreparePut(t *testing.T) {
type args struct {
table string
object *Test
}
type res struct {
result Test
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
db *dbMock
args args
res res
}{
{
"ok",
mockDB(t).
expectBegin(nil).
expectSave("TESTTABLE", Test{ID: "AggregateID", Test: "VALUE"}).
expectCommit(nil),
args{
table: "TESTTABLE",
object: &Test{ID: "AggregateID", Test: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"db error",
mockDB(t).
expectBegin(nil).
expectSaveErr("TESTTABLE", Test{ID: "AggregateID", Test: "VALUE"}, gorm.ErrUnaddressable).
expectCommit(nil),
args{
table: "TESTTABLE",
object: &Test{ID: "AggregateID", Test: "VALUE"},
},
res{
result: Test{ID: "VALUE"},
wantErr: true,
errFunc: zerrors.IsInternal,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
getPut := PrepareSave(tt.args.table)
err := getPut(tt.db.db, tt.args.object)
if !tt.res.wantErr && err != nil {
t.Errorf("got wrong err should be nil: %v ", err)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
if err := tt.db.mock.ExpectationsWereMet(); !tt.res.wantErr && err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
tt.db.close()
})
}
}
func TestPrepareDelete(t *testing.T) {
type args struct {
table string
key ColumnKey
value string
}
type res struct {
result Test
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
db *dbMock
args args
res res
}{
{
"delete",
mockDB(t).
expectBegin(nil).
expectRemove("TESTTABLE", "id", "VALUE").
expectCommit(nil),
args{
table: "TESTTABLE",
key: TestSearchKey_ID,
value: "VALUE",
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"db error",
mockDB(t).
expectBegin(nil).
expectRemoveErr("TESTTABLE", "id", "VALUE", gorm.ErrUnaddressable).
expectCommit(nil),
args{
table: "TESTTABLE",
key: TestSearchKey_ID,
value: "VALUE",
},
res{
result: Test{ID: "VALUE"},
wantErr: true,
errFunc: zerrors.IsInternal,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
getDelete := PrepareDeleteByKey(tt.args.table, tt.args.key, tt.args.value)
err := getDelete(tt.db.db)
if !tt.res.wantErr && err != nil {
t.Errorf("got wrong err should be nil: %v ", err)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
if err := tt.db.mock.ExpectationsWereMet(); !tt.res.wantErr && err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
tt.db.close()
})
}
}
func TestPrepareDeleteByKeys(t *testing.T) {
type args struct {
table string
keys []Key
}
type res struct {
result Test
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
db *dbMock
args args
res res
}{
{
"delete single key",
mockDB(t).
expectBegin(nil).
expectRemoveKeys("TESTTABLE", Key{Key: TestSearchKey_ID, Value: "VALUE"}).
expectCommit(nil),
args{
table: "TESTTABLE",
keys: []Key{
{Key: TestSearchKey_ID, Value: "VALUE"},
},
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"delete multiple keys",
mockDB(t).
expectBegin(nil).
expectRemoveKeys("TESTTABLE", Key{Key: TestSearchKey_ID, Value: "VALUE"}, Key{Key: TestSearchKey_TEST, Value: "VALUE2"}).
expectCommit(nil),
args{
table: "TESTTABLE",
keys: []Key{
{Key: TestSearchKey_ID, Value: "VALUE"},
{Key: TestSearchKey_TEST, Value: "VALUE2"},
},
},
res{
result: Test{ID: "VALUE"},
wantErr: false,
},
},
{
"db error",
mockDB(t).
expectBegin(nil).
expectRemoveErr("TESTTABLE", "id", "VALUE", gorm.ErrUnaddressable).
expectCommit(nil),
args{
table: "TESTTABLE",
keys: []Key{
{Key: TestSearchKey_ID, Value: "VALUE"},
},
},
res{
result: Test{ID: "VALUE"},
wantErr: true,
errFunc: zerrors.IsInternal,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
getDelete := PrepareDeleteByKeys(tt.args.table, tt.args.keys...)
err := getDelete(tt.db.db)
if !tt.res.wantErr && err != nil {
t.Errorf("got wrong err should be nil: %v ", err)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
if err := tt.db.mock.ExpectationsWereMet(); !tt.res.wantErr && err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
tt.db.close()
})
}
}