package eventstore import ( "math" "reflect" "testing" "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore/repository" ) func testSetColumns(columns Columns) func(factory *SearchQueryBuilder) *SearchQueryBuilder { return func(factory *SearchQueryBuilder) *SearchQueryBuilder { factory = factory.Columns(columns) return factory } } func testSetLimit(limit uint64) func(factory *SearchQueryBuilder) *SearchQueryBuilder { return func(factory *SearchQueryBuilder) *SearchQueryBuilder { factory = factory.Limit(limit) return factory } } func testSetSequence(sequence uint64) func(factory *SearchQueryBuilder) *SearchQueryBuilder { return func(factory *SearchQueryBuilder) *SearchQueryBuilder { factory = factory.SequenceGreater(sequence) return factory } } func testSetAggregateIDs(aggregateIDs ...string) func(factory *SearchQueryBuilder) *SearchQueryBuilder { return func(factory *SearchQueryBuilder) *SearchQueryBuilder { factory = factory.AggregateIDs(aggregateIDs...) return factory } } func testSetEventTypes(eventTypes ...EventType) func(factory *SearchQueryBuilder) *SearchQueryBuilder { return func(factory *SearchQueryBuilder) *SearchQueryBuilder { factory = factory.EventTypes(eventTypes...) return factory } } func testSetResourceOwner(resourceOwner string) func(factory *SearchQueryBuilder) *SearchQueryBuilder { return func(factory *SearchQueryBuilder) *SearchQueryBuilder { factory = factory.ResourceOwner(resourceOwner) return factory } } func testSetSortOrder(asc bool) func(factory *SearchQueryBuilder) *SearchQueryBuilder { return func(factory *SearchQueryBuilder) *SearchQueryBuilder { if asc { factory = factory.OrderAsc() } else { factory = factory.OrderDesc() } return factory } } func TestSearchQueryFactorySetters(t *testing.T) { type args struct { columns Columns aggregateTypes []AggregateType setters []func(*SearchQueryBuilder) *SearchQueryBuilder } tests := []struct { name string args args res *SearchQueryBuilder }{ { name: "New factory", args: args{ columns: ColumnsEvent, aggregateTypes: []AggregateType{"user", "org"}, }, res: &SearchQueryBuilder{ columns: repository.Columns(ColumnsEvent), aggregateTypes: []AggregateType{"user", "org"}, }, }, { name: "set columns", args: args{ setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{testSetColumns(repository.ColumnsMaxSequence)}, }, res: &SearchQueryBuilder{ columns: repository.ColumnsMaxSequence, }, }, { name: "set limit", args: args{ setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{testSetLimit(100)}, }, res: &SearchQueryBuilder{ limit: 100, }, }, { name: "set sequence", args: args{ setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{testSetSequence(90)}, }, res: &SearchQueryBuilder{ eventSequence: 90, }, }, { name: "set aggregateIDs", args: args{ setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{testSetAggregateIDs("1235", "09824")}, }, res: &SearchQueryBuilder{ aggregateIDs: []string{"1235", "09824"}, }, }, { name: "set eventTypes", args: args{ setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{testSetEventTypes("user.created", "user.updated")}, }, res: &SearchQueryBuilder{ eventTypes: []EventType{"user.created", "user.updated"}, }, }, { name: "set resource owner", args: args{ setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{testSetResourceOwner("hodor")}, }, res: &SearchQueryBuilder{ resourceOwner: "hodor", }, }, { name: "default search query", args: args{ aggregateTypes: []AggregateType{"user"}, setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{testSetAggregateIDs("1235", "024"), testSetSortOrder(false)}, }, res: &SearchQueryBuilder{ aggregateTypes: []AggregateType{"user"}, aggregateIDs: []string{"1235", "024"}, desc: true, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { factory := NewSearchQueryBuilder(tt.args.columns, tt.args.aggregateTypes...) for _, setter := range tt.args.setters { factory = setter(factory) } if !reflect.DeepEqual(factory, tt.res) { t.Errorf("NewSearchQueryFactory() = %v, want %v", factory, tt.res) } }) } } func TestSearchQueryFactoryBuild(t *testing.T) { type args struct { columns Columns aggregateTypes []AggregateType setters []func(*SearchQueryBuilder) *SearchQueryBuilder } type res struct { isErr func(err error) bool query *repository.SearchQuery } tests := []struct { name string args args res res }{ { name: "no aggregate types", args: args{ columns: ColumnsEvent, aggregateTypes: []AggregateType{}, setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{}, }, res: res{ isErr: errors.IsPreconditionFailed, query: nil, }, }, { name: "invalid column (too low)", args: args{ columns: ColumnsEvent, aggregateTypes: []AggregateType{"user"}, setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{ testSetColumns(Columns(-1)), }, }, res: res{ isErr: errors.IsPreconditionFailed, }, }, { name: "invalid column (too high)", args: args{ columns: ColumnsEvent, aggregateTypes: []AggregateType{"user"}, setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{ testSetColumns(math.MaxInt32), }, }, res: res{ isErr: errors.IsPreconditionFailed, }, }, { name: "filter aggregate type", args: args{ columns: ColumnsEvent, aggregateTypes: []AggregateType{"user"}, setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{}, }, res: res{ isErr: nil, query: &repository.SearchQuery{ Columns: repository.ColumnsEvent, Desc: false, Limit: 0, Filters: []*repository.Filter{ repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals), }, }, }, }, { name: "filter aggregate types", args: args{ columns: ColumnsEvent, aggregateTypes: []AggregateType{"user", "org"}, setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{}, }, res: res{ isErr: nil, query: &repository.SearchQuery{ Columns: repository.ColumnsEvent, Desc: false, Limit: 0, Filters: []*repository.Filter{ repository.NewFilter(repository.FieldAggregateType, []repository.AggregateType{"user", "org"}, repository.OperationIn), }, }, }, }, { name: "filter aggregate type, limit, desc", args: args{ columns: ColumnsEvent, aggregateTypes: []AggregateType{"user"}, setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{ testSetLimit(5), testSetSortOrder(false), testSetSequence(100), }, }, res: res{ isErr: nil, query: &repository.SearchQuery{ Columns: repository.ColumnsEvent, Desc: true, Limit: 5, Filters: []*repository.Filter{ repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals), repository.NewFilter(repository.FieldSequence, uint64(100), repository.OperationLess), }, }, }, }, { name: "filter aggregate type, limit, asc", args: args{ columns: ColumnsEvent, aggregateTypes: []AggregateType{"user"}, setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{ testSetLimit(5), testSetSortOrder(true), testSetSequence(100), }, }, res: res{ isErr: nil, query: &repository.SearchQuery{ Columns: repository.ColumnsEvent, Desc: false, Limit: 5, Filters: []*repository.Filter{ repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals), repository.NewFilter(repository.FieldSequence, uint64(100), repository.OperationGreater), }, }, }, }, { name: "filter aggregate type, limit, desc, max event sequence cols", args: args{ columns: ColumnsEvent, aggregateTypes: []AggregateType{"user"}, setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{ testSetLimit(5), testSetSortOrder(false), testSetSequence(100), testSetColumns(repository.ColumnsMaxSequence), }, }, res: res{ isErr: nil, query: &repository.SearchQuery{ Columns: repository.ColumnsMaxSequence, Desc: true, Limit: 5, Filters: []*repository.Filter{ repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals), repository.NewFilter(repository.FieldSequence, uint64(100), repository.OperationLess), }, }, }, }, { name: "filter aggregate type and aggregate id", args: args{ columns: ColumnsEvent, aggregateTypes: []AggregateType{"user"}, setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{ testSetAggregateIDs("1234"), }, }, res: res{ isErr: nil, query: &repository.SearchQuery{ Columns: repository.ColumnsEvent, Desc: false, Limit: 0, Filters: []*repository.Filter{ repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals), repository.NewFilter(repository.FieldAggregateID, "1234", repository.OperationEquals), }, }, }, }, { name: "filter aggregate type and aggregate ids", args: args{ columns: ColumnsEvent, aggregateTypes: []AggregateType{"user"}, setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{ testSetAggregateIDs("1234", "0815"), }, }, res: res{ isErr: nil, query: &repository.SearchQuery{ Columns: repository.ColumnsEvent, Desc: false, Limit: 0, Filters: []*repository.Filter{ repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals), repository.NewFilter(repository.FieldAggregateID, []string{"1234", "0815"}, repository.OperationIn), }, }, }, }, { name: "filter aggregate type and sequence greater", args: args{ columns: ColumnsEvent, aggregateTypes: []AggregateType{"user"}, setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{ testSetSequence(8), }, }, res: res{ isErr: nil, query: &repository.SearchQuery{ Columns: repository.ColumnsEvent, Desc: false, Limit: 0, Filters: []*repository.Filter{ repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals), repository.NewFilter(repository.FieldSequence, uint64(8), repository.OperationGreater), }, }, }, }, { name: "filter aggregate type and event type", args: args{ columns: ColumnsEvent, aggregateTypes: []AggregateType{"user"}, setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{ testSetEventTypes("user.created"), }, }, res: res{ isErr: nil, query: &repository.SearchQuery{ Columns: repository.ColumnsEvent, Desc: false, Limit: 0, Filters: []*repository.Filter{ repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals), repository.NewFilter(repository.FieldEventType, repository.EventType("user.created"), repository.OperationEquals), }, }, }, }, { name: "filter aggregate type and event types", args: args{ columns: ColumnsEvent, aggregateTypes: []AggregateType{"user"}, setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{ testSetEventTypes("user.created", "user.changed"), }, }, res: res{ isErr: nil, query: &repository.SearchQuery{ Columns: repository.ColumnsEvent, Desc: false, Limit: 0, Filters: []*repository.Filter{ repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals), repository.NewFilter(repository.FieldEventType, []repository.EventType{"user.created", "user.changed"}, repository.OperationIn), }, }, }, }, { name: "filter aggregate type resource owner", args: args{ columns: ColumnsEvent, aggregateTypes: []AggregateType{"user"}, setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{ testSetResourceOwner("hodor"), }, }, res: res{ isErr: nil, query: &repository.SearchQuery{ Columns: repository.ColumnsEvent, Desc: false, Limit: 0, Filters: []*repository.Filter{ repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals), repository.NewFilter(repository.FieldResourceOwner, "hodor", repository.OperationEquals), }, }, }, }, { name: "column invalid", args: args{ columns: Columns(-1), aggregateTypes: []AggregateType{"user"}, }, res: res{ isErr: errors.IsPreconditionFailed, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { factory := NewSearchQueryBuilder(tt.args.columns, tt.args.aggregateTypes...) 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(%T): %v", err, 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.query) } }) } }