mirror of
https://github.com/zitadel/zitadel.git
synced 2025-01-05 14:37:45 +00:00
feat: View (#27)
* feat: eventstore repository * fix: remove gorm * version * feat: pkg * feat: eventstore without eventstore-lib * rename files * gnueg * fix: add object * fix: global model * feat: add global view functions * feat(eventstore): sdk * fix(eventstore): search query * fix(eventstore): rename app to eventstore * delete empty test * remove unused func * merge master * fix(eventstore): tests * fix(models): delete unused struct * feat(eventstore): implemented push events * feat(eventstore): overwrite context data * feat(types): SQL-config * feat(eventstore): options to overwrite editor * fix: use global sql config * fix: changes from mr * fix: add some tests * Update internal/eventstore/models/field.go Co-Authored-By: livio-a <livio.a@gmail.com> * fix(eventstore): code quality * fix: try tests * fix: query tests * fix: use prepare funcs * fix: go mod * fix: go tests * fix: better error func testing * fix: merge master * fix: changes for mr * fix: change value to interface * fix: searchmethods * fix: check if value is string on equal ignore case Co-authored-by: adlerhurst <silvan.reusser@gmail.com> Co-authored-by: livio-a <livio.a@gmail.com>
This commit is contained in:
parent
27b6aee310
commit
34e2f1bcdd
1
go.mod
1
go.mod
@ -26,6 +26,7 @@ require (
|
||||
github.com/huandu/xstrings v1.3.0 // indirect
|
||||
github.com/imdario/mergo v0.3.9 // indirect
|
||||
github.com/jackc/pgconn v1.5.0 // indirect
|
||||
github.com/jinzhu/gorm v1.9.12
|
||||
github.com/lib/pq v1.3.0
|
||||
github.com/mitchellh/copystructure v1.0.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.1 // indirect
|
||||
|
13
go.sum
13
go.sum
@ -72,6 +72,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ=
|
||||
github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
@ -79,6 +80,7 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrp
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.3.0 h1:Y2J74o+yAfcD8jpqtkLnUqRo+yshLr4eR1WPYGX0cic=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.3.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
@ -86,6 +88,7 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
@ -93,6 +96,7 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@ -185,6 +189,11 @@ github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9
|
||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q=
|
||||
github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
|
||||
@ -205,12 +214,14 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
|
||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
|
||||
@ -266,6 +277,7 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@ -275,6 +287,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8 h1:fpnn/HnJONpIu6hkXi1u/7rR0NzilgWr4T0JmWkEitk=
|
||||
|
@ -1,5 +1,6 @@
|
||||
package model
|
||||
|
||||
//Deprecated: Enum is useless, better use normal enums, because we rarely need string value
|
||||
type Enum interface {
|
||||
String() string
|
||||
}
|
||||
|
@ -1,26 +1,20 @@
|
||||
package model
|
||||
|
||||
// code below could be generated
|
||||
type SearchMethod Enum
|
||||
|
||||
var methods = []string{"Equals", "StartsWith", "Contains"}
|
||||
|
||||
type method int32
|
||||
|
||||
func (s method) String() string {
|
||||
return methods[s]
|
||||
}
|
||||
type SearchMethod int32
|
||||
|
||||
const (
|
||||
Equals method = iota
|
||||
StartsWith
|
||||
Contains
|
||||
SEARCHMETHOD_EQUALS SearchMethod = iota
|
||||
SEARCHMETHOD_STARTS_WITH
|
||||
SEARCHMETHOD_CONTAINS
|
||||
SEARCHMETHOD_EQUALS_IGNORE_CASE
|
||||
SEARCHMETHOD_STARTS_WITH_IGNORE_CASE
|
||||
SEARCHMETHOD_CONTAINS_IGNORE_CASE
|
||||
)
|
||||
|
||||
func SearchMethodToInt(s SearchMethod) int32 {
|
||||
return int32(s.(method))
|
||||
return int32(s)
|
||||
}
|
||||
|
||||
func SearchMethodFromInt(index int32) SearchMethod {
|
||||
return method(index)
|
||||
return SearchMethod(index)
|
||||
}
|
||||
|
9
internal/view/config.go
Normal file
9
internal/view/config.go
Normal file
@ -0,0 +1,9 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/config/types"
|
||||
)
|
||||
|
||||
type ViewConfig struct {
|
||||
SQL *types.SQL
|
||||
}
|
353
internal/view/db_mock_test.go
Normal file
353
internal/view/db_mock_test.go
Normal file
@ -0,0 +1,353 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/caos/zitadel/internal/model"
|
||||
"github.com/jinzhu/gorm"
|
||||
"testing"
|
||||
)
|
||||
|
||||
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\)`
|
||||
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:id;primary_key"`
|
||||
Test string `json:"test" 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, "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) 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) 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
|
||||
}
|
91
internal/view/failed_events.go
Normal file
91
internal/view/failed_events.go
Normal file
@ -0,0 +1,91 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/model"
|
||||
"github.com/jinzhu/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
errViewNameKey = "view_name"
|
||||
errFailedSeqKey = "failed_sequence"
|
||||
)
|
||||
|
||||
type FailedEvent struct {
|
||||
ViewName string `gorm:"column:view_name;primary_key"`
|
||||
FailedSequnce uint64 `gorm:"column:failed_sequence;primary_key`
|
||||
FailureCount uint64 `gorm:"column:failure_count`
|
||||
ErrMsg uint64 `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 (
|
||||
FAILEDEVENTKEY_UNDEFINED FailedEventSearchKey = iota
|
||||
FAILEDEVENTKEY_VIEW_NAME
|
||||
FAILEDEVENTKEY_FAILED_SEQUENCE
|
||||
)
|
||||
|
||||
type failedEventSearchKey FailedEventSearchKey
|
||||
|
||||
func (key failedEventSearchKey) ToColumnName() string {
|
||||
switch FailedEventSearchKey(key) {
|
||||
case FAILEDEVENTKEY_VIEW_NAME:
|
||||
return "view_name"
|
||||
case FAILEDEVENTKEY_FAILED_SEQUENCE:
|
||||
return "failed_sequence"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
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 LatestFailedEvent(db *gorm.DB, table, viewName string, sequence uint64) (*FailedEvent, error) {
|
||||
failedEvent := new(FailedEvent)
|
||||
queries := []SearchQuery{
|
||||
FailedEventSearchQuery{Key: FAILEDEVENTKEY_VIEW_NAME, Method: model.SEARCHMETHOD_EQUALS_IGNORE_CASE, Value: viewName},
|
||||
FailedEventSearchQuery{Key: FAILEDEVENTKEY_FAILED_SEQUENCE, Method: model.SEARCHMETHOD_EQUALS_IGNORE_CASE, Value: sequence},
|
||||
}
|
||||
query := PrepareGetByQuery(table, queries...)
|
||||
err := query(db, sequence)
|
||||
|
||||
if err == nil {
|
||||
return failedEvent, nil
|
||||
}
|
||||
|
||||
if gorm.IsRecordNotFoundError(err) {
|
||||
failedEvent.ViewName = viewName
|
||||
failedEvent.FailedSequnce = sequence
|
||||
failedEvent.FailureCount = 0
|
||||
return failedEvent, nil
|
||||
}
|
||||
return nil, errors.ThrowInternalf(err, "VIEW-9LyCB", "unable to get failed events of %s", viewName)
|
||||
|
||||
}
|
104
internal/view/query.go
Normal file
104
internal/view/query.go
Normal file
@ -0,0 +1,104 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/model"
|
||||
"github.com/jinzhu/gorm"
|
||||
)
|
||||
|
||||
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.SEARCHMETHOD_EQUALS:
|
||||
query = query.Where(""+column+" = ?", value)
|
||||
case model.SEARCHMETHOD_EQUALS_IGNORE_CASE:
|
||||
valueText, ok := value.(string)
|
||||
if !ok {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-idu8e", "Starts with only possible for strings")
|
||||
}
|
||||
query = query.Where("LOWER("+column+") = LOWER(?)", valueText)
|
||||
case model.SEARCHMETHOD_STARTS_WITH:
|
||||
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.SEARCHMETHOD_STARTS_WITH_IGNORE_CASE:
|
||||
valueText, ok := value.(string)
|
||||
if !ok {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-eidus", "Starts with only possible for strings")
|
||||
}
|
||||
query = query.Where("LOWER("+column+") LIKE LOWER(?)", valueText+"%")
|
||||
case model.SEARCHMETHOD_CONTAINS:
|
||||
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.SEARCHMETHOD_CONTAINS_IGNORE_CASE:
|
||||
valueText, ok := value.(string)
|
||||
if !ok {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-eid73", "Contains with only possible for strings")
|
||||
}
|
||||
query = query.Where("LOWER("+column+") LIKE LOWER(?)", "%"+valueText+"%")
|
||||
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
return query, nil
|
||||
}
|
156
internal/view/query_test.go
Normal file
156
internal/view/query_test.go
Normal file
@ -0,0 +1,156 @@
|
||||
package view
|
||||
|
||||
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(), "=", "ID", 2, 2),
|
||||
args{
|
||||
table: "TESTTABLE",
|
||||
searchRequest: TestSearchRequest{queries: []SearchQuery{TestSearchQuery{key: TestSearchKey_ID, method: model.SEARCHMETHOD_EQUALS_IGNORE_CASE, value: "ID"}}},
|
||||
},
|
||||
res{
|
||||
count: 2,
|
||||
wantErr: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
"search with all params",
|
||||
mockDB(t).
|
||||
expectGetSearchRequestWithAllParams("TESTTABLE", TestSearchKey_ID.ToColumnName(), "=", "ID", "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.SEARCHMETHOD_EQUALS_IGNORE_CASE, value: "ID"}}},
|
||||
},
|
||||
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()
|
||||
})
|
||||
}
|
||||
}
|
72
internal/view/requests.go
Normal file
72
internal/view/requests.go
Normal file
@ -0,0 +1,72 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/caos/logging"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/jinzhu/gorm"
|
||||
)
|
||||
|
||||
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", "ID", id).WithError(err).Warn("get from view error")
|
||||
return caos_errs.ThrowInternal(err, "VIEW-J92Td", "view error")
|
||||
}
|
||||
}
|
||||
|
||||
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 PrepareDelete(table string, key ColumnKey, id string) 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
|
||||
}
|
||||
}
|
392
internal/view/requests_test.go
Normal file
392
internal/view/requests_test.go
Normal file
@ -0,0 +1,392 @@
|
||||
package view
|
||||
|
||||
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.SEARCHMETHOD_EQUALS_IGNORE_CASE, 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.SEARCHMETHOD_EQUALS, 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.SEARCHMETHOD_STARTS_WITH_IGNORE_CASE, 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.SEARCHMETHOD_STARTS_WITH, 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.SEARCHMETHOD_CONTAINS_IGNORE_CASE, 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.SEARCHMETHOD_CONTAINS, 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.SEARCHMETHOD_CONTAINS_IGNORE_CASE, 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.SEARCHMETHOD_CONTAINS_IGNORE_CASE, 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.SEARCHMETHOD_EQUALS_IGNORE_CASE, 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: "ID", Test: "VALUE"}).
|
||||
expectCommit(nil),
|
||||
args{
|
||||
table: "TESTTABLE",
|
||||
object: &Test{ID: "ID", Test: "VALUE"},
|
||||
},
|
||||
res{
|
||||
result: Test{ID: "VALUE"},
|
||||
wantErr: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
"db error",
|
||||
mockDB(t).
|
||||
expectBegin(nil).
|
||||
expectSaveErr("TESTTABLE", Test{ID: "ID", Test: "VALUE"}, gorm.ErrUnaddressable).
|
||||
expectCommit(nil),
|
||||
args{
|
||||
table: "TESTTABLE",
|
||||
object: &Test{ID: "ID", 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 := PrepareDelete(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()
|
||||
})
|
||||
}
|
||||
}
|
58
internal/view/sequence.go
Normal file
58
internal/view/sequence.go
Normal file
@ -0,0 +1,58 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/jinzhu/gorm"
|
||||
)
|
||||
|
||||
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 (
|
||||
SEQUENCESEARCHKEY_UNDEFINED SequenceSearchKey = iota
|
||||
SEQUENCESEARCHKEY_VIEW_NAME
|
||||
)
|
||||
|
||||
type sequenceSearchKey SequenceSearchKey
|
||||
|
||||
func (key sequenceSearchKey) ToColumnName() string {
|
||||
switch SequenceSearchKey(key) {
|
||||
case SEQUENCESEARCHKEY_VIEW_NAME:
|
||||
return "view_name"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func SaveCurrentSequence(db *gorm.DB, table, viewName string, sequence uint64) error {
|
||||
save := PrepareSave(table)
|
||||
err := save(db, ¤tSequence{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(SEQUENCESEARCHKEY_VIEW_NAME), viewName)
|
||||
err := query(db, sequence)
|
||||
|
||||
if err == nil {
|
||||
return sequence.ActualSequence, nil
|
||||
}
|
||||
|
||||
if gorm.IsRecordNotFoundError(err) {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, caos_errs.ThrowInternalf(err, "VIEW-9LyCB", "unable to get latest sequence of %s", viewName)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user