mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 19:17:32 +00:00
feat(eventstore): increase parallel write capabilities (#5940)
This implementation increases parallel write capabilities of the eventstore. Please have a look at the technical advisories: [05](https://zitadel.com/docs/support/advisory/a10005) and [06](https://zitadel.com/docs/support/advisory/a10006). The implementation of eventstore.push is rewritten and stored events are migrated to a new table `eventstore.events2`. If you are using cockroach: make sure that the database user of ZITADEL has `VIEWACTIVITY` grant. This is used to query events.
This commit is contained in:
@@ -1,99 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
type AggregateType string
|
||||
|
||||
func (at AggregateType) String() string {
|
||||
return string(at)
|
||||
}
|
||||
|
||||
type Aggregates []*Aggregate
|
||||
|
||||
type Aggregate struct {
|
||||
ID string
|
||||
typ AggregateType
|
||||
PreviousSequence uint64
|
||||
version Version
|
||||
|
||||
editorService string
|
||||
editorUser string
|
||||
resourceOwner string
|
||||
instanceID string
|
||||
Events []*Event
|
||||
Precondition *precondition
|
||||
}
|
||||
|
||||
func (a *Aggregate) Type() AggregateType {
|
||||
return a.typ
|
||||
}
|
||||
|
||||
type precondition struct {
|
||||
Query *SearchQuery
|
||||
Validation func(...*Event) error
|
||||
}
|
||||
|
||||
func (a *Aggregate) AppendEvent(typ EventType, payload interface{}) (*Aggregate, error) {
|
||||
if string(typ) == "" {
|
||||
return a, errors.ThrowInvalidArgument(nil, "MODEL-TGoCb", "no event type")
|
||||
}
|
||||
data, err := eventData(payload)
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
|
||||
e := &Event{
|
||||
CreationDate: time.Now(),
|
||||
Data: data,
|
||||
Type: typ,
|
||||
AggregateID: a.ID,
|
||||
AggregateType: a.typ,
|
||||
AggregateVersion: a.version,
|
||||
EditorService: a.editorService,
|
||||
EditorUser: a.editorUser,
|
||||
ResourceOwner: a.resourceOwner,
|
||||
InstanceID: a.instanceID,
|
||||
}
|
||||
|
||||
a.Events = append(a.Events, e)
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (a *Aggregate) SetPrecondition(query *SearchQuery, validateFunc func(...*Event) error) *Aggregate {
|
||||
a.Precondition = &precondition{Query: query, Validation: validateFunc}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Aggregate) Validate() error {
|
||||
if a == nil {
|
||||
return errors.ThrowPreconditionFailed(nil, "MODEL-yi5AC", "aggregate is nil")
|
||||
}
|
||||
if a.ID == "" {
|
||||
return errors.ThrowPreconditionFailed(nil, "MODEL-FSjKV", "id not set")
|
||||
}
|
||||
if string(a.typ) == "" {
|
||||
return errors.ThrowPreconditionFailed(nil, "MODEL-aj4t2", "type not set")
|
||||
}
|
||||
if err := a.version.Validate(); err != nil {
|
||||
return errors.ThrowPreconditionFailed(err, "MODEL-PupjX", "invalid version")
|
||||
}
|
||||
|
||||
if a.editorService == "" {
|
||||
return errors.ThrowPreconditionFailed(nil, "MODEL-clYbY", "editor service not set")
|
||||
}
|
||||
if a.editorUser == "" {
|
||||
return errors.ThrowPreconditionFailed(nil, "MODEL-Xcssi", "editor user not set")
|
||||
}
|
||||
if a.resourceOwner == "" {
|
||||
return errors.ThrowPreconditionFailed(nil, "MODEL-eBYUW", "resource owner not set")
|
||||
}
|
||||
if a.Precondition != nil && (a.Precondition.Query == nil || a.Precondition.Validation == nil) {
|
||||
return errors.ThrowPreconditionFailed(nil, "MODEL-EEUvA", "invalid precondition")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@@ -1,59 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
)
|
||||
|
||||
type AggregateCreator struct {
|
||||
serviceName string
|
||||
}
|
||||
|
||||
func NewAggregateCreator(serviceName string) *AggregateCreator {
|
||||
return &AggregateCreator{serviceName: serviceName}
|
||||
}
|
||||
|
||||
type option func(*Aggregate)
|
||||
|
||||
func (c *AggregateCreator) NewAggregate(ctx context.Context, id string, typ AggregateType, version Version, previousSequence uint64, opts ...option) (*Aggregate, error) {
|
||||
ctxData := authz.GetCtxData(ctx)
|
||||
instance := authz.GetInstance(ctx)
|
||||
editorUser := ctxData.UserID
|
||||
resourceOwner := ctxData.OrgID
|
||||
instanceID := instance.InstanceID()
|
||||
|
||||
aggregate := &Aggregate{
|
||||
ID: id,
|
||||
typ: typ,
|
||||
PreviousSequence: previousSequence,
|
||||
version: version,
|
||||
Events: make([]*Event, 0, 2),
|
||||
editorService: c.serviceName,
|
||||
editorUser: editorUser,
|
||||
resourceOwner: resourceOwner,
|
||||
instanceID: instanceID,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(aggregate)
|
||||
}
|
||||
|
||||
if err := aggregate.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return aggregate, nil
|
||||
}
|
||||
|
||||
func OverwriteEditorUser(userID string) func(*Aggregate) {
|
||||
return func(a *Aggregate) {
|
||||
a.editorUser = userID
|
||||
}
|
||||
}
|
||||
|
||||
func OverwriteResourceOwner(resourceOwner string) func(*Aggregate) {
|
||||
return func(a *Aggregate) {
|
||||
a.resourceOwner = resourceOwner
|
||||
}
|
||||
}
|
@@ -1,118 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAggregateCreator_NewAggregate(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
id string
|
||||
typ AggregateType
|
||||
version Version
|
||||
opts []option
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
creator *AggregateCreator
|
||||
args args
|
||||
want *Aggregate
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no ctxdata and no options",
|
||||
creator: &AggregateCreator{serviceName: "admin"},
|
||||
wantErr: true,
|
||||
want: nil,
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
id: "hodor",
|
||||
typ: "user",
|
||||
version: "v1.0.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no id error",
|
||||
creator: &AggregateCreator{serviceName: "admin"},
|
||||
wantErr: true,
|
||||
want: nil,
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
typ: "user",
|
||||
version: "v1.0.0",
|
||||
opts: []option{
|
||||
OverwriteEditorUser("hodor"),
|
||||
OverwriteResourceOwner("org"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no type error",
|
||||
creator: &AggregateCreator{serviceName: "admin"},
|
||||
wantErr: true,
|
||||
want: nil,
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
id: "hodor",
|
||||
version: "v1.0.0",
|
||||
opts: []option{
|
||||
OverwriteEditorUser("hodor"),
|
||||
OverwriteResourceOwner("org"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid version error",
|
||||
creator: &AggregateCreator{serviceName: "admin"},
|
||||
wantErr: true,
|
||||
want: nil,
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
id: "hodor",
|
||||
typ: "user",
|
||||
opts: []option{
|
||||
OverwriteEditorUser("hodor"),
|
||||
OverwriteResourceOwner("org"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create ok",
|
||||
creator: &AggregateCreator{serviceName: "admin"},
|
||||
wantErr: false,
|
||||
want: &Aggregate{
|
||||
ID: "hodor",
|
||||
Events: make([]*Event, 0, 2),
|
||||
typ: "user",
|
||||
version: "v1.0.0",
|
||||
editorService: "admin",
|
||||
editorUser: "hodor",
|
||||
resourceOwner: "org",
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
id: "hodor",
|
||||
typ: "user",
|
||||
version: "v1.0.0",
|
||||
opts: []option{
|
||||
OverwriteEditorUser("hodor"),
|
||||
OverwriteResourceOwner("org"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.creator.NewAggregate(tt.args.ctx, tt.args.id, tt.args.typ, tt.args.version, 0, tt.args.opts...)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("AggregateCreator.NewAggregate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("AggregateCreator.NewAggregate() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -1,310 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func TestAggregate_AppendEvent(t *testing.T) {
|
||||
type fields struct {
|
||||
aggregate *Aggregate
|
||||
}
|
||||
type args struct {
|
||||
typ EventType
|
||||
payload interface{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want *Aggregate
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no event type error",
|
||||
fields: fields{aggregate: &Aggregate{}},
|
||||
args: args{},
|
||||
want: &Aggregate{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid payload error",
|
||||
fields: fields{aggregate: &Aggregate{}},
|
||||
args: args{typ: "user", payload: 134},
|
||||
want: &Aggregate{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "event added",
|
||||
fields: fields{aggregate: &Aggregate{Events: []*Event{}}},
|
||||
args: args{typ: "user.deactivated"},
|
||||
want: &Aggregate{Events: []*Event{
|
||||
{Type: "user.deactivated"},
|
||||
}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "event added",
|
||||
fields: fields{aggregate: &Aggregate{Events: []*Event{
|
||||
{},
|
||||
}}},
|
||||
args: args{typ: "user.deactivated"},
|
||||
want: &Aggregate{Events: []*Event{
|
||||
{},
|
||||
{Type: "user.deactivated"},
|
||||
}},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.fields.aggregate.AppendEvent(tt.args.typ, tt.args.payload)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Aggregate.AppendEvent() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if len(tt.fields.aggregate.Events) != len(got.Events) {
|
||||
t.Errorf("events len should be %d but was %d", len(tt.fields.aggregate.Events), len(got.Events))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregate_Validate(t *testing.T) {
|
||||
type fields struct {
|
||||
aggregate *Aggregate
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "aggregate nil error",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "aggregate empty error",
|
||||
wantErr: true,
|
||||
fields: fields{aggregate: &Aggregate{}},
|
||||
},
|
||||
{
|
||||
name: "no id error",
|
||||
wantErr: true,
|
||||
fields: fields{aggregate: &Aggregate{
|
||||
typ: "user",
|
||||
version: "v1.0.0",
|
||||
editorService: "svc",
|
||||
editorUser: "hodor",
|
||||
resourceOwner: "org",
|
||||
PreviousSequence: 5,
|
||||
Events: []*Event{
|
||||
{
|
||||
AggregateType: "user",
|
||||
AggregateVersion: "v1.0.0",
|
||||
EditorService: "management",
|
||||
EditorUser: "hodor",
|
||||
ResourceOwner: "org",
|
||||
Type: "born",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "no type error",
|
||||
wantErr: true,
|
||||
fields: fields{aggregate: &Aggregate{
|
||||
ID: "aggID",
|
||||
version: "v1.0.0",
|
||||
editorService: "svc",
|
||||
editorUser: "hodor",
|
||||
resourceOwner: "org",
|
||||
PreviousSequence: 5,
|
||||
Events: []*Event{
|
||||
{
|
||||
AggregateID: "hodor",
|
||||
AggregateVersion: "v1.0.0",
|
||||
EditorService: "management",
|
||||
EditorUser: "hodor",
|
||||
ResourceOwner: "org",
|
||||
Type: "born",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "invalid version error",
|
||||
wantErr: true,
|
||||
fields: fields{aggregate: &Aggregate{
|
||||
ID: "aggID",
|
||||
typ: "user",
|
||||
editorService: "svc",
|
||||
editorUser: "hodor",
|
||||
resourceOwner: "org",
|
||||
PreviousSequence: 5,
|
||||
Events: []*Event{
|
||||
{
|
||||
AggregateID: "hodor",
|
||||
AggregateType: "user",
|
||||
EditorService: "management",
|
||||
EditorUser: "hodor",
|
||||
ResourceOwner: "org",
|
||||
Type: "born",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "no query in precondition error",
|
||||
wantErr: true,
|
||||
fields: fields{aggregate: &Aggregate{
|
||||
ID: "aggID",
|
||||
typ: "user",
|
||||
version: "v1.0.0",
|
||||
editorService: "svc",
|
||||
editorUser: "hodor",
|
||||
resourceOwner: "org",
|
||||
PreviousSequence: 5,
|
||||
Precondition: &precondition{
|
||||
Validation: func(...*Event) error { return nil },
|
||||
},
|
||||
Events: []*Event{
|
||||
{
|
||||
AggregateID: "hodor",
|
||||
AggregateType: "user",
|
||||
AggregateVersion: "v1.0.0",
|
||||
EditorService: "management",
|
||||
EditorUser: "hodor",
|
||||
ResourceOwner: "org",
|
||||
Type: "born",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "no func in precondition error",
|
||||
wantErr: true,
|
||||
fields: fields{aggregate: &Aggregate{
|
||||
ID: "aggID",
|
||||
typ: "user",
|
||||
version: "v1.0.0",
|
||||
editorService: "svc",
|
||||
editorUser: "hodor",
|
||||
resourceOwner: "org",
|
||||
PreviousSequence: 5,
|
||||
Precondition: &precondition{
|
||||
Query: NewSearchQuery().AddQuery().AggregateIDFilter("hodor").SearchQuery(),
|
||||
},
|
||||
Events: []*Event{
|
||||
{
|
||||
AggregateID: "hodor",
|
||||
AggregateType: "user",
|
||||
AggregateVersion: "v1.0.0",
|
||||
EditorService: "management",
|
||||
EditorUser: "hodor",
|
||||
ResourceOwner: "org",
|
||||
Type: "born",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "validation without precondition ok",
|
||||
wantErr: false,
|
||||
fields: fields{aggregate: &Aggregate{
|
||||
ID: "aggID",
|
||||
typ: "user",
|
||||
version: "v1.0.0",
|
||||
editorService: "svc",
|
||||
editorUser: "hodor",
|
||||
resourceOwner: "org",
|
||||
PreviousSequence: 5,
|
||||
Events: []*Event{
|
||||
{
|
||||
AggregateID: "hodor",
|
||||
AggregateType: "user",
|
||||
AggregateVersion: "v1.0.0",
|
||||
EditorService: "management",
|
||||
EditorUser: "hodor",
|
||||
ResourceOwner: "org",
|
||||
Type: "born",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "validation with precondition ok",
|
||||
wantErr: false,
|
||||
fields: fields{aggregate: &Aggregate{
|
||||
ID: "aggID",
|
||||
typ: "user",
|
||||
version: "v1.0.0",
|
||||
editorService: "svc",
|
||||
editorUser: "hodor",
|
||||
resourceOwner: "org",
|
||||
PreviousSequence: 5,
|
||||
Precondition: &precondition{
|
||||
Validation: func(...*Event) error { return nil },
|
||||
Query: NewSearchQuery().AddQuery().AggregateIDFilter("hodor").SearchQuery(),
|
||||
},
|
||||
Events: []*Event{
|
||||
{
|
||||
AggregateID: "hodor",
|
||||
AggregateType: "user",
|
||||
AggregateVersion: "v1.0.0",
|
||||
EditorService: "management",
|
||||
EditorUser: "hodor",
|
||||
ResourceOwner: "org",
|
||||
Type: "born",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.fields.aggregate.Validate()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Aggregate.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if tt.wantErr && !errors.IsPreconditionFailed(err) {
|
||||
t.Errorf("error must extend precondition failed: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregate_SetPrecondition(t *testing.T) {
|
||||
type fields struct {
|
||||
aggregate *Aggregate
|
||||
}
|
||||
type args struct {
|
||||
query *SearchQuery
|
||||
validateFunc func(...*Event) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want *Aggregate
|
||||
}{
|
||||
{
|
||||
name: "set precondition",
|
||||
fields: fields{aggregate: &Aggregate{}},
|
||||
args: args{
|
||||
query: &SearchQuery{},
|
||||
validateFunc: func(...*Event) error { return nil },
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
got := tt.fields.aggregate.SetPrecondition(tt.args.query, tt.args.validateFunc)
|
||||
if got.Precondition == nil {
|
||||
t.Error("precondition must not be nil")
|
||||
t.FailNow()
|
||||
}
|
||||
if got.Precondition.Query == nil {
|
||||
t.Error("query of precondition must not be nil")
|
||||
}
|
||||
if got.Precondition.Validation == nil {
|
||||
t.Error("precondition func must not be nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
type EventType string
|
||||
@@ -14,23 +15,85 @@ func (et EventType) String() string {
|
||||
return string(et)
|
||||
}
|
||||
|
||||
var _ eventstore.Event = (*Event)(nil)
|
||||
|
||||
type Event struct {
|
||||
ID string
|
||||
Sequence uint64
|
||||
Seq uint64
|
||||
Pos float64
|
||||
CreationDate time.Time
|
||||
Type EventType
|
||||
Typ eventstore.EventType
|
||||
PreviousSequence uint64
|
||||
Data []byte
|
||||
|
||||
AggregateID string
|
||||
AggregateType AggregateType
|
||||
AggregateVersion Version
|
||||
EditorService string
|
||||
EditorUser string
|
||||
AggregateType eventstore.AggregateType
|
||||
AggregateVersion eventstore.Version
|
||||
Service string
|
||||
User string
|
||||
ResourceOwner string
|
||||
InstanceID string
|
||||
}
|
||||
|
||||
// Aggregate implements [eventstore.Event]
|
||||
func (e *Event) Aggregate() *eventstore.Aggregate {
|
||||
return &eventstore.Aggregate{
|
||||
ID: e.AggregateID,
|
||||
Type: e.AggregateType,
|
||||
ResourceOwner: e.ResourceOwner,
|
||||
InstanceID: e.InstanceID,
|
||||
// Version: eventstore.Version(e.AggregateVersion),
|
||||
}
|
||||
}
|
||||
|
||||
// CreatedAt implements [eventstore.Event]
|
||||
func (e *Event) CreatedAt() time.Time {
|
||||
return e.CreationDate
|
||||
}
|
||||
|
||||
// DataAsBytes implements [eventstore.Event]
|
||||
func (e *Event) DataAsBytes() []byte {
|
||||
return e.Data
|
||||
}
|
||||
|
||||
// Unmarshal implements [eventstore.Event]
|
||||
func (e *Event) Unmarshal(ptr any) error {
|
||||
if len(e.Data) == 0 {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(e.Data, ptr)
|
||||
}
|
||||
|
||||
// EditorService implements [eventstore.Event]
|
||||
func (e *Event) EditorService() string {
|
||||
return e.Service
|
||||
}
|
||||
|
||||
// Creator implements [eventstore.action]
|
||||
func (e *Event) Creator() string {
|
||||
return e.User
|
||||
}
|
||||
|
||||
// Sequence implements [eventstore.Event]
|
||||
func (e *Event) Sequence() uint64 {
|
||||
return e.Seq
|
||||
}
|
||||
|
||||
// Position implements [eventstore.Event]
|
||||
func (e *Event) Position() float64 {
|
||||
return e.Pos
|
||||
}
|
||||
|
||||
// Type implements [eventstore.action]
|
||||
func (e *Event) Type() eventstore.EventType {
|
||||
return e.Typ
|
||||
}
|
||||
|
||||
// Type implements [eventstore.action]
|
||||
func (e *Event) Revision() uint16 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func eventData(i interface{}) ([]byte, error) {
|
||||
switch v := i.(type) {
|
||||
case []byte:
|
||||
@@ -63,7 +126,7 @@ func (e *Event) Validate() error {
|
||||
if e == nil {
|
||||
return errors.ThrowPreconditionFailed(nil, "MODEL-oEAG4", "event is nil")
|
||||
}
|
||||
if string(e.Type) == "" {
|
||||
if string(e.Typ) == "" {
|
||||
return errors.ThrowPreconditionFailed(nil, "MODEL-R2sB0", "type not defined")
|
||||
}
|
||||
|
||||
@@ -74,13 +137,12 @@ func (e *Event) Validate() error {
|
||||
return errors.ThrowPreconditionFailed(nil, "MODEL-EzdyK", "aggregate type not set")
|
||||
}
|
||||
if err := e.AggregateVersion.Validate(); err != nil {
|
||||
return err
|
||||
return errors.ThrowPreconditionFailed(err, "MODEL-KO71q", "version invalid")
|
||||
}
|
||||
|
||||
if e.EditorService == "" {
|
||||
if e.Service == "" {
|
||||
return errors.ThrowPreconditionFailed(nil, "MODEL-4Yqik", "editor service not set")
|
||||
}
|
||||
if e.EditorUser == "" {
|
||||
if e.User == "" {
|
||||
return errors.ThrowPreconditionFailed(nil, "MODEL-L3NHO", "editor user not set")
|
||||
}
|
||||
if e.ResourceOwner == "" {
|
||||
|
@@ -95,10 +95,10 @@ func TestEvent_Validate(t *testing.T) {
|
||||
fields: fields{event: &Event{
|
||||
AggregateType: "user",
|
||||
AggregateVersion: "v1.0.0",
|
||||
EditorService: "management",
|
||||
EditorUser: "hodor",
|
||||
Service: "management",
|
||||
User: "hodor",
|
||||
ResourceOwner: "org",
|
||||
Type: "born",
|
||||
Typ: "born",
|
||||
}},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -107,10 +107,10 @@ func TestEvent_Validate(t *testing.T) {
|
||||
fields: fields{event: &Event{
|
||||
AggregateID: "hodor",
|
||||
AggregateVersion: "v1.0.0",
|
||||
EditorService: "management",
|
||||
EditorUser: "hodor",
|
||||
Service: "management",
|
||||
User: "hodor",
|
||||
ResourceOwner: "org",
|
||||
Type: "born",
|
||||
Typ: "born",
|
||||
}},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -119,10 +119,10 @@ func TestEvent_Validate(t *testing.T) {
|
||||
fields: fields{event: &Event{
|
||||
AggregateID: "hodor",
|
||||
AggregateType: "user",
|
||||
EditorService: "management",
|
||||
EditorUser: "hodor",
|
||||
Service: "management",
|
||||
User: "hodor",
|
||||
ResourceOwner: "org",
|
||||
Type: "born",
|
||||
Typ: "born",
|
||||
}},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -132,9 +132,9 @@ func TestEvent_Validate(t *testing.T) {
|
||||
AggregateID: "hodor",
|
||||
AggregateType: "user",
|
||||
AggregateVersion: "v1.0.0",
|
||||
EditorUser: "hodor",
|
||||
User: "hodor",
|
||||
ResourceOwner: "org",
|
||||
Type: "born",
|
||||
Typ: "born",
|
||||
}},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -144,9 +144,9 @@ func TestEvent_Validate(t *testing.T) {
|
||||
AggregateID: "hodor",
|
||||
AggregateType: "user",
|
||||
AggregateVersion: "v1.0.0",
|
||||
EditorService: "management",
|
||||
Service: "management",
|
||||
ResourceOwner: "org",
|
||||
Type: "born",
|
||||
Typ: "born",
|
||||
}},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -156,9 +156,9 @@ func TestEvent_Validate(t *testing.T) {
|
||||
AggregateID: "hodor",
|
||||
AggregateType: "user",
|
||||
AggregateVersion: "v1.0.0",
|
||||
EditorService: "management",
|
||||
EditorUser: "hodor",
|
||||
Type: "born",
|
||||
Service: "management",
|
||||
User: "hodor",
|
||||
Typ: "born",
|
||||
}},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -168,8 +168,8 @@ func TestEvent_Validate(t *testing.T) {
|
||||
AggregateID: "hodor",
|
||||
AggregateType: "user",
|
||||
AggregateVersion: "v1.0.0",
|
||||
EditorService: "management",
|
||||
EditorUser: "hodor",
|
||||
Service: "management",
|
||||
User: "hodor",
|
||||
ResourceOwner: "org",
|
||||
}},
|
||||
wantErr: true,
|
||||
@@ -180,10 +180,10 @@ func TestEvent_Validate(t *testing.T) {
|
||||
AggregateID: "hodor",
|
||||
AggregateType: "user",
|
||||
AggregateVersion: "v1.0.0",
|
||||
EditorService: "management",
|
||||
EditorUser: "hodor",
|
||||
Service: "management",
|
||||
User: "hodor",
|
||||
ResourceOwner: "org",
|
||||
Type: "born",
|
||||
Typ: "born",
|
||||
}},
|
||||
wantErr: false,
|
||||
},
|
||||
|
@@ -1,15 +0,0 @@
|
||||
package models
|
||||
|
||||
type Field int32
|
||||
|
||||
const (
|
||||
Field_AggregateType Field = 1 + iota
|
||||
Field_AggregateID
|
||||
Field_LatestSequence
|
||||
Field_ResourceOwner
|
||||
Field_EditorService
|
||||
Field_EditorUser
|
||||
Field_EventType
|
||||
Field_CreationDate
|
||||
Field_InstanceID
|
||||
)
|
@@ -1,46 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
type Filter struct {
|
||||
field Field
|
||||
value interface{}
|
||||
operation Operation
|
||||
}
|
||||
|
||||
//NewFilter is used in tests. Use searchQuery.*Filter() instead
|
||||
func NewFilter(field Field, value interface{}, operation Operation) *Filter {
|
||||
return &Filter{
|
||||
field: field,
|
||||
value: value,
|
||||
operation: operation,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Filter) GetField() Field {
|
||||
return f.field
|
||||
}
|
||||
func (f *Filter) GetOperation() Operation {
|
||||
return f.operation
|
||||
}
|
||||
func (f *Filter) GetValue() interface{} {
|
||||
return f.value
|
||||
}
|
||||
|
||||
func (f *Filter) Validate() error {
|
||||
if f == nil {
|
||||
return errors.ThrowPreconditionFailed(nil, "MODEL-z6KcG", "filter is nil")
|
||||
}
|
||||
if f.field <= 0 {
|
||||
return errors.ThrowPreconditionFailed(nil, "MODEL-zw62U", "field not definded")
|
||||
}
|
||||
if f.value == nil {
|
||||
return errors.ThrowPreconditionFailed(nil, "MODEL-GJ9ct", "no value definded")
|
||||
}
|
||||
if f.operation <= 0 {
|
||||
return errors.ThrowPreconditionFailed(nil, "MODEL-RrQTy", "operation not definded")
|
||||
}
|
||||
return nil
|
||||
}
|
@@ -1,104 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewFilter(t *testing.T) {
|
||||
type args struct {
|
||||
field Field
|
||||
value interface{}
|
||||
operation Operation
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *Filter
|
||||
}{
|
||||
{
|
||||
name: "aggregateID equals",
|
||||
args: args{
|
||||
field: Field_AggregateID,
|
||||
value: "hodor",
|
||||
operation: Operation_Equals,
|
||||
},
|
||||
want: &Filter{field: Field_AggregateID, operation: Operation_Equals, value: "hodor"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := NewFilter(tt.args.field, tt.args.value, tt.args.operation); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("NewFilter() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilter_Validate(t *testing.T) {
|
||||
type fields struct {
|
||||
field Field
|
||||
value interface{}
|
||||
operation Operation
|
||||
isNil bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "correct filter",
|
||||
fields: fields{
|
||||
field: Field_LatestSequence,
|
||||
operation: Operation_Greater,
|
||||
value: uint64(235),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "filter is nil",
|
||||
fields: fields{isNil: true},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no field error",
|
||||
fields: fields{
|
||||
operation: Operation_Greater,
|
||||
value: uint64(235),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no value error",
|
||||
fields: fields{
|
||||
field: Field_LatestSequence,
|
||||
operation: Operation_Greater,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no operation error",
|
||||
fields: fields{
|
||||
field: Field_LatestSequence,
|
||||
value: uint64(235),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var f *Filter
|
||||
if !tt.fields.isNil {
|
||||
f = &Filter{
|
||||
field: tt.fields.field,
|
||||
value: tt.fields.value,
|
||||
operation: tt.fields.operation,
|
||||
}
|
||||
}
|
||||
if err := f.Validate(); (err != nil) != tt.wantErr {
|
||||
t.Errorf("Filter.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -2,6 +2,8 @@ package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
type ObjectRoot struct {
|
||||
@@ -13,25 +15,25 @@ type ObjectRoot struct {
|
||||
ChangeDate time.Time `json:"-"`
|
||||
}
|
||||
|
||||
func (o *ObjectRoot) AppendEvent(event *Event) {
|
||||
func (o *ObjectRoot) AppendEvent(event eventstore.Event) {
|
||||
if o.AggregateID == "" {
|
||||
o.AggregateID = event.AggregateID
|
||||
} else if o.AggregateID != event.AggregateID {
|
||||
o.AggregateID = event.Aggregate().ID
|
||||
} else if o.AggregateID != event.Aggregate().ID {
|
||||
return
|
||||
}
|
||||
if o.ResourceOwner == "" {
|
||||
o.ResourceOwner = event.ResourceOwner
|
||||
o.ResourceOwner = event.Aggregate().ResourceOwner
|
||||
}
|
||||
if o.InstanceID == "" {
|
||||
o.InstanceID = event.InstanceID
|
||||
o.InstanceID = event.Aggregate().InstanceID
|
||||
}
|
||||
|
||||
o.ChangeDate = event.CreationDate
|
||||
o.ChangeDate = event.CreatedAt()
|
||||
if o.CreationDate.IsZero() {
|
||||
o.CreationDate = o.ChangeDate
|
||||
}
|
||||
|
||||
o.Sequence = event.Sequence
|
||||
o.Sequence = event.Sequence()
|
||||
}
|
||||
func (o *ObjectRoot) IsZero() bool {
|
||||
return o.AggregateID == ""
|
||||
|
@@ -27,7 +27,7 @@ func TestObjectRoot_AppendEvent(t *testing.T) {
|
||||
args{
|
||||
&Event{
|
||||
AggregateID: "aggID",
|
||||
Sequence: 34555,
|
||||
Seq: 34555,
|
||||
CreationDate: time.Now(),
|
||||
},
|
||||
true,
|
||||
@@ -44,7 +44,7 @@ func TestObjectRoot_AppendEvent(t *testing.T) {
|
||||
args{
|
||||
&Event{
|
||||
AggregateID: "agg",
|
||||
Sequence: 34555425,
|
||||
Seq: 34555425,
|
||||
CreationDate: time.Now(),
|
||||
PreviousSequence: 22,
|
||||
},
|
||||
@@ -70,8 +70,8 @@ func TestObjectRoot_AppendEvent(t *testing.T) {
|
||||
t.Error("creationDate and changedate should differ")
|
||||
}
|
||||
}
|
||||
if o.Sequence != tt.args.event.Sequence {
|
||||
t.Errorf("sequence not equal to event: event: %d root: %d", tt.args.event.Sequence, o.Sequence)
|
||||
if o.Sequence != tt.args.event.Seq {
|
||||
t.Errorf("sequence not equal to event: event: %d root: %d", tt.args.event.Seq, o.Sequence)
|
||||
}
|
||||
if !o.ChangeDate.Equal(tt.args.event.CreationDate) {
|
||||
t.Errorf("changedate should be equal to event creation date: event: %v root: %v", tt.args.event.CreationDate, o.ChangeDate)
|
||||
|
@@ -1,11 +0,0 @@
|
||||
package models
|
||||
|
||||
type Operation int32
|
||||
|
||||
const (
|
||||
Operation_Equals Operation = 1 + iota
|
||||
Operation_Greater
|
||||
Operation_Less
|
||||
Operation_In
|
||||
Operation_NotIn
|
||||
)
|
@@ -1,296 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
type SearchQueryFactory struct {
|
||||
columns Columns
|
||||
limit uint64
|
||||
desc bool
|
||||
queries []*query
|
||||
|
||||
InstanceFiltered bool
|
||||
}
|
||||
|
||||
type query struct {
|
||||
desc bool
|
||||
aggregateTypes []AggregateType
|
||||
aggregateIDs []string
|
||||
sequenceFrom uint64
|
||||
sequenceTo uint64
|
||||
eventTypes []EventType
|
||||
resourceOwner string
|
||||
instanceID string
|
||||
ignoredInstanceIDs []string
|
||||
creationDate time.Time
|
||||
factory *SearchQueryFactory
|
||||
}
|
||||
|
||||
type searchQuery struct {
|
||||
Columns Columns
|
||||
Limit uint64
|
||||
Desc bool
|
||||
Filters [][]*Filter
|
||||
}
|
||||
|
||||
type Columns int32
|
||||
|
||||
const (
|
||||
Columns_Event = iota
|
||||
Columns_Max_Sequence
|
||||
Columns_InstanceIDs
|
||||
// insert new columns-types before this columnsCount because count is needed for validation
|
||||
columnsCount
|
||||
)
|
||||
|
||||
// FactoryFromSearchQuery is deprecated because it's for migration purposes. use NewSearchQueryFactory
|
||||
func FactoryFromSearchQuery(q *SearchQuery) *SearchQueryFactory {
|
||||
factory := &SearchQueryFactory{
|
||||
columns: q.Columns,
|
||||
desc: q.Desc,
|
||||
limit: q.Limit,
|
||||
queries: make([]*query, len(q.Queries)),
|
||||
}
|
||||
|
||||
for i, qq := range q.Queries {
|
||||
factory.queries[i] = &query{factory: factory}
|
||||
for _, filter := range qq.Filters {
|
||||
switch filter.field {
|
||||
case Field_AggregateType:
|
||||
factory.queries[i] = factory.queries[i].aggregateTypesMig(filter.value.([]AggregateType)...)
|
||||
case Field_AggregateID:
|
||||
if aggregateID, ok := filter.value.(string); ok {
|
||||
factory.queries[i] = factory.queries[i].AggregateIDs(aggregateID)
|
||||
} else if aggregateIDs, ok := filter.value.([]string); ok {
|
||||
factory.queries[i] = factory.queries[i].AggregateIDs(aggregateIDs...)
|
||||
}
|
||||
case Field_LatestSequence:
|
||||
if filter.operation == Operation_Greater {
|
||||
factory.queries[i] = factory.queries[i].SequenceGreater(filter.value.(uint64))
|
||||
} else {
|
||||
factory.queries[i] = factory.queries[i].SequenceLess(filter.value.(uint64))
|
||||
}
|
||||
case Field_ResourceOwner:
|
||||
factory.queries[i] = factory.queries[i].ResourceOwner(filter.value.(string))
|
||||
case Field_InstanceID:
|
||||
factory.InstanceFiltered = true
|
||||
if filter.operation == Operation_Equals {
|
||||
factory.queries[i] = factory.queries[i].InstanceID(filter.value.(string))
|
||||
} else if filter.operation == Operation_NotIn {
|
||||
factory.queries[i] = factory.queries[i].IgnoredInstanceIDs(filter.value.([]string)...)
|
||||
}
|
||||
case Field_EventType:
|
||||
factory.queries[i] = factory.queries[i].EventTypes(filter.value.([]EventType)...)
|
||||
case Field_EditorService, Field_EditorUser:
|
||||
logging.WithFields("value", filter.value).Panic("field not converted to factory")
|
||||
case Field_CreationDate:
|
||||
factory.queries[i] = factory.queries[i].CreationDateNewer(filter.value.(time.Time))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return factory
|
||||
}
|
||||
|
||||
func NewSearchQueryFactory() *SearchQueryFactory {
|
||||
return &SearchQueryFactory{}
|
||||
}
|
||||
|
||||
func (factory *SearchQueryFactory) Columns(columns Columns) *SearchQueryFactory {
|
||||
factory.columns = columns
|
||||
return factory
|
||||
}
|
||||
|
||||
func (factory *SearchQueryFactory) Limit(limit uint64) *SearchQueryFactory {
|
||||
factory.limit = limit
|
||||
return factory
|
||||
}
|
||||
|
||||
func (factory *SearchQueryFactory) OrderDesc() *SearchQueryFactory {
|
||||
factory.desc = true
|
||||
return factory
|
||||
}
|
||||
|
||||
func (factory *SearchQueryFactory) OrderAsc() *SearchQueryFactory {
|
||||
factory.desc = false
|
||||
return factory
|
||||
}
|
||||
|
||||
func (factory *SearchQueryFactory) AddQuery() *query {
|
||||
q := &query{factory: factory}
|
||||
factory.queries = append(factory.queries, q)
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *query) Factory() *SearchQueryFactory {
|
||||
return q.factory
|
||||
}
|
||||
|
||||
func (q *query) SequenceGreater(sequence uint64) *query {
|
||||
q.sequenceFrom = sequence
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *query) SequenceLess(sequence uint64) *query {
|
||||
q.sequenceTo = sequence
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *query) AggregateTypes(types ...AggregateType) *query {
|
||||
q.aggregateTypes = types
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *query) AggregateIDs(ids ...string) *query {
|
||||
q.aggregateIDs = ids
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *query) aggregateTypesMig(types ...AggregateType) *query {
|
||||
q.aggregateTypes = types
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *query) EventTypes(types ...EventType) *query {
|
||||
q.eventTypes = types
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *query) ResourceOwner(resourceOwner string) *query {
|
||||
q.resourceOwner = resourceOwner
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *query) InstanceID(instanceID string) *query {
|
||||
q.instanceID = instanceID
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *query) IgnoredInstanceIDs(instanceIDs ...string) *query {
|
||||
q.ignoredInstanceIDs = instanceIDs
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *query) CreationDateNewer(time time.Time) *query {
|
||||
q.creationDate = time
|
||||
return q
|
||||
}
|
||||
|
||||
func (factory *SearchQueryFactory) Build() (*searchQuery, error) {
|
||||
if factory == nil ||
|
||||
len(factory.queries) < 1 ||
|
||||
(factory.columns < 0 || factory.columns >= columnsCount) {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "MODEL-tGAD3", "factory invalid")
|
||||
}
|
||||
filters := make([][]*Filter, len(factory.queries))
|
||||
|
||||
for i, query := range factory.queries {
|
||||
for _, f := range []func() *Filter{
|
||||
query.aggregateTypeFilter,
|
||||
query.aggregateIDFilter,
|
||||
query.sequenceFromFilter,
|
||||
query.sequenceToFilter,
|
||||
query.eventTypeFilter,
|
||||
query.resourceOwnerFilter,
|
||||
query.instanceIDFilter,
|
||||
query.ignoredInstanceIDsFilter,
|
||||
query.creationDateNewerFilter,
|
||||
} {
|
||||
if filter := f(); filter != nil {
|
||||
filters[i] = append(filters[i], filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &searchQuery{
|
||||
Columns: factory.columns,
|
||||
Limit: factory.limit,
|
||||
Desc: factory.desc,
|
||||
Filters: filters,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (q *query) aggregateIDFilter() *Filter {
|
||||
if len(q.aggregateIDs) < 1 {
|
||||
return nil
|
||||
}
|
||||
if len(q.aggregateIDs) == 1 {
|
||||
return NewFilter(Field_AggregateID, q.aggregateIDs[0], Operation_Equals)
|
||||
}
|
||||
return NewFilter(Field_AggregateID, q.aggregateIDs, Operation_In)
|
||||
}
|
||||
|
||||
func (q *query) eventTypeFilter() *Filter {
|
||||
if len(q.eventTypes) < 1 {
|
||||
return nil
|
||||
}
|
||||
if len(q.eventTypes) == 1 {
|
||||
return NewFilter(Field_EventType, q.eventTypes[0], Operation_Equals)
|
||||
}
|
||||
return NewFilter(Field_EventType, q.eventTypes, Operation_In)
|
||||
}
|
||||
|
||||
func (q *query) aggregateTypeFilter() *Filter {
|
||||
if len(q.aggregateTypes) < 1 {
|
||||
return nil
|
||||
}
|
||||
if len(q.aggregateTypes) == 1 {
|
||||
return NewFilter(Field_AggregateType, q.aggregateTypes[0], Operation_Equals)
|
||||
}
|
||||
return NewFilter(Field_AggregateType, q.aggregateTypes, Operation_In)
|
||||
}
|
||||
|
||||
func (q *query) sequenceFromFilter() *Filter {
|
||||
if q.sequenceFrom == 0 {
|
||||
return nil
|
||||
}
|
||||
sortOrder := Operation_Greater
|
||||
if q.factory.desc {
|
||||
sortOrder = Operation_Less
|
||||
}
|
||||
return NewFilter(Field_LatestSequence, q.sequenceFrom, sortOrder)
|
||||
}
|
||||
|
||||
func (q *query) sequenceToFilter() *Filter {
|
||||
if q.sequenceTo == 0 {
|
||||
return nil
|
||||
}
|
||||
sortOrder := Operation_Less
|
||||
if q.factory.desc {
|
||||
sortOrder = Operation_Greater
|
||||
}
|
||||
return NewFilter(Field_LatestSequence, q.sequenceTo, sortOrder)
|
||||
}
|
||||
|
||||
func (q *query) resourceOwnerFilter() *Filter {
|
||||
if q.resourceOwner == "" {
|
||||
return nil
|
||||
}
|
||||
return NewFilter(Field_ResourceOwner, q.resourceOwner, Operation_Equals)
|
||||
}
|
||||
|
||||
func (q *query) instanceIDFilter() *Filter {
|
||||
if q.instanceID == "" {
|
||||
return nil
|
||||
}
|
||||
return NewFilter(Field_InstanceID, q.instanceID, Operation_Equals)
|
||||
}
|
||||
|
||||
func (q *query) ignoredInstanceIDsFilter() *Filter {
|
||||
if len(q.ignoredInstanceIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return NewFilter(Field_InstanceID, q.ignoredInstanceIDs, Operation_NotIn)
|
||||
}
|
||||
|
||||
func (q *query) creationDateNewerFilter() *Filter {
|
||||
if q.creationDate.IsZero() {
|
||||
return nil
|
||||
}
|
||||
return NewFilter(Field_CreationDate, q.creationDate, Operation_Greater)
|
||||
}
|
@@ -1,148 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
//SearchQuery is deprecated. Use SearchQueryFactory
|
||||
type SearchQuery struct {
|
||||
Columns Columns
|
||||
Limit uint64
|
||||
Desc bool
|
||||
Filters []*Filter
|
||||
Queries []*Query
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
searchQuery *SearchQuery
|
||||
Filters []*Filter
|
||||
}
|
||||
|
||||
//NewSearchQuery is deprecated. Use SearchQueryFactory
|
||||
func NewSearchQuery() *SearchQuery {
|
||||
return &SearchQuery{
|
||||
Filters: make([]*Filter, 0, 4),
|
||||
Queries: make([]*Query, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (q *SearchQuery) SetColumn(columns Columns) *SearchQuery {
|
||||
q.Columns = columns
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *SearchQuery) AddQuery() *Query {
|
||||
query := &Query{
|
||||
searchQuery: q,
|
||||
}
|
||||
q.Queries = append(q.Queries, query)
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
//SearchQuery returns the SearchQuery of the sub query
|
||||
func (q *Query) SearchQuery() *SearchQuery {
|
||||
return q.searchQuery
|
||||
}
|
||||
func (q *Query) setFilter(filter *Filter) *Query {
|
||||
for i, f := range q.Filters {
|
||||
if f.field == filter.field && f.field != Field_LatestSequence {
|
||||
q.Filters[i] = filter
|
||||
return q
|
||||
}
|
||||
}
|
||||
q.Filters = append(q.Filters, filter)
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *SearchQuery) SetLimit(limit uint64) *SearchQuery {
|
||||
q.Limit = limit
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *SearchQuery) OrderDesc() *SearchQuery {
|
||||
q.Desc = true
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *SearchQuery) OrderAsc() *SearchQuery {
|
||||
q.Desc = false
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *Query) AggregateIDFilter(id string) *Query {
|
||||
return q.setFilter(NewFilter(Field_AggregateID, id, Operation_Equals))
|
||||
}
|
||||
|
||||
func (q *Query) AggregateIDsFilter(ids ...string) *Query {
|
||||
return q.setFilter(NewFilter(Field_AggregateID, ids, Operation_In))
|
||||
}
|
||||
|
||||
func (q *Query) AggregateTypeFilter(types ...AggregateType) *Query {
|
||||
return q.setFilter(NewFilter(Field_AggregateType, types, Operation_In))
|
||||
}
|
||||
|
||||
func (q *Query) EventTypesFilter(types ...EventType) *Query {
|
||||
return q.setFilter(NewFilter(Field_EventType, types, Operation_In))
|
||||
}
|
||||
|
||||
func (q *Query) LatestSequenceFilter(sequence uint64) *Query {
|
||||
if sequence == 0 {
|
||||
return q
|
||||
}
|
||||
sortOrder := Operation_Greater
|
||||
return q.setFilter(NewFilter(Field_LatestSequence, sequence, sortOrder))
|
||||
}
|
||||
|
||||
func (q *Query) SequenceBetween(from, to uint64) *Query {
|
||||
q.setFilter(NewFilter(Field_LatestSequence, from, Operation_Greater))
|
||||
q.setFilter(NewFilter(Field_LatestSequence, to, Operation_Less))
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *Query) ResourceOwnerFilter(resourceOwner string) *Query {
|
||||
return q.setFilter(NewFilter(Field_ResourceOwner, resourceOwner, Operation_Equals))
|
||||
}
|
||||
|
||||
func (q *Query) InstanceIDFilter(instanceID string) *Query {
|
||||
return q.setFilter(NewFilter(Field_InstanceID, instanceID, Operation_Equals))
|
||||
}
|
||||
|
||||
func (q *Query) ExcludedInstanceIDsFilter(instanceIDs ...string) *Query {
|
||||
return q.setFilter(NewFilter(Field_InstanceID, instanceIDs, Operation_NotIn))
|
||||
}
|
||||
|
||||
func (q *Query) CreationDateNewerFilter(time time.Time) *Query {
|
||||
return q.setFilter(NewFilter(Field_CreationDate, time, Operation_Greater))
|
||||
}
|
||||
|
||||
func (q *SearchQuery) setFilter(filter *Filter) *SearchQuery {
|
||||
for i, f := range q.Filters {
|
||||
if f.field == filter.field && f.field != Field_LatestSequence {
|
||||
q.Filters[i] = filter
|
||||
return q
|
||||
}
|
||||
}
|
||||
q.Filters = append(q.Filters, filter)
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *SearchQuery) Validate() error {
|
||||
if q == nil {
|
||||
return errors.ThrowPreconditionFailed(nil, "MODEL-J5xQi", "search query is nil")
|
||||
}
|
||||
if len(q.Queries) == 0 {
|
||||
return errors.ThrowPreconditionFailed(nil, "MODEL-pF3DR", "no filters set")
|
||||
}
|
||||
for _, query := range q.Queries {
|
||||
for _, filter := range query.Filters {
|
||||
if err := filter.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@@ -1,65 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSearchQuery_setFilter(t *testing.T) {
|
||||
type fields struct {
|
||||
query *SearchQuery
|
||||
}
|
||||
type args struct {
|
||||
filters []*Filter
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want *SearchQuery
|
||||
}{
|
||||
{
|
||||
name: "set idFilter",
|
||||
fields: fields{query: NewSearchQuery()},
|
||||
args: args{filters: []*Filter{
|
||||
{field: Field_AggregateID, operation: Operation_Equals, value: "hodor"},
|
||||
}},
|
||||
want: &SearchQuery{Filters: []*Filter{
|
||||
{field: Field_AggregateID, operation: Operation_Equals, value: "hodor"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "overwrite idFilter",
|
||||
fields: fields{query: NewSearchQuery()},
|
||||
args: args{filters: []*Filter{
|
||||
{field: Field_AggregateID, operation: Operation_Equals, value: "hodor"},
|
||||
{field: Field_AggregateID, operation: Operation_Equals, value: "ursli"},
|
||||
}},
|
||||
want: &SearchQuery{Filters: []*Filter{
|
||||
{field: Field_AggregateID, operation: Operation_Equals, value: "ursli"},
|
||||
}},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.fields.query
|
||||
for _, filter := range tt.args.filters {
|
||||
got = got.setFilter(filter)
|
||||
}
|
||||
for _, wantFilter := range tt.want.Filters {
|
||||
found := false
|
||||
for _, gotFilter := range got.Filters {
|
||||
if gotFilter.field == wantFilter.field {
|
||||
found = true
|
||||
if !reflect.DeepEqual(wantFilter, gotFilter) {
|
||||
t.Errorf("filter not as expected: want: %v got %v", wantFilter, gotFilter)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("filter field %v not found", wantFilter.field)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -1,590 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func testSetColumns(columns Columns) func(factory *SearchQueryFactory) *SearchQueryFactory {
|
||||
return func(factory *SearchQueryFactory) *SearchQueryFactory {
|
||||
factory = factory.Columns(columns)
|
||||
return factory
|
||||
}
|
||||
}
|
||||
|
||||
func testSetLimit(limit uint64) func(factory *SearchQueryFactory) *SearchQueryFactory {
|
||||
return func(factory *SearchQueryFactory) *SearchQueryFactory {
|
||||
factory = factory.Limit(limit)
|
||||
return factory
|
||||
}
|
||||
}
|
||||
|
||||
func testAddQuery(queryFuncs ...func(*query) *query) func(*SearchQueryFactory) *SearchQueryFactory {
|
||||
return func(builder *SearchQueryFactory) *SearchQueryFactory {
|
||||
query := builder.AddQuery()
|
||||
for _, queryFunc := range queryFuncs {
|
||||
queryFunc(query)
|
||||
}
|
||||
return query.Factory()
|
||||
}
|
||||
}
|
||||
|
||||
func testSetSequence(sequence uint64) func(*query) *query {
|
||||
return func(q *query) *query {
|
||||
q.SequenceGreater(sequence)
|
||||
return q
|
||||
}
|
||||
}
|
||||
|
||||
func testSetAggregateIDs(aggregateIDs ...string) func(*query) *query {
|
||||
return func(q *query) *query {
|
||||
q.AggregateIDs(aggregateIDs...)
|
||||
return q
|
||||
}
|
||||
}
|
||||
|
||||
func testSetAggregateTypes(aggregateTypes ...AggregateType) func(*query) *query {
|
||||
return func(q *query) *query {
|
||||
q.AggregateTypes(aggregateTypes...)
|
||||
return q
|
||||
}
|
||||
}
|
||||
|
||||
func testSetEventTypes(eventTypes ...EventType) func(*query) *query {
|
||||
return func(q *query) *query {
|
||||
q.EventTypes(eventTypes...)
|
||||
return q
|
||||
}
|
||||
}
|
||||
|
||||
func testSetResourceOwner(resourceOwner string) func(*query) *query {
|
||||
return func(q *query) *query {
|
||||
q.ResourceOwner(resourceOwner)
|
||||
return q
|
||||
}
|
||||
}
|
||||
|
||||
func testSetSortOrder(asc bool) func(factory *SearchQueryFactory) *SearchQueryFactory {
|
||||
return func(factory *SearchQueryFactory) *SearchQueryFactory {
|
||||
if asc {
|
||||
factory = factory.OrderAsc()
|
||||
} else {
|
||||
factory = factory.OrderDesc()
|
||||
}
|
||||
return factory
|
||||
}
|
||||
}
|
||||
|
||||
func assertFactory(t *testing.T, want, got *SearchQueryFactory) {
|
||||
t.Helper()
|
||||
|
||||
if got.columns != want.columns {
|
||||
t.Errorf("wrong column: got: %v want: %v", got.columns, want.columns)
|
||||
}
|
||||
if got.desc != want.desc {
|
||||
t.Errorf("wrong desc: got: %v want: %v", got.desc, want.desc)
|
||||
}
|
||||
if got.limit != want.limit {
|
||||
t.Errorf("wrong limit: got: %v want: %v", got.limit, want.limit)
|
||||
}
|
||||
if len(got.queries) != len(want.queries) {
|
||||
t.Errorf("wrong length of queries: got: %v want: %v", len(got.queries), len(want.queries))
|
||||
}
|
||||
|
||||
for i, query := range got.queries {
|
||||
assertQuery(t, i, want.queries[i], query)
|
||||
}
|
||||
}
|
||||
|
||||
func assertQuery(t *testing.T, i int, want, got *query) {
|
||||
t.Helper()
|
||||
|
||||
if !reflect.DeepEqual(got.aggregateIDs, want.aggregateIDs) {
|
||||
t.Errorf("wrong aggregateIDs in query %d : got: %v want: %v", i, got.aggregateIDs, want.aggregateIDs)
|
||||
}
|
||||
if !reflect.DeepEqual(got.aggregateTypes, want.aggregateTypes) {
|
||||
t.Errorf("wrong aggregateTypes in query %d : got: %v want: %v", i, got.aggregateTypes, want.aggregateTypes)
|
||||
}
|
||||
if got.sequenceFrom != want.sequenceFrom {
|
||||
t.Errorf("wrong sequenceFrom in query %d : got: %v want: %v", i, got.sequenceFrom, want.sequenceFrom)
|
||||
}
|
||||
if got.sequenceTo != want.sequenceTo {
|
||||
t.Errorf("wrong sequenceTo in query %d : got: %v want: %v", i, got.sequenceTo, want.sequenceTo)
|
||||
}
|
||||
if !reflect.DeepEqual(got.eventTypes, want.eventTypes) {
|
||||
t.Errorf("wrong eventTypes in query %d : got: %v want: %v", i, got.eventTypes, want.eventTypes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchQueryFactorySetters(t *testing.T) {
|
||||
type args struct {
|
||||
setters []func(*SearchQueryFactory) *SearchQueryFactory
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res *SearchQueryFactory
|
||||
}{
|
||||
{
|
||||
name: "New factory",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{},
|
||||
},
|
||||
res: &SearchQueryFactory{},
|
||||
},
|
||||
{
|
||||
name: "set columns",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{testSetColumns(Columns_Max_Sequence)},
|
||||
},
|
||||
res: &SearchQueryFactory{
|
||||
columns: Columns_Max_Sequence,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set limit",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{testSetLimit(100)},
|
||||
},
|
||||
res: &SearchQueryFactory{
|
||||
limit: 100,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set sequence",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{testAddQuery(testSetSequence(90))},
|
||||
},
|
||||
res: &SearchQueryFactory{
|
||||
queries: []*query{
|
||||
{
|
||||
sequenceFrom: 90,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set aggregateTypes",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{testAddQuery(testSetAggregateTypes("user", "org"))},
|
||||
},
|
||||
res: &SearchQueryFactory{
|
||||
queries: []*query{
|
||||
{
|
||||
aggregateTypes: []AggregateType{"user", "org"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set aggregateIDs",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{testAddQuery(testSetAggregateIDs("1235", "09824"))},
|
||||
},
|
||||
res: &SearchQueryFactory{
|
||||
queries: []*query{
|
||||
{
|
||||
aggregateIDs: []string{"1235", "09824"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set eventTypes",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{testAddQuery(testSetEventTypes("user.created", "user.updated"))},
|
||||
},
|
||||
res: &SearchQueryFactory{
|
||||
queries: []*query{
|
||||
{
|
||||
eventTypes: []EventType{"user.created", "user.updated"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set resource owner",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{testAddQuery(testSetResourceOwner("hodor"))},
|
||||
},
|
||||
res: &SearchQueryFactory{
|
||||
queries: []*query{
|
||||
{
|
||||
resourceOwner: "hodor",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "default search query",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{testAddQuery(testSetAggregateTypes("user"), testSetAggregateIDs("1235", "024")), testSetSortOrder(false)},
|
||||
},
|
||||
res: &SearchQueryFactory{
|
||||
desc: true,
|
||||
queries: []*query{
|
||||
{
|
||||
aggregateTypes: []AggregateType{"user"},
|
||||
aggregateIDs: []string{"1235", "024"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
factory := NewSearchQueryFactory()
|
||||
for _, setter := range tt.args.setters {
|
||||
factory = setter(factory)
|
||||
}
|
||||
assertFactory(t, tt.res, factory)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchQueryFactoryBuild(t *testing.T) {
|
||||
type args struct {
|
||||
setters []func(*SearchQueryFactory) *SearchQueryFactory
|
||||
}
|
||||
type res struct {
|
||||
isErr func(err error) bool
|
||||
query *searchQuery
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "no aggregate types",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{},
|
||||
},
|
||||
res: res{
|
||||
isErr: errors.IsPreconditionFailed,
|
||||
query: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid column (too low)",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{
|
||||
testSetColumns(Columns(-1)),
|
||||
testAddQuery(testSetAggregateTypes("user")),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
isErr: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid column (too high)",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{
|
||||
testSetColumns(columnsCount),
|
||||
testAddQuery(testSetAggregateTypes("user")),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
isErr: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter aggregate type",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{
|
||||
testAddQuery(testSetAggregateTypes("user")),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
isErr: nil,
|
||||
query: &searchQuery{
|
||||
Columns: 0,
|
||||
Desc: false,
|
||||
Limit: 0,
|
||||
Filters: [][]*Filter{
|
||||
{
|
||||
NewFilter(Field_AggregateType, AggregateType("user"), Operation_Equals),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter aggregate types",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{
|
||||
testAddQuery(testSetAggregateTypes("user", "org")),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
isErr: nil,
|
||||
query: &searchQuery{
|
||||
Columns: 0,
|
||||
Desc: false,
|
||||
Limit: 0,
|
||||
Filters: [][]*Filter{
|
||||
{
|
||||
NewFilter(Field_AggregateType, []AggregateType{"user", "org"}, Operation_In),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter aggregate type, limit, desc",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{
|
||||
testSetLimit(5),
|
||||
testSetSortOrder(false),
|
||||
testAddQuery(
|
||||
testSetAggregateTypes("user"),
|
||||
testSetSequence(100),
|
||||
),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
isErr: nil,
|
||||
query: &searchQuery{
|
||||
Columns: 0,
|
||||
Desc: true,
|
||||
Limit: 5,
|
||||
Filters: [][]*Filter{
|
||||
{
|
||||
NewFilter(Field_AggregateType, AggregateType("user"), Operation_Equals),
|
||||
NewFilter(Field_LatestSequence, uint64(100), Operation_Less),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter aggregate type, limit, asc",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{
|
||||
testSetLimit(5),
|
||||
testSetSortOrder(true),
|
||||
testAddQuery(
|
||||
testSetSequence(100),
|
||||
testSetAggregateTypes("user"),
|
||||
),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
isErr: nil,
|
||||
query: &searchQuery{
|
||||
Columns: 0,
|
||||
Desc: false,
|
||||
Limit: 5,
|
||||
Filters: [][]*Filter{
|
||||
{
|
||||
NewFilter(Field_AggregateType, AggregateType("user"), Operation_Equals),
|
||||
NewFilter(Field_LatestSequence, uint64(100), Operation_Greater),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter aggregate type, limit, desc, max event sequence cols",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{
|
||||
testSetLimit(5),
|
||||
testSetSortOrder(false),
|
||||
testSetColumns(Columns_Max_Sequence),
|
||||
testAddQuery(
|
||||
testSetSequence(100),
|
||||
testSetAggregateTypes("user"),
|
||||
),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
isErr: nil,
|
||||
query: &searchQuery{
|
||||
Columns: Columns_Max_Sequence,
|
||||
Desc: true,
|
||||
Limit: 5,
|
||||
Filters: [][]*Filter{
|
||||
{
|
||||
NewFilter(Field_AggregateType, AggregateType("user"), Operation_Equals),
|
||||
NewFilter(Field_LatestSequence, uint64(100), Operation_Less),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter aggregate type and aggregate id",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{
|
||||
testAddQuery(
|
||||
testSetAggregateIDs("1234"),
|
||||
testSetAggregateTypes("user"),
|
||||
),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
isErr: nil,
|
||||
query: &searchQuery{
|
||||
Columns: 0,
|
||||
Desc: false,
|
||||
Limit: 0,
|
||||
Filters: [][]*Filter{
|
||||
{
|
||||
NewFilter(Field_AggregateType, AggregateType("user"), Operation_Equals),
|
||||
NewFilter(Field_AggregateID, "1234", Operation_Equals),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter aggregate type and aggregate ids",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{
|
||||
testAddQuery(
|
||||
testSetAggregateIDs("1234", "0815"),
|
||||
testSetAggregateTypes("user"),
|
||||
),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
isErr: nil,
|
||||
query: &searchQuery{
|
||||
Columns: 0,
|
||||
Desc: false,
|
||||
Limit: 0,
|
||||
Filters: [][]*Filter{
|
||||
{
|
||||
NewFilter(Field_AggregateType, AggregateType("user"), Operation_Equals),
|
||||
NewFilter(Field_AggregateID, []string{"1234", "0815"}, Operation_In),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter aggregate type and sequence greater",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{
|
||||
testAddQuery(
|
||||
testSetSequence(8),
|
||||
testSetAggregateTypes("user"),
|
||||
),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
isErr: nil,
|
||||
query: &searchQuery{
|
||||
Columns: 0,
|
||||
Desc: false,
|
||||
Limit: 0,
|
||||
Filters: [][]*Filter{
|
||||
{
|
||||
NewFilter(Field_AggregateType, AggregateType("user"), Operation_Equals),
|
||||
NewFilter(Field_LatestSequence, uint64(8), Operation_Greater),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter aggregate type and event type",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{
|
||||
testAddQuery(
|
||||
testSetAggregateTypes("user"),
|
||||
testSetEventTypes("user.created"),
|
||||
),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
isErr: nil,
|
||||
query: &searchQuery{
|
||||
Columns: 0,
|
||||
Desc: false,
|
||||
Limit: 0,
|
||||
Filters: [][]*Filter{
|
||||
{
|
||||
NewFilter(Field_AggregateType, AggregateType("user"), Operation_Equals),
|
||||
NewFilter(Field_EventType, EventType("user.created"), Operation_Equals),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter aggregate type and event types",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{
|
||||
testAddQuery(
|
||||
testSetAggregateTypes("user"),
|
||||
testSetEventTypes("user.created", "user.changed"),
|
||||
),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
isErr: nil,
|
||||
query: &searchQuery{
|
||||
Columns: 0,
|
||||
Desc: false,
|
||||
Limit: 0,
|
||||
Filters: [][]*Filter{
|
||||
{
|
||||
NewFilter(Field_AggregateType, AggregateType("user"), Operation_Equals),
|
||||
NewFilter(Field_EventType, []EventType{"user.created", "user.changed"}, Operation_In),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter aggregate type resource owner",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryFactory) *SearchQueryFactory{
|
||||
testAddQuery(
|
||||
testSetAggregateTypes("user"),
|
||||
testSetResourceOwner("hodor"),
|
||||
),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
isErr: nil,
|
||||
query: &searchQuery{
|
||||
Columns: 0,
|
||||
Desc: false,
|
||||
Limit: 0,
|
||||
Filters: [][]*Filter{
|
||||
{
|
||||
NewFilter(Field_AggregateType, AggregateType("user"), Operation_Equals),
|
||||
NewFilter(Field_ResourceOwner, "hodor", Operation_Equals),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
factory := NewSearchQueryFactory()
|
||||
for _, f := range tt.args.setters {
|
||||
factory = f(factory)
|
||||
}
|
||||
query, err := factory.Build()
|
||||
if tt.res.isErr != nil && !tt.res.isErr(err) {
|
||||
t.Errorf("wrong error: %v", err)
|
||||
return
|
||||
}
|
||||
if err != nil && tt.res.isErr == nil {
|
||||
t.Errorf("no error expected: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(query, tt.res.query) {
|
||||
t.Errorf("NewSearchQueryFactory() = %v, want %v", factory, tt.res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
var versionRegexp = regexp.MustCompile(`^v[0-9]+(\.[0-9]+){0,2}$`)
|
||||
|
||||
type Version string
|
||||
|
||||
func (v Version) Validate() error {
|
||||
if !versionRegexp.MatchString(string(v)) {
|
||||
return errors.ThrowPreconditionFailed(nil, "MODEL-luDuS", "version is not semver")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
return string(v)
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
package models
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestVersion_Validate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
v Version
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"correct version",
|
||||
"v1.23.23",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"no v prefix",
|
||||
"1.2.2",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"letters in version",
|
||||
"v1.as.3",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"no version",
|
||||
"",
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := tt.v.Validate(); (err != nil) != tt.wantErr {
|
||||
t.Errorf("Version.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user