2020-10-05 17:09:26 +00:00
package sql
2020-10-02 14:21:51 +00:00
import (
2020-10-21 17:00:41 +00:00
"context"
2020-10-02 14:21:51 +00:00
"database/sql"
2020-10-21 17:00:41 +00:00
"database/sql/driver"
2020-10-02 14:21:51 +00:00
"reflect"
"testing"
"time"
2020-10-21 17:00:41 +00:00
"github.com/DATA-DOG/go-sqlmock"
2022-01-06 07:29:58 +00:00
2023-02-27 21:36:43 +00:00
"github.com/zitadel/zitadel/internal/database"
2022-04-26 23:01:45 +00:00
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore/repository"
2020-10-02 14:21:51 +00:00
)
func Test_getCondition ( t * testing . T ) {
type args struct {
2020-10-05 17:09:26 +00:00
filter * repository . Filter
2020-10-02 14:21:51 +00:00
}
tests := [ ] struct {
name string
args args
want string
} {
{
name : "equals" ,
2020-10-06 19:28:09 +00:00
args : args { filter : repository . NewFilter ( repository . FieldAggregateID , "" , repository . OperationEquals ) } ,
2020-10-02 14:21:51 +00:00
want : "aggregate_id = ?" ,
} ,
{
name : "greater" ,
2020-10-06 19:28:09 +00:00
args : args { filter : repository . NewFilter ( repository . FieldSequence , 0 , repository . OperationGreater ) } ,
2020-10-02 14:21:51 +00:00
want : "event_sequence > ?" ,
} ,
{
name : "less" ,
2020-10-06 19:28:09 +00:00
args : args { filter : repository . NewFilter ( repository . FieldSequence , 5000 , repository . OperationLess ) } ,
2020-10-02 14:21:51 +00:00
want : "event_sequence < ?" ,
} ,
{
name : "in list" ,
2020-10-06 19:28:09 +00:00
args : args { filter : repository . NewFilter ( repository . FieldAggregateType , [ ] repository . AggregateType { "movies" , "actors" } , repository . OperationIn ) } ,
2020-10-02 14:21:51 +00:00
want : "aggregate_type = ANY(?)" ,
} ,
{
name : "invalid operation" ,
2020-10-06 19:28:09 +00:00
args : args { filter : repository . NewFilter ( repository . FieldAggregateType , [ ] repository . AggregateType { "movies" , "actors" } , repository . Operation ( - 1 ) ) } ,
2020-10-02 14:21:51 +00:00
want : "" ,
} ,
{
name : "invalid field" ,
2020-10-06 19:28:09 +00:00
args : args { filter : repository . NewFilter ( repository . Field ( - 1 ) , [ ] repository . AggregateType { "movies" , "actors" } , repository . OperationEquals ) } ,
2020-10-02 14:21:51 +00:00
want : "" ,
} ,
{
name : "invalid field and operation" ,
2020-10-05 17:09:26 +00:00
args : args { filter : repository . NewFilter ( repository . Field ( - 1 ) , [ ] repository . AggregateType { "movies" , "actors" } , repository . Operation ( - 1 ) ) } ,
2020-10-02 14:21:51 +00:00
want : "" ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
2020-10-05 18:39:36 +00:00
db := & CRDB { }
if got := getCondition ( db , tt . args . filter ) ; got != tt . want {
2020-10-02 14:21:51 +00:00
t . Errorf ( "getCondition() = %v, want %v" , got , tt . want )
}
} )
}
}
func Test_prepareColumns ( t * testing . T ) {
2020-10-05 18:39:36 +00:00
type fields struct {
dbRow [ ] interface { }
}
2020-10-02 14:21:51 +00:00
type args struct {
2020-10-05 17:09:26 +00:00
columns repository . Columns
2020-10-02 14:21:51 +00:00
dest interface { }
dbErr error
}
type res struct {
query string
expected interface { }
dbErr func ( error ) bool
}
tests := [ ] struct {
2020-10-05 18:39:36 +00:00
name string
args args
res res
fields fields
2020-10-02 14:21:51 +00:00
} {
{
name : "invalid columns" ,
2020-10-05 17:09:26 +00:00
args : args { columns : repository . Columns ( - 1 ) } ,
2020-10-02 14:21:51 +00:00
res : res {
query : "" ,
dbErr : func ( err error ) bool { return err == nil } ,
} ,
} ,
{
name : "max column" ,
args : args {
2020-10-06 19:28:09 +00:00
columns : repository . ColumnsMaxSequence ,
2020-10-02 14:21:51 +00:00
dest : new ( Sequence ) ,
} ,
res : res {
query : "SELECT MAX(event_sequence) FROM eventstore.events" ,
expected : Sequence ( 5 ) ,
} ,
2020-10-05 18:39:36 +00:00
fields : fields {
dbRow : [ ] interface { } { Sequence ( 5 ) } ,
} ,
2020-10-02 14:21:51 +00:00
} ,
{
name : "max sequence wrong dest type" ,
args : args {
2020-10-06 19:28:09 +00:00
columns : repository . ColumnsMaxSequence ,
2020-10-02 14:21:51 +00:00
dest : new ( uint64 ) ,
} ,
res : res {
query : "SELECT MAX(event_sequence) FROM eventstore.events" ,
dbErr : errors . IsErrorInvalidArgument ,
} ,
} ,
{
2020-10-05 18:39:36 +00:00
name : "events" ,
2020-10-02 14:21:51 +00:00
args : args {
2020-10-06 19:28:09 +00:00
columns : repository . ColumnsEvent ,
2020-10-05 18:39:36 +00:00
dest : & [ ] * repository . Event { } ,
2020-10-02 14:21:51 +00:00
} ,
res : res {
2022-03-23 08:02:39 +00:00
query : "SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, instance_id, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events" ,
2020-10-05 18:39:36 +00:00
expected : [ ] * repository . Event {
{ AggregateID : "hodor" , AggregateType : "user" , Sequence : 5 , Data : make ( Data , 0 ) } ,
} ,
} ,
fields : fields {
2022-04-12 14:20:17 +00:00
dbRow : [ ] interface { } { time . Time { } , repository . EventType ( "" ) , uint64 ( 5 ) , Sequence ( 0 ) , Sequence ( 0 ) , Data ( nil ) , "" , "" , sql . NullString { String : "" } , "" , repository . AggregateType ( "user" ) , "hodor" , repository . Version ( "" ) } ,
2020-10-02 14:21:51 +00:00
} ,
} ,
{
2020-10-05 18:39:36 +00:00
name : "events wrong dest type" ,
2020-10-02 14:21:51 +00:00
args : args {
2020-10-06 19:28:09 +00:00
columns : repository . ColumnsEvent ,
2020-10-05 18:39:36 +00:00
dest : [ ] * repository . Event { } ,
2020-10-02 14:21:51 +00:00
} ,
res : res {
2022-03-23 08:02:39 +00:00
query : "SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, instance_id, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events" ,
2020-10-02 14:21:51 +00:00
dbErr : errors . IsErrorInvalidArgument ,
} ,
} ,
{
name : "event query error" ,
args : args {
2020-10-06 19:28:09 +00:00
columns : repository . ColumnsEvent ,
2020-10-05 18:39:36 +00:00
dest : & [ ] * repository . Event { } ,
2020-10-02 14:21:51 +00:00
dbErr : sql . ErrConnDone ,
} ,
res : res {
2022-03-23 08:02:39 +00:00
query : "SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, instance_id, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events" ,
2020-10-02 14:21:51 +00:00
dbErr : errors . IsInternal ,
} ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
2020-10-05 18:39:36 +00:00
crdb := & CRDB { }
query , rowScanner := prepareColumns ( crdb , tt . args . columns )
2020-10-02 14:21:51 +00:00
if query != tt . res . query {
2020-10-05 18:39:36 +00:00
t . Errorf ( "prepareColumns() got = %s, want %s" , query , tt . res . query )
2020-10-02 14:21:51 +00:00
}
if tt . res . query == "" && rowScanner != nil {
t . Errorf ( "row scanner should be nil" )
}
if rowScanner == nil {
return
}
2020-10-05 18:39:36 +00:00
err := rowScanner ( prepareTestScan ( tt . args . dbErr , tt . fields . dbRow ) , tt . args . dest )
if err != nil && tt . res . dbErr == nil || err != nil && ! tt . res . dbErr ( err ) || err == nil && tt . res . dbErr != nil {
t . Errorf ( "wrong error type in rowScanner got: %v" , err )
return
}
if tt . res . dbErr != nil && tt . res . dbErr ( err ) {
return
}
if ! reflect . DeepEqual ( reflect . Indirect ( reflect . ValueOf ( tt . args . dest ) ) . Interface ( ) , tt . res . expected ) {
t . Errorf ( "unexpected result from rowScanner \nwant: %+v \ngot: %+v" , tt . fields . dbRow , reflect . Indirect ( reflect . ValueOf ( tt . args . dest ) ) . Interface ( ) )
2020-10-02 14:21:51 +00:00
}
} )
}
}
2020-10-05 17:09:26 +00:00
func prepareTestScan ( err error , res [ ] interface { } ) scan {
2020-10-02 14:21:51 +00:00
return func ( dests ... interface { } ) error {
if err != nil {
return err
}
if len ( dests ) != len ( res ) {
return errors . ThrowInvalidArgumentf ( nil , "SQL-NML1q" , "expected len %d got %d" , len ( res ) , len ( dests ) )
}
for i , r := range res {
reflect . ValueOf ( dests [ i ] ) . Elem ( ) . Set ( reflect . ValueOf ( r ) )
}
return nil
}
}
func Test_prepareCondition ( t * testing . T ) {
type args struct {
2021-07-06 11:55:57 +00:00
filters [ ] [ ] * repository . Filter
2020-10-02 14:21:51 +00:00
}
type res struct {
clause string
values [ ] interface { }
}
tests := [ ] struct {
name string
args args
res res
} {
{
name : "nil filters" ,
args : args {
filters : nil ,
} ,
res : res {
clause : "" ,
values : nil ,
} ,
} ,
{
name : "empty filters" ,
args : args {
2021-07-06 11:55:57 +00:00
filters : [ ] [ ] * repository . Filter { } ,
2020-10-02 14:21:51 +00:00
} ,
res : res {
clause : "" ,
values : nil ,
} ,
} ,
{
name : "invalid condition" ,
args : args {
2021-07-06 11:55:57 +00:00
filters : [ ] [ ] * repository . Filter {
{
repository . NewFilter ( repository . FieldAggregateID , "wrong" , repository . Operation ( - 1 ) ) ,
} ,
2020-10-02 14:21:51 +00:00
} ,
} ,
res : res {
clause : "" ,
values : nil ,
} ,
} ,
{
name : "array as condition value" ,
args : args {
2021-07-06 11:55:57 +00:00
filters : [ ] [ ] * repository . Filter {
{
repository . NewFilter ( repository . FieldAggregateType , [ ] repository . AggregateType { "user" , "org" } , repository . OperationIn ) ,
} ,
2020-10-02 14:21:51 +00:00
} ,
} ,
res : res {
2021-07-06 11:55:57 +00:00
clause : " WHERE ( aggregate_type = ANY(?) )" ,
2022-08-31 07:52:43 +00:00
values : [ ] interface { } { [ ] repository . AggregateType { "user" , "org" } } ,
2020-10-02 14:21:51 +00:00
} ,
} ,
{
name : "multiple filters" ,
args : args {
2021-07-06 11:55:57 +00:00
filters : [ ] [ ] * repository . Filter {
{
repository . NewFilter ( repository . FieldAggregateType , [ ] repository . AggregateType { "user" , "org" } , repository . OperationIn ) ,
repository . NewFilter ( repository . FieldAggregateID , "1234" , repository . OperationEquals ) ,
repository . NewFilter ( repository . FieldEventType , [ ] repository . EventType { "user.created" , "org.created" } , repository . OperationIn ) ,
} ,
2020-10-02 14:21:51 +00:00
} ,
} ,
res : res {
2021-07-06 11:55:57 +00:00
clause : " WHERE ( aggregate_type = ANY(?) AND aggregate_id = ? AND event_type = ANY(?) )" ,
2022-08-31 07:52:43 +00:00
values : [ ] interface { } { [ ] repository . AggregateType { "user" , "org" } , "1234" , [ ] repository . EventType { "user.created" , "org.created" } } ,
2020-10-02 14:21:51 +00:00
} ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
2020-10-05 18:39:36 +00:00
crdb := & CRDB { }
gotClause , gotValues := prepareCondition ( crdb , tt . args . filters )
2020-10-02 14:21:51 +00:00
if gotClause != tt . res . clause {
t . Errorf ( "prepareCondition() gotClause = %v, want %v" , gotClause , tt . res . clause )
}
if len ( gotValues ) != len ( tt . res . values ) {
t . Errorf ( "wrong length of gotten values got = %d, want %d" , len ( gotValues ) , len ( tt . res . values ) )
return
}
for i , value := range gotValues {
if ! reflect . DeepEqual ( value , tt . res . values [ i ] ) {
t . Errorf ( "prepareCondition() gotValues = %v, want %v" , gotValues , tt . res . values )
}
}
} )
}
}
2020-10-21 17:00:41 +00:00
func Test_query_events_with_crdb ( t * testing . T ) {
type args struct {
searchQuery * repository . SearchQuery
}
type fields struct {
existingEvents [ ] * repository . Event
client * sql . DB
}
type res struct {
eventCount int
}
tests := [ ] struct {
name string
fields fields
args args
res res
wantErr bool
} {
{
name : "aggregate type filter no events" ,
args : args {
searchQuery : & repository . SearchQuery {
Columns : repository . ColumnsEvent ,
2021-07-06 11:55:57 +00:00
Filters : [ ] [ ] * repository . Filter {
{
repository . NewFilter ( repository . FieldAggregateType , "not found" , repository . OperationEquals ) ,
} ,
2020-10-21 17:00:41 +00:00
} ,
} ,
} ,
fields : fields {
client : testCRDBClient ,
existingEvents : [ ] * repository . Event {
2021-01-15 08:32:59 +00:00
generateEvent ( t , "300" ) ,
generateEvent ( t , "300" ) ,
generateEvent ( t , "300" ) ,
2020-10-21 17:00:41 +00:00
} ,
} ,
res : res {
eventCount : 0 ,
} ,
wantErr : false ,
} ,
{
name : "aggregate type filter events found" ,
args : args {
searchQuery : & repository . SearchQuery {
Columns : repository . ColumnsEvent ,
2021-07-06 11:55:57 +00:00
Filters : [ ] [ ] * repository . Filter {
{
repository . NewFilter ( repository . FieldAggregateType , t . Name ( ) , repository . OperationEquals ) ,
} ,
2020-10-21 17:00:41 +00:00
} ,
} ,
} ,
fields : fields {
client : testCRDBClient ,
existingEvents : [ ] * repository . Event {
2021-01-15 08:32:59 +00:00
generateEvent ( t , "301" ) ,
generateEvent ( t , "302" ) ,
generateEvent ( t , "302" ) ,
generateEvent ( t , "303" , func ( e * repository . Event ) { e . AggregateType = "not in list" } ) ,
2020-10-21 17:00:41 +00:00
} ,
} ,
res : res {
eventCount : 3 ,
} ,
wantErr : false ,
} ,
{
name : "aggregate type and id filter events found" ,
args : args {
searchQuery : & repository . SearchQuery {
Columns : repository . ColumnsEvent ,
2021-07-06 11:55:57 +00:00
Filters : [ ] [ ] * repository . Filter {
{
repository . NewFilter ( repository . FieldAggregateType , t . Name ( ) , repository . OperationEquals ) ,
repository . NewFilter ( repository . FieldAggregateID , "303" , repository . OperationEquals ) ,
} ,
2020-10-21 17:00:41 +00:00
} ,
} ,
} ,
fields : fields {
client : testCRDBClient ,
existingEvents : [ ] * repository . Event {
2021-01-15 08:32:59 +00:00
generateEvent ( t , "303" ) ,
generateEvent ( t , "303" ) ,
generateEvent ( t , "303" ) ,
generateEvent ( t , "304" , func ( e * repository . Event ) { e . AggregateType = "not in list" } ) ,
generateEvent ( t , "305" ) ,
2020-10-21 17:00:41 +00:00
} ,
} ,
res : res {
eventCount : 3 ,
} ,
wantErr : false ,
} ,
{
name : "resource owner filter events found" ,
args : args {
searchQuery : & repository . SearchQuery {
Columns : repository . ColumnsEvent ,
2021-07-06 11:55:57 +00:00
Filters : [ ] [ ] * repository . Filter {
{
repository . NewFilter ( repository . FieldResourceOwner , "caos" , repository . OperationEquals ) ,
} ,
2020-10-21 17:00:41 +00:00
} ,
} ,
} ,
fields : fields {
client : testCRDBClient ,
existingEvents : [ ] * repository . Event {
2022-01-06 07:29:58 +00:00
generateEvent ( t , "306" , func ( e * repository . Event ) { e . ResourceOwner = sql . NullString { String : "caos" , Valid : true } } ) ,
generateEvent ( t , "307" , func ( e * repository . Event ) { e . ResourceOwner = sql . NullString { String : "caos" , Valid : true } } ) ,
generateEvent ( t , "308" , func ( e * repository . Event ) { e . ResourceOwner = sql . NullString { String : "caos" , Valid : true } } ) ,
generateEvent ( t , "309" , func ( e * repository . Event ) { e . ResourceOwner = sql . NullString { String : "orgID" , Valid : true } } ) ,
generateEvent ( t , "309" , func ( e * repository . Event ) { e . ResourceOwner = sql . NullString { String : "orgID" , Valid : true } } ) ,
2020-10-21 17:00:41 +00:00
} ,
} ,
res : res {
eventCount : 3 ,
} ,
wantErr : false ,
} ,
{
name : "editor service filter events found" ,
args : args {
searchQuery : & repository . SearchQuery {
Columns : repository . ColumnsEvent ,
2021-07-06 11:55:57 +00:00
Filters : [ ] [ ] * repository . Filter {
{
repository . NewFilter ( repository . FieldEditorService , "MANAGEMENT-API" , repository . OperationEquals ) ,
repository . NewFilter ( repository . FieldEditorService , "ADMIN-API" , repository . OperationEquals ) ,
} ,
2020-10-21 17:00:41 +00:00
} ,
} ,
} ,
fields : fields {
client : testCRDBClient ,
existingEvents : [ ] * repository . Event {
2021-01-15 08:32:59 +00:00
generateEvent ( t , "307" , func ( e * repository . Event ) { e . EditorService = "MANAGEMENT-API" } ) ,
generateEvent ( t , "307" , func ( e * repository . Event ) { e . EditorService = "MANAGEMENT-API" } ) ,
generateEvent ( t , "308" , func ( e * repository . Event ) { e . EditorService = "ADMIN-API" } ) ,
generateEvent ( t , "309" , func ( e * repository . Event ) { e . EditorService = "AUTHAPI" } ) ,
generateEvent ( t , "309" , func ( e * repository . Event ) { e . EditorService = "AUTHAPI" } ) ,
2020-10-21 17:00:41 +00:00
} ,
} ,
res : res {
eventCount : 3 ,
} ,
wantErr : false ,
} ,
{
name : "editor user filter events found" ,
args : args {
searchQuery : & repository . SearchQuery {
Columns : repository . ColumnsEvent ,
2021-07-06 11:55:57 +00:00
Filters : [ ] [ ] * repository . Filter {
{
repository . NewFilter ( repository . FieldEditorUser , "adlerhurst" , repository . OperationEquals ) ,
repository . NewFilter ( repository . FieldEditorUser , "nobody" , repository . OperationEquals ) ,
repository . NewFilter ( repository . FieldEditorUser , "" , repository . OperationEquals ) ,
} ,
2020-10-21 17:00:41 +00:00
} ,
} ,
} ,
fields : fields {
client : testCRDBClient ,
existingEvents : [ ] * repository . Event {
2021-01-15 08:32:59 +00:00
generateEvent ( t , "310" , func ( e * repository . Event ) { e . EditorUser = "adlerhurst" } ) ,
generateEvent ( t , "310" , func ( e * repository . Event ) { e . EditorUser = "adlerhurst" } ) ,
generateEvent ( t , "310" , func ( e * repository . Event ) { e . EditorUser = "nobody" } ) ,
generateEvent ( t , "311" , func ( e * repository . Event ) { e . EditorUser = "" } ) ,
generateEvent ( t , "311" , func ( e * repository . Event ) { e . EditorUser = "" } ) ,
generateEvent ( t , "312" , func ( e * repository . Event ) { e . EditorUser = "fforootd" } ) ,
generateEvent ( t , "312" , func ( e * repository . Event ) { e . EditorUser = "fforootd" } ) ,
2020-10-21 17:00:41 +00:00
} ,
} ,
res : res {
eventCount : 5 ,
} ,
wantErr : false ,
} ,
{
name : "event type filter events found" ,
args : args {
searchQuery : & repository . SearchQuery {
Columns : repository . ColumnsEvent ,
2021-07-06 11:55:57 +00:00
Filters : [ ] [ ] * repository . Filter {
{
repository . NewFilter ( repository . FieldEventType , repository . EventType ( "user.created" ) , repository . OperationEquals ) ,
repository . NewFilter ( repository . FieldEventType , repository . EventType ( "user.updated" ) , repository . OperationEquals ) ,
} ,
2020-10-21 17:00:41 +00:00
} ,
} ,
} ,
fields : fields {
client : testCRDBClient ,
existingEvents : [ ] * repository . Event {
2021-01-15 08:32:59 +00:00
generateEvent ( t , "311" , func ( e * repository . Event ) { e . Type = "user.created" } ) ,
generateEvent ( t , "311" , func ( e * repository . Event ) { e . Type = "user.updated" } ) ,
generateEvent ( t , "311" , func ( e * repository . Event ) { e . Type = "user.deactivated" } ) ,
generateEvent ( t , "311" , func ( e * repository . Event ) { e . Type = "user.locked" } ) ,
generateEvent ( t , "312" , func ( e * repository . Event ) { e . Type = "user.created" } ) ,
generateEvent ( t , "312" , func ( e * repository . Event ) { e . Type = "user.updated" } ) ,
generateEvent ( t , "312" , func ( e * repository . Event ) { e . Type = "user.deactivated" } ) ,
generateEvent ( t , "312" , func ( e * repository . Event ) { e . Type = "user.reactivated" } ) ,
generateEvent ( t , "313" , func ( e * repository . Event ) { e . Type = "user.locked" } ) ,
2020-10-21 17:00:41 +00:00
} ,
} ,
res : res {
eventCount : 7 ,
} ,
wantErr : false ,
} ,
{
name : "fail because no filter" ,
args : args {
searchQuery : & repository . SearchQuery { } ,
} ,
fields : fields {
client : testCRDBClient ,
existingEvents : [ ] * repository . Event { } ,
} ,
res : res {
eventCount : 0 ,
} ,
wantErr : true ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
db := & CRDB {
2023-02-27 21:36:43 +00:00
DB : & database . DB {
DB : tt . fields . client ,
Database : new ( testDB ) ,
} ,
2023-04-28 14:56:51 +00:00
AllowOrderByCreationDate : true ,
2020-10-21 17:00:41 +00:00
}
// setup initial data for query
2021-05-03 08:15:50 +00:00
if err := db . Push ( context . Background ( ) , tt . fields . existingEvents ) ; err != nil {
2020-10-21 17:00:41 +00:00
t . Errorf ( "error in setup = %v" , err )
return
}
events := [ ] * repository . Event { }
if err := query ( context . Background ( ) , db , tt . args . searchQuery , & events ) ; ( err != nil ) != tt . wantErr {
t . Errorf ( "CRDB.query() error = %v, wantErr %v" , err , tt . wantErr )
}
} )
}
}
func Test_query_events_mocked ( t * testing . T ) {
2020-10-02 14:21:51 +00:00
type args struct {
2020-10-05 18:39:36 +00:00
query * repository . SearchQuery
2020-10-21 17:00:41 +00:00
dest interface { }
2020-10-02 14:21:51 +00:00
}
type res struct {
2020-10-21 17:00:41 +00:00
wantErr bool
}
type fields struct {
mock * dbMock
2020-10-02 14:21:51 +00:00
}
tests := [ ] struct {
2020-10-21 17:00:41 +00:00
name string
args args
fields fields
res res
2020-10-02 14:21:51 +00:00
} {
{
name : "with order by desc" ,
args : args {
2020-10-21 17:00:41 +00:00
dest : & [ ] * repository . Event { } ,
2020-10-05 18:39:36 +00:00
query : & repository . SearchQuery {
2020-10-06 19:28:09 +00:00
Columns : repository . ColumnsEvent ,
2020-10-05 18:39:36 +00:00
Desc : true ,
2021-07-06 11:55:57 +00:00
Filters : [ ] [ ] * repository . Filter {
2020-10-05 18:39:36 +00:00
{
2021-07-06 11:55:57 +00:00
{
Field : repository . FieldAggregateType ,
Value : repository . AggregateType ( "user" ) ,
Operation : repository . OperationEquals ,
} ,
2020-10-05 18:39:36 +00:00
} ,
} ,
} ,
2020-10-02 14:21:51 +00:00
} ,
2020-10-21 17:00:41 +00:00
fields : fields {
mock : newMockClient ( t ) . expectQuery ( t ,
2023-04-05 09:34:24 +00:00
` SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, instance_id, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE \( aggregate_type = \$1 \) ORDER BY creation_date DESC, event_sequence DESC ` ,
2020-10-21 17:00:41 +00:00
[ ] driver . Value { repository . AggregateType ( "user" ) } ,
) ,
} ,
2020-10-02 14:21:51 +00:00
res : res {
2020-10-21 17:00:41 +00:00
wantErr : false ,
2020-10-02 14:21:51 +00:00
} ,
} ,
{
name : "with limit" ,
args : args {
2020-10-21 17:00:41 +00:00
dest : & [ ] * repository . Event { } ,
2020-10-05 18:39:36 +00:00
query : & repository . SearchQuery {
2020-10-06 19:28:09 +00:00
Columns : repository . ColumnsEvent ,
2020-10-05 18:39:36 +00:00
Desc : false ,
Limit : 5 ,
2021-07-06 11:55:57 +00:00
Filters : [ ] [ ] * repository . Filter {
2020-10-05 18:39:36 +00:00
{
2021-07-06 11:55:57 +00:00
{
Field : repository . FieldAggregateType ,
Value : repository . AggregateType ( "user" ) ,
Operation : repository . OperationEquals ,
} ,
2020-10-05 18:39:36 +00:00
} ,
} ,
} ,
2020-10-02 14:21:51 +00:00
} ,
2020-10-21 17:00:41 +00:00
fields : fields {
mock : newMockClient ( t ) . expectQuery ( t ,
2023-04-05 09:34:24 +00:00
` SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, instance_id, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE \( aggregate_type = \$1 \) ORDER BY creation_date, event_sequence LIMIT \$2 ` ,
2020-10-21 17:00:41 +00:00
[ ] driver . Value { repository . AggregateType ( "user" ) , uint64 ( 5 ) } ,
) ,
} ,
2020-10-02 14:21:51 +00:00
res : res {
2020-10-21 17:00:41 +00:00
wantErr : false ,
2020-10-02 14:21:51 +00:00
} ,
} ,
{
name : "with limit and order by desc" ,
args : args {
2020-10-21 17:00:41 +00:00
dest : & [ ] * repository . Event { } ,
2020-10-05 18:39:36 +00:00
query : & repository . SearchQuery {
2020-10-06 19:28:09 +00:00
Columns : repository . ColumnsEvent ,
2020-10-05 18:39:36 +00:00
Desc : true ,
Limit : 5 ,
2021-07-06 11:55:57 +00:00
Filters : [ ] [ ] * repository . Filter {
2020-10-05 18:39:36 +00:00
{
2021-07-06 11:55:57 +00:00
{
Field : repository . FieldAggregateType ,
Value : repository . AggregateType ( "user" ) ,
Operation : repository . OperationEquals ,
} ,
2020-10-05 18:39:36 +00:00
} ,
} ,
} ,
2020-10-02 14:21:51 +00:00
} ,
2020-10-21 17:00:41 +00:00
fields : fields {
mock : newMockClient ( t ) . expectQuery ( t ,
2023-04-05 09:34:24 +00:00
` SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, instance_id, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE \( aggregate_type = \$1 \) ORDER BY creation_date DESC, event_sequence DESC LIMIT \$2 ` ,
2020-10-21 17:00:41 +00:00
[ ] driver . Value { repository . AggregateType ( "user" ) , uint64 ( 5 ) } ,
) ,
} ,
res : res {
wantErr : false ,
} ,
} ,
2023-02-27 21:36:43 +00:00
{
name : "with limit and order by desc as of system time" ,
args : args {
dest : & [ ] * repository . Event { } ,
query : & repository . SearchQuery {
Columns : repository . ColumnsEvent ,
Desc : true ,
Limit : 5 ,
AllowTimeTravel : true ,
Filters : [ ] [ ] * repository . Filter {
{
{
Field : repository . FieldAggregateType ,
Value : repository . AggregateType ( "user" ) ,
Operation : repository . OperationEquals ,
} ,
} ,
} ,
} ,
} ,
fields : fields {
mock : newMockClient ( t ) . expectQuery ( t ,
2023-04-05 09:34:24 +00:00
` SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, instance_id, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events AS OF SYSTEM TIME '-1 ms' WHERE \( aggregate_type = \$1 \) ORDER BY creation_date DESC, event_sequence DESC LIMIT \$2 ` ,
2023-02-27 21:36:43 +00:00
[ ] driver . Value { repository . AggregateType ( "user" ) , uint64 ( 5 ) } ,
) ,
} ,
res : res {
wantErr : false ,
} ,
} ,
2020-10-21 17:00:41 +00:00
{
name : "error sql conn closed" ,
args : args {
dest : & [ ] * repository . Event { } ,
query : & repository . SearchQuery {
Columns : repository . ColumnsEvent ,
Desc : true ,
Limit : 0 ,
2021-07-06 11:55:57 +00:00
Filters : [ ] [ ] * repository . Filter {
2020-10-21 17:00:41 +00:00
{
2021-07-06 11:55:57 +00:00
{
Field : repository . FieldAggregateType ,
Value : repository . AggregateType ( "user" ) ,
Operation : repository . OperationEquals ,
} ,
2020-10-21 17:00:41 +00:00
} ,
} ,
} ,
} ,
fields : fields {
mock : newMockClient ( t ) . expectQueryErr ( t ,
2023-04-05 09:34:24 +00:00
` SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, instance_id, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE \( aggregate_type = \$1 \) ORDER BY creation_date DESC, event_sequence DESC ` ,
2020-10-21 17:00:41 +00:00
[ ] driver . Value { repository . AggregateType ( "user" ) } ,
sql . ErrConnDone ) ,
} ,
res : res {
wantErr : true ,
} ,
} ,
{
name : "error unexpected dest" ,
args : args {
dest : nil ,
query : & repository . SearchQuery {
Columns : repository . ColumnsEvent ,
Desc : true ,
Limit : 0 ,
2021-07-06 11:55:57 +00:00
Filters : [ ] [ ] * repository . Filter {
2020-10-21 17:00:41 +00:00
{
2021-07-06 11:55:57 +00:00
{
Field : repository . FieldAggregateType ,
Value : repository . AggregateType ( "user" ) ,
Operation : repository . OperationEquals ,
} ,
2020-10-21 17:00:41 +00:00
} ,
} ,
} ,
} ,
fields : fields {
2023-08-22 12:49:02 +00:00
mock : newMockClient ( t ) . expectQueryScanErr ( t ,
2023-04-05 09:34:24 +00:00
` SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, instance_id, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE \( aggregate_type = \$1 \) ORDER BY creation_date DESC, event_sequence DESC ` ,
2020-10-21 17:00:41 +00:00
[ ] driver . Value { repository . AggregateType ( "user" ) } ,
& repository . Event { Sequence : 100 } ) ,
} ,
2020-10-02 14:21:51 +00:00
res : res {
2020-10-21 17:00:41 +00:00
wantErr : true ,
2020-10-02 14:21:51 +00:00
} ,
} ,
2020-10-05 20:03:21 +00:00
{
name : "error no columns" ,
args : args {
query : & repository . SearchQuery {
Columns : repository . Columns ( - 1 ) ,
} ,
} ,
res : res {
2020-10-21 17:00:41 +00:00
wantErr : true ,
2020-10-05 20:03:21 +00:00
} ,
} ,
{
name : "invalid condition" ,
args : args {
query : & repository . SearchQuery {
2020-10-06 19:28:09 +00:00
Columns : repository . ColumnsEvent ,
2021-07-06 11:55:57 +00:00
Filters : [ ] [ ] * repository . Filter {
{
{ } ,
} ,
2020-10-05 20:03:21 +00:00
} ,
} ,
} ,
res : res {
2020-10-21 17:00:41 +00:00
wantErr : true ,
2020-10-05 20:03:21 +00:00
} ,
} ,
2021-07-06 11:55:57 +00:00
{
name : "with subqueries" ,
args : args {
dest : & [ ] * repository . Event { } ,
query : & repository . SearchQuery {
Columns : repository . ColumnsEvent ,
Desc : true ,
Limit : 5 ,
Filters : [ ] [ ] * repository . Filter {
{
{
Field : repository . FieldAggregateType ,
Value : repository . AggregateType ( "user" ) ,
Operation : repository . OperationEquals ,
} ,
} ,
{
{
Field : repository . FieldAggregateType ,
Value : repository . AggregateType ( "org" ) ,
Operation : repository . OperationEquals ,
} ,
{
Field : repository . FieldAggregateID ,
Value : "asdf42" ,
Operation : repository . OperationEquals ,
} ,
} ,
} ,
} ,
} ,
fields : fields {
mock : newMockClient ( t ) . expectQuery ( t ,
2023-04-05 09:34:24 +00:00
` SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, instance_id, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE \( aggregate_type = \$1 \) OR \( aggregate_type = \$2 AND aggregate_id = \$3 \) ORDER BY creation_date DESC, event_sequence DESC LIMIT \$4 ` ,
2021-07-06 11:55:57 +00:00
[ ] driver . Value { repository . AggregateType ( "user" ) , repository . AggregateType ( "org" ) , "asdf42" , uint64 ( 5 ) } ,
) ,
} ,
res : res {
wantErr : false ,
} ,
} ,
2020-10-02 14:21:51 +00:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
2023-04-28 14:56:51 +00:00
crdb := & CRDB {
DB : & database . DB {
Database : new ( testDB ) ,
} ,
AllowOrderByCreationDate : true ,
}
2020-10-21 17:00:41 +00:00
if tt . fields . mock != nil {
2023-02-27 21:36:43 +00:00
crdb . DB . DB = tt . fields . mock . client
2020-10-02 14:21:51 +00:00
}
2020-10-21 17:00:41 +00:00
err := query ( context . Background ( ) , crdb , tt . args . query , tt . args . dest )
if ( err != nil ) != tt . res . wantErr {
t . Errorf ( "query() error = %v, wantErr %v" , err , tt . res . wantErr )
2020-10-02 14:21:51 +00:00
}
2020-10-21 17:00:41 +00:00
if tt . fields . mock == nil {
return
2020-10-02 14:21:51 +00:00
}
2020-10-21 17:00:41 +00:00
if err := tt . fields . mock . mock . ExpectationsWereMet ( ) ; err != nil {
t . Errorf ( "not all expectaions met: %v" , err )
2020-10-02 14:21:51 +00:00
}
} )
}
}
2020-10-21 17:00:41 +00:00
type dbMock struct {
mock sqlmock . Sqlmock
client * sql . DB
}
func ( m * dbMock ) expectQuery ( t * testing . T , expectedQuery string , args [ ] driver . Value , events ... * repository . Event ) * dbMock {
2023-08-22 12:49:02 +00:00
m . mock . ExpectBegin ( )
query := m . mock . ExpectQuery ( expectedQuery ) . WithArgs ( args ... )
m . mock . ExpectCommit ( )
rows := sqlmock . NewRows ( [ ] string { "event_sequence" } )
for _ , event := range events {
rows = rows . AddRow ( event . Sequence )
}
query . WillReturnRows ( rows ) . RowsWillBeClosed ( )
return m
}
func ( m * dbMock ) expectQueryScanErr ( t * testing . T , expectedQuery string , args [ ] driver . Value , events ... * repository . Event ) * dbMock {
m . mock . ExpectBegin ( )
2020-10-21 17:00:41 +00:00
query := m . mock . ExpectQuery ( expectedQuery ) . WithArgs ( args ... )
2023-08-22 12:49:02 +00:00
m . mock . ExpectRollback ( )
2020-10-21 17:00:41 +00:00
rows := sqlmock . NewRows ( [ ] string { "event_sequence" } )
for _ , event := range events {
rows = rows . AddRow ( event . Sequence )
}
query . WillReturnRows ( rows ) . RowsWillBeClosed ( )
return m
}
func ( m * dbMock ) expectQueryErr ( t * testing . T , expectedQuery string , args [ ] driver . Value , err error ) * dbMock {
2023-08-22 12:49:02 +00:00
m . mock . ExpectBegin ( )
2020-10-21 17:00:41 +00:00
m . mock . ExpectQuery ( expectedQuery ) . WithArgs ( args ... ) . WillReturnError ( err )
return m
}
func newMockClient ( t * testing . T ) * dbMock {
t . Helper ( )
db , mock , err := sqlmock . New ( )
if err != nil {
t . Errorf ( "unable to create mock client: %v" , err )
t . FailNow ( )
return nil
}
return & dbMock {
mock : mock ,
client : db ,
}
}