package command import ( "context" "encoding/json" "fmt" "testing" "time" "github.com/stretchr/testify/assert" zitadel_errors "github.com/zitadel/zitadel/internal/errors" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/id" id_mock "github.com/zitadel/zitadel/internal/id/mock" "github.com/zitadel/zitadel/internal/repository/quota" ) func TestQuotaWriteModel_NewChanges(t *testing.T) { type fields struct { from time.Time resetInterval time.Duration amount uint64 limit bool notifications []*quota.SetEventNotification } type args struct { idGenerator id.Generator createNew bool amount uint64 from time.Time resetInterval time.Duration limit bool notifications []*QuotaNotification } tests := []struct { name string fields fields args args wantEvent quota.SetEvent wantChanges int wantErr assert.ErrorAssertionFunc }{{ name: "change reset interval", fields: fields{ amount: 5, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), resetInterval: time.Hour, limit: true, notifications: make([]*quota.SetEventNotification, 0), }, args: args{ amount: 5, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), resetInterval: time.Minute, limit: true, notifications: make([]*QuotaNotification, 0), }, wantChanges: 1, wantEvent: quota.SetEvent{ ResetInterval: durationPtr(time.Minute), }, }, { name: "change reset interval and amount", fields: fields{ amount: 5, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), resetInterval: time.Hour, limit: true, notifications: make([]*quota.SetEventNotification, 0), }, args: args{ amount: 10, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), resetInterval: time.Minute, limit: true, notifications: make([]*QuotaNotification, 0), }, wantChanges: 2, wantEvent: quota.SetEvent{ ResetInterval: durationPtr(time.Minute), Amount: uint64Ptr(10), }, }, { name: "change nothing", fields: fields{ amount: 5, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), resetInterval: time.Hour, limit: true, notifications: []*quota.SetEventNotification{}, }, args: args{ amount: 5, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), resetInterval: time.Hour, limit: true, notifications: []*QuotaNotification{}, }, }, { name: "change limit to zero value", fields: fields{ amount: 5, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), resetInterval: time.Hour, limit: true, notifications: make([]*quota.SetEventNotification, 0), }, args: args{ amount: 5, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), resetInterval: time.Hour, limit: false, notifications: make([]*QuotaNotification, 0), }, wantChanges: 1, wantEvent: quota.SetEvent{Limit: boolPtr(false)}, }, { name: "change amount to zero value", fields: fields{ amount: 5, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), resetInterval: time.Hour, limit: true, notifications: make([]*quota.SetEventNotification, 0), }, args: args{ amount: 0, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), resetInterval: time.Hour, limit: true, notifications: make([]*QuotaNotification, 0), }, wantChanges: 1, wantEvent: quota.SetEvent{Amount: uint64Ptr(0)}, }, { name: "change from to zero value", fields: fields{ amount: 5, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), resetInterval: time.Hour, limit: true, notifications: make([]*quota.SetEventNotification, 0), }, args: args{ amount: 5, from: time.Time{}, resetInterval: time.Hour, limit: true, notifications: make([]*QuotaNotification, 0), }, wantChanges: 1, wantEvent: quota.SetEvent{From: &time.Time{}}, }, { name: "add notification", fields: fields{ amount: 5, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), resetInterval: time.Hour, limit: true, }, args: args{ amount: 5, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), resetInterval: time.Hour, limit: true, notifications: []*QuotaNotification{{ Percent: 10, Repeat: false, CallURL: "https://call.url", }}, idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "notification1"), }, wantChanges: 1, wantEvent: quota.SetEvent{Notifications: &[]*quota.SetEventNotification{{ ID: "notification1", Percent: 10, Repeat: false, CallURL: "https://call.url", }}}, }, { name: "change nothing with notification", fields: fields{ amount: 5, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), resetInterval: time.Hour, limit: true, notifications: []*quota.SetEventNotification{{ ID: "notification1", Percent: 10, Repeat: true, CallURL: "https://call.url", }}, }, args: args{ amount: 5, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), resetInterval: time.Hour, limit: true, notifications: []*QuotaNotification{{ Percent: 10, Repeat: true, CallURL: "https://call.url", }}, idGenerator: id_mock.NewIDGenerator(t), }, }, { name: "don't change notification order", fields: fields{ amount: 5, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), resetInterval: time.Hour, limit: true, notifications: []*quota.SetEventNotification{{ ID: "notification1", Percent: 10, Repeat: true, CallURL: "https://call.url", }, { ID: "notification2", Percent: 10, Repeat: false, CallURL: "https://call.url", }}, }, args: args{ amount: 5, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), resetInterval: time.Hour, limit: true, notifications: []*QuotaNotification{{ Percent: 10, Repeat: false, CallURL: "https://call.url", }, { Percent: 10, Repeat: true, CallURL: "https://call.url", }}, idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "newnotification1", "newnotification2"), }, }, { name: "change notification to zero value", fields: fields{ amount: 5, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), resetInterval: time.Hour, limit: true, notifications: []*quota.SetEventNotification{{ ID: "notification1", Percent: 10, Repeat: true, CallURL: "https://call.url", }}, }, args: args{ amount: 5, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), resetInterval: time.Hour, limit: true, notifications: []*QuotaNotification{}, }, wantChanges: 1, wantEvent: quota.SetEvent{Notifications: &[]*quota.SetEventNotification{}}, }, { name: "validate no duplicate notifications", args: args{ notifications: []*QuotaNotification{{ Percent: 10, Repeat: false, CallURL: "https://call.url", }, { Percent: 10, Repeat: false, CallURL: "https://call.url", }}, idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "notification1", "notification2"), }, wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { return zitadel_errors.IsErrorInvalidArgument(err) }, }, { name: "deduplicate existing notifications", fields: fields{ notifications: []*quota.SetEventNotification{{ ID: "existingnotification1", Percent: 10, Repeat: false, CallURL: "https://call.url", }, { ID: "existingnotification2", Percent: 10, Repeat: true, CallURL: "https://call.url", }, { ID: "existingnotification3", Percent: 10, Repeat: true, CallURL: "https://call.url", }}, }, args: args{ notifications: []*QuotaNotification{{ Percent: 10, Repeat: false, CallURL: "https://call.url", }, { Percent: 10, Repeat: true, CallURL: "https://call.url", }}, idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "notification1", "notification2"), }, wantChanges: 1, wantEvent: quota.SetEvent{ Notifications: &[]*quota.SetEventNotification{{ ID: "notification1", Percent: 10, Repeat: false, CallURL: "https://call.url", }, { ID: "notification2", Percent: 10, Repeat: true, CallURL: "https://call.url", }}}, }, { name: "create new with all values", args: args{ amount: 5, from: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), resetInterval: time.Hour, limit: true, notifications: []*QuotaNotification{{ Percent: 10, Repeat: true, CallURL: "https://call.url", }}, idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "notification1"), createNew: true, }, wantChanges: 5, wantEvent: quota.SetEvent{ From: timePtr(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)), ResetInterval: durationPtr(time.Hour), Amount: uint64Ptr(5), Limit: boolPtr(true), Notifications: &[]*quota.SetEventNotification{{ ID: "notification1", Percent: 10, Repeat: true, CallURL: "https://call.url", }}, }, }, { name: "create new with zero values", args: args{createNew: true}, wantChanges: 5, wantEvent: quota.SetEvent{ From: &time.Time{}, ResetInterval: durationPtr(0), Amount: uint64Ptr(0), Limit: boolPtr(false), Notifications: &[]*quota.SetEventNotification{}, }, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { wm := "aWriteModel{ from: tt.fields.from, resetInterval: tt.fields.resetInterval, amount: tt.fields.amount, limit: tt.fields.limit, notifications: tt.fields.notifications, } gotChanges, err := wm.NewChanges(tt.args.idGenerator, tt.args.createNew, tt.args.amount, tt.args.from, tt.args.resetInterval, tt.args.limit, tt.args.notifications...) assert.Len(t, gotChanges, tt.wantChanges) if tt.wantErr != nil { tt.wantErr(t, err, fmt.Sprintf("NewChanges(%v, %v, %v, %v, %v, %v)", tt.args.createNew, tt.args.amount, tt.args.from, tt.args.resetInterval, tt.args.limit, tt.args.notifications)) return } assert.NoError(t, err) marshalled, err := json.Marshal(quota.NewSetEvent( eventstore.NewBaseEventForPush( context.Background(), "a.NewAggregate("quota1", "instance1").Aggregate, quota.SetEventType, ), quota.Unimplemented, gotChanges..., )) assert.NoError(t, err) unmarshalled := new(quota.SetEvent) assert.NoError(t, json.Unmarshal(marshalled, unmarshalled)) assert.Equalf(t, tt.wantEvent, *unmarshalled, "NewChanges(%v, %v, %v, %v, %v, %v)", tt.args.createNew, tt.args.amount, tt.args.from, tt.args.resetInterval, tt.args.limit, tt.args.notifications) }) } } func uint64Ptr(i uint64) *uint64 { return &i } func boolPtr(b bool) *bool { return &b } func durationPtr(d time.Duration) *time.Duration { return &d } func timePtr(t time.Time) *time.Time { return &t }