mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 08:57:35 +00:00
feat: administrator (#271)
* feat: get views and failed events * feat: get views and failed events * feat: get views and failed events * Update internal/view/repository/sequence.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * Update internal/view/repository/general_query.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
25
internal/view/repository/config.go
Normal file
25
internal/view/repository/config.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/caos/zitadel/internal/config/types"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/jinzhu/gorm"
|
||||
)
|
||||
|
||||
type ViewConfig struct {
|
||||
SQL *types.SQL
|
||||
}
|
||||
|
||||
func Start(conf ViewConfig) (*sql.DB, *gorm.DB, error) {
|
||||
sqlClient, err := sql.Open("postgres", conf.SQL.ConnectionString())
|
||||
if err != nil {
|
||||
return nil, nil, errors.ThrowPreconditionFailed(err, "SQL-9qBtr", "unable to open database connection")
|
||||
}
|
||||
|
||||
client, err := gorm.Open("postgres", sqlClient)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return sqlClient, client, nil
|
||||
}
|
421
internal/view/repository/db_mock_test.go
Normal file
421
internal/view/repository/db_mock_test.go
Normal file
@@ -0,0 +1,421 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/jinzhu/gorm"
|
||||
|
||||
"github.com/caos/zitadel/internal/model"
|
||||
)
|
||||
|
||||
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 model.SearchMethod
|
||||
value string
|
||||
}
|
||||
|
||||
func (req TestSearchQuery) GetKey() ColumnKey {
|
||||
return req.key
|
||||
}
|
||||
|
||||
func (req TestSearchQuery) GetMethod() model.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()
|
||||
if err != nil {
|
||||
t.Fatalf("error occured while creating stub db %v", err)
|
||||
}
|
||||
|
||||
mockDB.mock = mock
|
||||
mockDB.db, err = gorm.Open("postgres", db)
|
||||
if err != nil {
|
||||
t.Fatalf("error occured 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(sqlmock.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(sqlmock.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(sqlmock.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 := sqlmock.NewRows([]string{"id"})
|
||||
for i := 0; i < resultAmount; i++ {
|
||||
rows.AddRow(fmt.Sprintf("hodor-%d", i))
|
||||
}
|
||||
|
||||
db.mock.ExpectQuery(queryCount).
|
||||
WillReturnRows(sqlmock.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 := sqlmock.NewRows([]string{"id"})
|
||||
for i := 0; i < resultAmount; i++ {
|
||||
rows.AddRow(fmt.Sprintf("hodor-%d", i))
|
||||
}
|
||||
|
||||
db.mock.ExpectQuery(queryCount).
|
||||
WillReturnRows(sqlmock.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 := sqlmock.NewRows([]string{"id"})
|
||||
for i := 0; i < resultAmount; i++ {
|
||||
rows.AddRow(fmt.Sprintf("hodor-%d", i))
|
||||
}
|
||||
|
||||
db.mock.ExpectQuery(queryCount).
|
||||
WillReturnRows(sqlmock.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 := sqlmock.NewRows([]string{"id"})
|
||||
for i := 0; i < resultAmount; i++ {
|
||||
rows.AddRow(fmt.Sprintf("hodor-%d", i))
|
||||
}
|
||||
|
||||
db.mock.ExpectQuery(queryCount).
|
||||
WillReturnRows(sqlmock.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 := sqlmock.NewRows([]string{"id"})
|
||||
for i := 0; i < resultAmount; i++ {
|
||||
rows.AddRow(fmt.Sprintf("hodor-%d", i))
|
||||
}
|
||||
|
||||
db.mock.ExpectQuery(queryCount).
|
||||
WithArgs(value).
|
||||
WillReturnRows(sqlmock.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 := sqlmock.NewRows([]string{"id"})
|
||||
for i := 0; i < resultAmount; i++ {
|
||||
rows.AddRow(fmt.Sprintf("hodor-%d", i))
|
||||
}
|
||||
|
||||
db.mock.ExpectQuery(queryCount).
|
||||
WithArgs(value).
|
||||
WillReturnRows(sqlmock.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 := sqlmock.NewRows([]string{"id"})
|
||||
for i := 0; i < resultAmount; i++ {
|
||||
rows.AddRow(fmt.Sprintf("hodor-%d", i))
|
||||
}
|
||||
|
||||
db.mock.ExpectQuery(queryCount).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(total))
|
||||
db.mock.ExpectQuery(query).
|
||||
WillReturnError(err)
|
||||
return db
|
||||
}
|
131
internal/view/repository/failed_events.go
Normal file
131
internal/view/repository/failed_events.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/model"
|
||||
view_model "github.com/caos/zitadel/internal/view/model"
|
||||
"github.com/jinzhu/gorm"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
errViewNameKey = "view_name"
|
||||
errFailedSeqKey = "failed_sequence"
|
||||
)
|
||||
|
||||
type FailedEvent struct {
|
||||
ViewName string `gorm:"column:view_name;primary_key"`
|
||||
FailedSequence uint64 `gorm:"column:failed_sequence;primary_key"`
|
||||
FailureCount uint64 `gorm:"column:failure_count"`
|
||||
ErrMsg string `gorm:"column:err_msg"`
|
||||
}
|
||||
|
||||
type FailedEventSearchQuery struct {
|
||||
Key FailedEventSearchKey
|
||||
Method model.SearchMethod
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func (req FailedEventSearchQuery) GetKey() ColumnKey {
|
||||
return failedEventSearchKey(req.Key)
|
||||
}
|
||||
|
||||
func (req FailedEventSearchQuery) GetMethod() model.SearchMethod {
|
||||
return req.Method
|
||||
}
|
||||
|
||||
func (req FailedEventSearchQuery) GetValue() interface{} {
|
||||
return req.Value
|
||||
}
|
||||
|
||||
type FailedEventSearchKey int32
|
||||
|
||||
const (
|
||||
FailedEventKeyUndefined FailedEventSearchKey = iota
|
||||
FailedEventKeyViewName
|
||||
FailedEventKeyFailedSequence
|
||||
)
|
||||
|
||||
type failedEventSearchKey FailedEventSearchKey
|
||||
|
||||
func (key failedEventSearchKey) ToColumnName() string {
|
||||
switch FailedEventSearchKey(key) {
|
||||
case FailedEventKeyViewName:
|
||||
return "view_name"
|
||||
case FailedEventKeyFailedSequence:
|
||||
return "failed_sequence"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func FailedEventFromModel(failedEvent *view_model.FailedEvent) *FailedEvent {
|
||||
return &FailedEvent{
|
||||
ViewName: failedEvent.Database + "." + failedEvent.ViewName,
|
||||
FailureCount: failedEvent.FailureCount,
|
||||
FailedSequence: failedEvent.FailedSequence,
|
||||
ErrMsg: failedEvent.ErrMsg,
|
||||
}
|
||||
}
|
||||
func FailedEventToModel(failedEvent *FailedEvent) *view_model.FailedEvent {
|
||||
dbView := strings.Split(failedEvent.ViewName, ".")
|
||||
return &view_model.FailedEvent{
|
||||
Database: dbView[0],
|
||||
ViewName: dbView[1],
|
||||
FailureCount: failedEvent.FailureCount,
|
||||
FailedSequence: failedEvent.FailedSequence,
|
||||
ErrMsg: failedEvent.ErrMsg,
|
||||
}
|
||||
}
|
||||
|
||||
func SaveFailedEvent(db *gorm.DB, table string, failedEvent *FailedEvent) error {
|
||||
save := PrepareSave(table)
|
||||
err := save(db, failedEvent)
|
||||
|
||||
if err != nil {
|
||||
return errors.ThrowInternal(err, "VIEW-5kOhP", "unable to updated failed events")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func RemoveFailedEvent(db *gorm.DB, table string, failedEvent *FailedEvent) error {
|
||||
delete := PrepareDeleteByKeys(table,
|
||||
Key{Key: failedEventSearchKey(FailedEventKeyViewName), Value: failedEvent.ViewName},
|
||||
Key{Key: failedEventSearchKey(FailedEventKeyFailedSequence), Value: failedEvent.FailedSequence},
|
||||
)
|
||||
return delete(db)
|
||||
}
|
||||
|
||||
func LatestFailedEvent(db *gorm.DB, table, viewName string, sequence uint64) (*FailedEvent, error) {
|
||||
failedEvent := new(FailedEvent)
|
||||
queries := []SearchQuery{
|
||||
FailedEventSearchQuery{Key: FailedEventKeyViewName, Method: model.SearchMethodEqualsIgnoreCase, Value: viewName},
|
||||
FailedEventSearchQuery{Key: FailedEventKeyFailedSequence, Method: model.SearchMethodEquals, Value: sequence},
|
||||
}
|
||||
query := PrepareGetByQuery(table, queries...)
|
||||
err := query(db, failedEvent)
|
||||
|
||||
if err == nil && failedEvent.ViewName != "" {
|
||||
return failedEvent, nil
|
||||
}
|
||||
|
||||
if errors.IsNotFound(err) {
|
||||
return &FailedEvent{
|
||||
ViewName: viewName,
|
||||
FailedSequence: sequence,
|
||||
FailureCount: 0,
|
||||
}, nil
|
||||
}
|
||||
return nil, errors.ThrowInternalf(err, "VIEW-9LyCB", "unable to get failed events of %s", viewName)
|
||||
|
||||
}
|
||||
|
||||
func AllFailedEvents(db *gorm.DB, table string) ([]*FailedEvent, error) {
|
||||
failedEvents := make([]*FailedEvent, 0)
|
||||
query := PrepareSearchQuery(table, GeneralSearchRequest{})
|
||||
_, err := query(db, &failedEvents)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return failedEvents, nil
|
||||
}
|
53
internal/view/repository/general_query.go
Normal file
53
internal/view/repository/general_query.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
global_model "github.com/caos/zitadel/internal/model"
|
||||
"github.com/caos/zitadel/internal/view/model"
|
||||
)
|
||||
|
||||
type GeneralSearchRequest model.GeneralSearchRequest
|
||||
type GeneralSearchQuery model.GeneralSearchQuery
|
||||
type GeneralSearchKey model.GeneralSearchKey
|
||||
|
||||
func (req GeneralSearchRequest) GetLimit() uint64 {
|
||||
return req.Limit
|
||||
}
|
||||
|
||||
func (req GeneralSearchRequest) GetOffset() uint64 {
|
||||
return req.Offset
|
||||
}
|
||||
|
||||
func (req GeneralSearchRequest) GetSortingColumn() ColumnKey {
|
||||
if req.SortingColumn == model.GeneralSearchKeyUnspecified {
|
||||
return nil
|
||||
}
|
||||
return GeneralSearchKey(req.SortingColumn)
|
||||
}
|
||||
|
||||
func (req GeneralSearchRequest) GetAsc() bool {
|
||||
return req.Asc
|
||||
}
|
||||
|
||||
func (req GeneralSearchRequest) GetQueries() []SearchQuery {
|
||||
result := make([]SearchQuery, len(req.Queries))
|
||||
for i, q := range req.Queries {
|
||||
result[i] = GeneralSearchQuery{Key: q.Key, Value: q.Value, Method: q.Method}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (req GeneralSearchQuery) GetKey() ColumnKey {
|
||||
return GeneralSearchKey(req.Key)
|
||||
}
|
||||
|
||||
func (req GeneralSearchQuery) GetMethod() global_model.SearchMethod {
|
||||
return req.Method
|
||||
}
|
||||
|
||||
func (req GeneralSearchQuery) GetValue() interface{} {
|
||||
return req.Value
|
||||
}
|
||||
|
||||
func (key GeneralSearchKey) ToColumnName() string {
|
||||
return ""
|
||||
}
|
118
internal/view/repository/query.go
Normal file
118
internal/view/repository/query.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/model"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
type SearchRequest interface {
|
||||
GetLimit() uint64
|
||||
GetOffset() uint64
|
||||
GetSortingColumn() ColumnKey
|
||||
GetAsc() bool
|
||||
GetQueries() []SearchQuery
|
||||
}
|
||||
|
||||
type SearchQuery interface {
|
||||
GetKey() ColumnKey
|
||||
GetMethod() model.SearchMethod
|
||||
GetValue() interface{}
|
||||
}
|
||||
|
||||
type ColumnKey interface {
|
||||
ToColumnName() string
|
||||
}
|
||||
|
||||
func PrepareSearchQuery(table string, request SearchRequest) func(db *gorm.DB, res interface{}) (int, error) {
|
||||
return func(db *gorm.DB, res interface{}) (int, error) {
|
||||
count := 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, caos_errs.ThrowInvalidArgument(err, "VIEW-KaGue", "query is invalid")
|
||||
}
|
||||
}
|
||||
|
||||
query = query.Count(&count)
|
||||
if request.GetLimit() != 0 {
|
||||
query = query.Limit(request.GetLimit())
|
||||
}
|
||||
query = query.Offset(request.GetOffset())
|
||||
err := query.Find(res).Error
|
||||
if err != nil {
|
||||
return count, caos_errs.ThrowInternal(err, "VIEW-muSDK", "unable to find result")
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
}
|
||||
|
||||
func SetQuery(query *gorm.DB, key ColumnKey, value interface{}, method model.SearchMethod) (*gorm.DB, error) {
|
||||
column := key.ToColumnName()
|
||||
if column == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-7dz3w", "Column name missing")
|
||||
}
|
||||
|
||||
switch method {
|
||||
case model.SearchMethodEquals:
|
||||
query = query.Where(""+column+" = ?", value)
|
||||
case model.SearchMethodEqualsIgnoreCase:
|
||||
valueText, ok := value.(string)
|
||||
if !ok {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-idu8e", "Equal ignore case only possible for strings")
|
||||
}
|
||||
query = query.Where("LOWER("+column+") = LOWER(?)", valueText)
|
||||
case model.SearchMethodStartsWith:
|
||||
valueText, ok := value.(string)
|
||||
if !ok {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-idu8e", "Starts with only possible for strings")
|
||||
}
|
||||
query = query.Where(column+" LIKE ?", valueText+"%")
|
||||
case model.SearchMethodStartsWithIgnoreCase:
|
||||
valueText, ok := value.(string)
|
||||
if !ok {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-eidus", "Starts with ignore case only possible for strings")
|
||||
}
|
||||
query = query.Where("LOWER("+column+") LIKE LOWER(?)", valueText+"%")
|
||||
case model.SearchMethodContains:
|
||||
valueText, ok := value.(string)
|
||||
if !ok {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-3ids", "Contains with only possible for strings")
|
||||
}
|
||||
query = query.Where(column+" LIKE ?", "%"+valueText+"%")
|
||||
case model.SearchMethodContainsIgnoreCase:
|
||||
valueText, ok := value.(string)
|
||||
if !ok {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-eid73", "Contains with ignore case only possible for strings")
|
||||
}
|
||||
query = query.Where("LOWER("+column+") LIKE LOWER(?)", "%"+valueText+"%")
|
||||
case model.SearchMethodNotEquals:
|
||||
query = query.Where(""+column+" <> ?", value)
|
||||
case model.SearchMethodGreaterThan:
|
||||
query = query.Where(column+" > ?", value)
|
||||
case model.SearchMethodLessThan:
|
||||
query = query.Where(column+" < ?", value)
|
||||
case model.SearchMethodIsOneOf:
|
||||
query = query.Where(column+" IN (?)", value)
|
||||
case model.SearchMethodListContains:
|
||||
valueText, ok := value.(string)
|
||||
if !ok {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-Psois", "list contains only possible for strings")
|
||||
}
|
||||
query = query.Where("? <@ "+column, pq.Array([]string{valueText}))
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
return query, nil
|
||||
}
|
156
internal/view/repository/query_test.go
Normal file
156
internal/view/repository/query_test.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/model"
|
||||
"github.com/jinzhu/gorm"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPrepareSearchQuery(t *testing.T) {
|
||||
type args struct {
|
||||
table string
|
||||
searchRequest SearchRequest
|
||||
}
|
||||
type res struct {
|
||||
count int
|
||||
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: model.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: model.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: caos_errs.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()
|
||||
})
|
||||
}
|
||||
}
|
119
internal/view/repository/requests.go
Normal file
119
internal/view/repository/requests.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/jinzhu/gorm"
|
||||
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func PrepareGetByKey(table string, key ColumnKey, id string) func(db *gorm.DB, res interface{}) error {
|
||||
return func(db *gorm.DB, res interface{}) error {
|
||||
err := db.Table(table).
|
||||
Where(fmt.Sprintf("%s = ?", key.ToColumnName()), id).
|
||||
Take(res).
|
||||
Error
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return caos_errs.ThrowNotFound(err, "VIEW-XRI9c", "object not found")
|
||||
}
|
||||
logging.LogWithFields("VIEW-xVShS", "AggregateID", id).WithError(err).Warn("get from view error")
|
||||
return caos_errs.ThrowInternal(err, "VIEW-J92Td", "Errors.Internal")
|
||||
}
|
||||
}
|
||||
|
||||
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 caos_errs.ThrowInvalidArgument(err, "VIEW-KaGue", "query is invalid")
|
||||
}
|
||||
}
|
||||
|
||||
err := query.Take(res).Error
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return caos_errs.ThrowNotFound(err, "VIEW-hodc6", "object not found")
|
||||
}
|
||||
logging.LogWithFields("VIEW-Mg6la", "table ", table).WithError(err).Warn("get from cache error")
|
||||
return caos_errs.ThrowInternal(err, "VIEW-qJBg9", "cache error")
|
||||
}
|
||||
}
|
||||
|
||||
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 caos_errs.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 caos_errs.ThrowInternal(err, "VIEW-die73", "could not delete 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 caos_errs.ThrowInternal(err, "VIEW-die73", "could not delete object")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func PrepareDeleteByObject(table string, object interface{}) func(db *gorm.DB) error {
|
||||
return func(db *gorm.DB) error {
|
||||
err := db.Table(table).
|
||||
Delete(object).
|
||||
Error
|
||||
if err != nil {
|
||||
return caos_errs.ThrowInternal(err, "VIEW-lso9w", "could not delete object")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func PrepareTruncate(table string) func(db *gorm.DB) error {
|
||||
return func(db *gorm.DB) error {
|
||||
err := db.
|
||||
Exec("TRUNCATE " + table).
|
||||
Error
|
||||
if err != nil {
|
||||
return caos_errs.ThrowInternal(err, "VIEW-lso9w", "could not truncate table")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
566
internal/view/repository/requests_test.go
Normal file
566
internal/view/repository/requests_test.go
Normal file
@@ -0,0 +1,566 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/model"
|
||||
"github.com/jinzhu/gorm"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPrepareGetByKey(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
|
||||
}{
|
||||
{
|
||||
"ok",
|
||||
mockDB(t).
|
||||
expectGetByID("TESTTABLE", "test", "VALUE"),
|
||||
args{
|
||||
table: "TESTTABLE",
|
||||
key: TestSearchKey_TEST,
|
||||
value: "VALUE",
|
||||
},
|
||||
res{
|
||||
result: Test{ID: "VALUE"},
|
||||
wantErr: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
"not found",
|
||||
mockDB(t).
|
||||
expectGetByIDErr("TESTTABLE", "test", "VALUE", gorm.ErrRecordNotFound),
|
||||
args{
|
||||
table: "TESTTABLE",
|
||||
key: TestSearchKey_TEST,
|
||||
value: "VALUE",
|
||||
},
|
||||
res{
|
||||
result: Test{ID: "VALUE"},
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
"db err",
|
||||
mockDB(t).
|
||||
expectGetByIDErr("TESTTABLE", "test", "VALUE", gorm.ErrUnaddressable),
|
||||
args{
|
||||
table: "TESTTABLE",
|
||||
key: TestSearchKey_TEST,
|
||||
value: "VALUE",
|
||||
},
|
||||
res{
|
||||
result: Test{ID: "VALUE"},
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsInternal,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
res := &Test{}
|
||||
getByID := PrepareGetByKey(tt.args.table, tt.args.key, tt.args.value)
|
||||
err := getByID(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(); err != nil {
|
||||
t.Errorf("there were unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
tt.db.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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: model.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: model.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: model.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: model.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: model.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: model.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: model.SearchMethodContainsIgnoreCase, value: "VALUE"},
|
||||
},
|
||||
res{
|
||||
result: Test{ID: "VALUE"},
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
"search expect internal err",
|
||||
mockDB(t).
|
||||
expectGetByQueryErr("TESTTABLE", "test", "LIKE", "%VALUE%", gorm.ErrUnaddressable),
|
||||
args{
|
||||
table: "TESTTABLE",
|
||||
searchQuery: TestSearchQuery{key: TestSearchKey_TEST, method: model.SearchMethodContainsIgnoreCase, value: "VALUE"},
|
||||
},
|
||||
res{
|
||||
result: Test{ID: "VALUE"},
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsInternal,
|
||||
},
|
||||
},
|
||||
{
|
||||
"search with invalid column",
|
||||
mockDB(t).
|
||||
expectGetByQuery("TESTTABLE", "", "=", "VALUE"),
|
||||
args{
|
||||
table: "TESTTABLE",
|
||||
searchQuery: TestSearchQuery{key: TestSearchKey_UNDEFINED, method: model.SearchMethodEqualsIgnoreCase, value: "VALUE"},
|
||||
},
|
||||
res{
|
||||
result: Test{ID: "VALUE"},
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.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: caos_errs.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: caos_errs.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: caos_errs.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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareDeleteByObject(t *testing.T) {
|
||||
type args struct {
|
||||
table string
|
||||
object interface{}
|
||||
}
|
||||
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).
|
||||
expectRemoveByObject("TESTTABLE", Test{ID: "VALUE", Test: "TEST"}).
|
||||
expectCommit(nil),
|
||||
args{
|
||||
table: "TESTTABLE",
|
||||
object: &Test{ID: "VALUE", Test: "TEST"},
|
||||
},
|
||||
res{
|
||||
result: Test{ID: "VALUE"},
|
||||
wantErr: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
"delete multiple PK",
|
||||
mockDB(t).
|
||||
expectBegin(nil).
|
||||
expectRemoveByObjectMultiplePKs("TESTTABLE", TestMultiplePK{TestID: "TESTID", HodorID: "HODORID", Test: "TEST"}).
|
||||
expectCommit(nil),
|
||||
args{
|
||||
table: "TESTTABLE",
|
||||
object: &TestMultiplePK{TestID: "TESTID", HodorID: "HODORID", Test: "TEST"},
|
||||
},
|
||||
res{
|
||||
wantErr: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
"db error",
|
||||
mockDB(t).
|
||||
expectBegin(nil).
|
||||
expectRemoveErr("TESTTABLE", "id", "VALUE", gorm.ErrUnaddressable).
|
||||
expectCommit(nil),
|
||||
args{
|
||||
table: "TESTTABLE",
|
||||
object: &Test{ID: "VALUE", Test: "TEST"},
|
||||
},
|
||||
res{
|
||||
result: Test{ID: "VALUE"},
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsInternal,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
getDelete := PrepareDeleteByObject(tt.args.table, tt.args.object)
|
||||
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()
|
||||
})
|
||||
}
|
||||
}
|
88
internal/view/repository/sequence.go
Normal file
88
internal/view/repository/sequence.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/view/model"
|
||||
"github.com/jinzhu/gorm"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type actualSequece struct {
|
||||
ActualSequence uint64 `gorm:"column:current_sequence"`
|
||||
}
|
||||
|
||||
type CurrentSequence struct {
|
||||
ViewName string `gorm:"column:view_name;primary_key"`
|
||||
CurrentSequence uint64 `gorm:"column:current_sequence"`
|
||||
}
|
||||
|
||||
type SequenceSearchKey int32
|
||||
|
||||
const (
|
||||
SequenceSearchKeyUndefined SequenceSearchKey = iota
|
||||
SequenceSearchKeyViewName
|
||||
)
|
||||
|
||||
type sequenceSearchKey SequenceSearchKey
|
||||
|
||||
func (key sequenceSearchKey) ToColumnName() string {
|
||||
switch SequenceSearchKey(key) {
|
||||
case SequenceSearchKeyViewName:
|
||||
return "view_name"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func CurrentSequenceToModel(sequence *CurrentSequence) *model.View {
|
||||
dbView := strings.Split(sequence.ViewName, ".")
|
||||
return &model.View{
|
||||
Database: dbView[0],
|
||||
ViewName: dbView[1],
|
||||
CurrentSequence: sequence.CurrentSequence,
|
||||
}
|
||||
}
|
||||
|
||||
func SaveCurrentSequence(db *gorm.DB, table, viewName string, sequence uint64) error {
|
||||
save := PrepareSave(table)
|
||||
err := save(db, &CurrentSequence{viewName, sequence})
|
||||
|
||||
if err != nil {
|
||||
return caos_errs.ThrowInternal(err, "VIEW-5kOhP", "unable to updated processed sequence")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func LatestSequence(db *gorm.DB, table, viewName string) (uint64, error) {
|
||||
sequence := new(actualSequece)
|
||||
query := PrepareGetByKey(table, sequenceSearchKey(SequenceSearchKeyViewName), viewName)
|
||||
err := query(db, sequence)
|
||||
|
||||
if err == nil {
|
||||
return sequence.ActualSequence, nil
|
||||
}
|
||||
|
||||
if caos_errs.IsNotFound(err) {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, caos_errs.ThrowInternalf(err, "VIEW-9LyCB", "unable to get latest sequence of %s", viewName)
|
||||
}
|
||||
|
||||
func AllCurrentSequences(db *gorm.DB, table string) ([]*CurrentSequence, error) {
|
||||
sequences := make([]*CurrentSequence, 0)
|
||||
query := PrepareSearchQuery(table, GeneralSearchRequest{})
|
||||
_, err := query(db, &sequences)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sequences, nil
|
||||
}
|
||||
|
||||
func ClearView(db *gorm.DB, truncateView, sequenceTable string) error {
|
||||
truncate := PrepareTruncate(truncateView)
|
||||
err := truncate(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return SaveCurrentSequence(db, sequenceTable, truncateView, 0)
|
||||
}
|
Reference in New Issue
Block a user