mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 07:57:32 +00:00
chore: move the go code into a subfolder
This commit is contained in:
423
apps/api/internal/view/repository/db_mock_test.go
Normal file
423
apps/api/internal/view/repository/db_mock_test.go
Normal 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
|
||||
}
|
142
apps/api/internal/view/repository/query.go
Normal file
142
apps/api/internal/view/repository/query.go
Normal 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
|
||||
}
|
460
apps/api/internal/view/repository/query_test.go
Normal file
460
apps/api/internal/view/repository/query_test.go
Normal 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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
131
apps/api/internal/view/repository/requests.go
Normal file
131
apps/api/internal/view/repository/requests.go
Normal 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
|
||||
}
|
||||
}
|
401
apps/api/internal/view/repository/requests_test.go
Normal file
401
apps/api/internal/view/repository/requests_test.go
Normal 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()
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user