mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-12 11:04:25 +00:00
b5564572bc
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.
423 lines
12 KiB
Go
423 lines
12 KiB
Go
package projection
|
|
|
|
import (
|
|
"context"
|
|
"regexp"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/DATA-DOG/go-sqlmock"
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/zitadel/zitadel/internal/database"
|
|
"github.com/zitadel/zitadel/internal/errors"
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
|
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
|
"github.com/zitadel/zitadel/internal/repository/instance"
|
|
"github.com/zitadel/zitadel/internal/repository/quota"
|
|
)
|
|
|
|
func TestQuotasProjection_reduces(t *testing.T) {
|
|
type args struct {
|
|
event func(t *testing.T) eventstore.Event
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
reduce func(event eventstore.Event) (*handler.Statement, error)
|
|
want wantReduce
|
|
}{
|
|
{
|
|
name: "reduceQuotaSet with added type",
|
|
args: args{
|
|
event: getEvent(testEvent(
|
|
quota.AddedEventType,
|
|
quota.AggregateType,
|
|
[]byte(`{
|
|
"unit": 1,
|
|
"amount": 10,
|
|
"limit": true,
|
|
"from": "2023-01-01T00:00:00Z",
|
|
"interval": 300000000000
|
|
}`),
|
|
), quota.SetEventMapper),
|
|
},
|
|
reduce: ("aProjection{}).reduceQuotaSet,
|
|
want: wantReduce{
|
|
aggregateType: eventstore.AggregateType("quota"),
|
|
sequence: 15,
|
|
executer: &testExecuter{
|
|
executions: []execution{
|
|
{
|
|
expectedStmt: "INSERT INTO projections.quotas (limit_usage, amount, from_anchor, interval, id, instance_id, unit) VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (instance_id, unit) DO UPDATE SET (limit_usage, amount, from_anchor, interval, id) = (EXCLUDED.limit_usage, EXCLUDED.amount, EXCLUDED.from_anchor, EXCLUDED.interval, EXCLUDED.id)",
|
|
expectedArgs: []interface{}{
|
|
true,
|
|
uint64(10),
|
|
time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
time.Minute * 5,
|
|
"agg-id",
|
|
"instance-id",
|
|
quota.RequestsAllAuthenticated,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "reduceQuotaAdded with added type and notification",
|
|
args: args{
|
|
event: getEvent(testEvent(
|
|
quota.AddedEventType,
|
|
quota.AggregateType,
|
|
[]byte(`{
|
|
"unit": 1,
|
|
"amount": 10,
|
|
"limit": true,
|
|
"from": "2023-01-01T00:00:00Z",
|
|
"interval": 300000000000,
|
|
"notifications": [
|
|
{
|
|
"id": "id",
|
|
"percent": 100,
|
|
"repeat": true,
|
|
"callURL": "url"
|
|
}
|
|
]
|
|
}`),
|
|
), quota.SetEventMapper),
|
|
},
|
|
reduce: ("aProjection{}).reduceQuotaSet,
|
|
want: wantReduce{
|
|
aggregateType: eventstore.AggregateType("quota"),
|
|
sequence: 15,
|
|
executer: &testExecuter{
|
|
executions: []execution{
|
|
{
|
|
expectedStmt: "INSERT INTO projections.quotas (limit_usage, amount, from_anchor, interval, id, instance_id, unit) VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (instance_id, unit) DO UPDATE SET (limit_usage, amount, from_anchor, interval, id) = (EXCLUDED.limit_usage, EXCLUDED.amount, EXCLUDED.from_anchor, EXCLUDED.interval, EXCLUDED.id)",
|
|
expectedArgs: []interface{}{
|
|
true,
|
|
uint64(10),
|
|
time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
time.Minute * 5,
|
|
"agg-id",
|
|
"instance-id",
|
|
quota.RequestsAllAuthenticated,
|
|
},
|
|
},
|
|
{
|
|
expectedStmt: "DELETE FROM projections.quotas_notifications WHERE (instance_id = $1) AND (unit = $2)",
|
|
expectedArgs: []interface{}{
|
|
"instance-id",
|
|
quota.RequestsAllAuthenticated,
|
|
},
|
|
},
|
|
{
|
|
expectedStmt: "INSERT INTO projections.quotas_notifications (instance_id, unit, id, call_url, percent, repeat) VALUES ($1, $2, $3, $4, $5, $6)",
|
|
expectedArgs: []interface{}{
|
|
"instance-id",
|
|
quota.RequestsAllAuthenticated,
|
|
"id",
|
|
"url",
|
|
uint16(100),
|
|
true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "reduceQuotaSet with set type",
|
|
args: args{
|
|
event: getEvent(testEvent(
|
|
quota.SetEventType,
|
|
quota.AggregateType,
|
|
[]byte(`{
|
|
"unit": 1,
|
|
"amount": 10,
|
|
"limit": true,
|
|
"from": "2023-01-01T00:00:00Z",
|
|
"interval": 300000000000
|
|
}`),
|
|
), quota.SetEventMapper),
|
|
},
|
|
reduce: ("aProjection{}).reduceQuotaSet,
|
|
want: wantReduce{
|
|
aggregateType: eventstore.AggregateType("quota"),
|
|
sequence: 15,
|
|
executer: &testExecuter{
|
|
executions: []execution{
|
|
{
|
|
expectedStmt: "INSERT INTO projections.quotas (limit_usage, amount, from_anchor, interval, id, instance_id, unit) VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (instance_id, unit) DO UPDATE SET (limit_usage, amount, from_anchor, interval, id) = (EXCLUDED.limit_usage, EXCLUDED.amount, EXCLUDED.from_anchor, EXCLUDED.interval, EXCLUDED.id)",
|
|
expectedArgs: []interface{}{
|
|
true,
|
|
uint64(10),
|
|
time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
time.Minute * 5,
|
|
"agg-id",
|
|
"instance-id",
|
|
quota.RequestsAllAuthenticated,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "reduceQuotaAdded with set type and notification",
|
|
args: args{
|
|
event: getEvent(testEvent(
|
|
quota.SetEventType,
|
|
quota.AggregateType,
|
|
[]byte(`{
|
|
"unit": 1,
|
|
"amount": 10,
|
|
"limit": true,
|
|
"from": "2023-01-01T00:00:00Z",
|
|
"interval": 300000000000,
|
|
"notifications": [
|
|
{
|
|
"id": "id",
|
|
"percent": 100,
|
|
"repeat": true,
|
|
"callURL": "url"
|
|
}
|
|
]
|
|
}`),
|
|
), quota.SetEventMapper),
|
|
},
|
|
reduce: ("aProjection{}).reduceQuotaSet,
|
|
want: wantReduce{
|
|
aggregateType: eventstore.AggregateType("quota"),
|
|
sequence: 15,
|
|
executer: &testExecuter{
|
|
executions: []execution{
|
|
{
|
|
expectedStmt: "INSERT INTO projections.quotas (limit_usage, amount, from_anchor, interval, id, instance_id, unit) VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (instance_id, unit) DO UPDATE SET (limit_usage, amount, from_anchor, interval, id) = (EXCLUDED.limit_usage, EXCLUDED.amount, EXCLUDED.from_anchor, EXCLUDED.interval, EXCLUDED.id)",
|
|
expectedArgs: []interface{}{
|
|
true,
|
|
uint64(10),
|
|
time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
time.Minute * 5,
|
|
"agg-id",
|
|
"instance-id",
|
|
quota.RequestsAllAuthenticated,
|
|
},
|
|
},
|
|
{
|
|
expectedStmt: "DELETE FROM projections.quotas_notifications WHERE (instance_id = $1) AND (unit = $2)",
|
|
expectedArgs: []interface{}{
|
|
"instance-id",
|
|
quota.RequestsAllAuthenticated,
|
|
},
|
|
},
|
|
{
|
|
expectedStmt: "INSERT INTO projections.quotas_notifications (instance_id, unit, id, call_url, percent, repeat) VALUES ($1, $2, $3, $4, $5, $6)",
|
|
expectedArgs: []interface{}{
|
|
"instance-id",
|
|
quota.RequestsAllAuthenticated,
|
|
"id",
|
|
"url",
|
|
uint16(100),
|
|
true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "reduceQuotaNotificationDue",
|
|
args: args{
|
|
event: getEvent(testEvent(
|
|
quota.NotificationDueEventType,
|
|
quota.AggregateType,
|
|
[]byte(`{
|
|
"id": "id",
|
|
"unit": 1,
|
|
"callURL": "url",
|
|
"periodStart": "2023-01-01T00:00:00Z",
|
|
"threshold": 200,
|
|
"usage": 100
|
|
}`),
|
|
), quota.NotificationDueEventMapper),
|
|
},
|
|
reduce: ("aProjection{}).reduceQuotaNotificationDue,
|
|
want: wantReduce{
|
|
aggregateType: eventstore.AggregateType("quota"),
|
|
sequence: 15,
|
|
executer: &testExecuter{
|
|
executions: []execution{
|
|
{
|
|
expectedStmt: "UPDATE projections.quotas_notifications SET (latest_due_period_start, next_due_threshold) = ($1, $2) WHERE (instance_id = $3) AND (unit = $4) AND (id = $5)",
|
|
expectedArgs: []interface{}{
|
|
time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
uint16(300),
|
|
"instance-id",
|
|
quota.RequestsAllAuthenticated,
|
|
"id",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "reduceQuotaRemoved",
|
|
args: args{
|
|
event: getEvent(testEvent(
|
|
quota.RemovedEventType,
|
|
quota.AggregateType,
|
|
[]byte(`{
|
|
"unit": 1
|
|
}`),
|
|
), quota.RemovedEventMapper),
|
|
},
|
|
reduce: ("aProjection{}).reduceQuotaRemoved,
|
|
want: wantReduce{
|
|
aggregateType: eventstore.AggregateType("quota"),
|
|
sequence: 15,
|
|
executer: &testExecuter{
|
|
executions: []execution{
|
|
{
|
|
expectedStmt: "DELETE FROM projections.quotas_periods WHERE (instance_id = $1) AND (unit = $2)",
|
|
expectedArgs: []interface{}{
|
|
"instance-id",
|
|
quota.RequestsAllAuthenticated,
|
|
},
|
|
},
|
|
{
|
|
expectedStmt: "DELETE FROM projections.quotas_notifications WHERE (instance_id = $1) AND (unit = $2)",
|
|
expectedArgs: []interface{}{
|
|
"instance-id",
|
|
quota.RequestsAllAuthenticated,
|
|
},
|
|
},
|
|
{
|
|
expectedStmt: "DELETE FROM projections.quotas WHERE (instance_id = $1) AND (unit = $2)",
|
|
expectedArgs: []interface{}{
|
|
"instance-id",
|
|
quota.RequestsAllAuthenticated,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, {
|
|
name: "reduceInstanceRemoved",
|
|
args: args{
|
|
event: getEvent(testEvent(
|
|
instance.InstanceRemovedEventType,
|
|
instance.AggregateType,
|
|
[]byte(`{
|
|
"name": "name"
|
|
}`),
|
|
), instance.InstanceRemovedEventMapper),
|
|
},
|
|
reduce: ("aProjection{}).reduceInstanceRemoved,
|
|
want: wantReduce{
|
|
aggregateType: eventstore.AggregateType("instance"),
|
|
sequence: 15,
|
|
executer: &testExecuter{
|
|
executions: []execution{
|
|
{
|
|
expectedStmt: "DELETE FROM projections.quotas_periods WHERE (instance_id = $1)",
|
|
expectedArgs: []interface{}{
|
|
"instance-id",
|
|
},
|
|
},
|
|
{
|
|
expectedStmt: "DELETE FROM projections.quotas_notifications WHERE (instance_id = $1)",
|
|
expectedArgs: []interface{}{
|
|
"instance-id",
|
|
},
|
|
},
|
|
{
|
|
expectedStmt: "DELETE FROM projections.quotas WHERE (instance_id = $1)",
|
|
expectedArgs: []interface{}{
|
|
"instance-id",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
event := baseEvent(t)
|
|
got, err := tt.reduce(event)
|
|
if !errors.IsErrorInvalidArgument(err) {
|
|
t.Errorf("no wrong event mapping: %v, got: %v", err, got)
|
|
}
|
|
event = tt.args.event(t)
|
|
got, err = tt.reduce(event)
|
|
assertReduce(t, got, err, QuotasProjectionTable, tt.want)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_quotaProjection_IncrementUsage(t *testing.T) {
|
|
testNow := time.Now()
|
|
type fields struct {
|
|
client *database.DB
|
|
}
|
|
type args struct {
|
|
ctx context.Context
|
|
unit quota.Unit
|
|
instanceID string
|
|
periodStart time.Time
|
|
count uint64
|
|
}
|
|
type res struct {
|
|
sum uint64
|
|
err error
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
res res
|
|
}{
|
|
{
|
|
name: "",
|
|
fields: fields{
|
|
client: func() *database.DB {
|
|
db, mock, _ := sqlmock.New()
|
|
mock.ExpectQuery(regexp.QuoteMeta(incrementQuotaStatement)).
|
|
WithArgs(
|
|
"instance_id",
|
|
1,
|
|
testNow,
|
|
2,
|
|
).
|
|
WillReturnRows(sqlmock.NewRows([]string{"key"}).
|
|
AddRow(3))
|
|
return &database.DB{DB: db}
|
|
}(),
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
unit: quota.RequestsAllAuthenticated,
|
|
instanceID: "instance_id",
|
|
periodStart: testNow,
|
|
count: 2,
|
|
},
|
|
res: res{
|
|
sum: 3,
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
q := "aProjection{
|
|
client: tt.fields.client,
|
|
}
|
|
gotSum, err := q.IncrementUsage(tt.args.ctx, tt.args.unit, tt.args.instanceID, tt.args.periodStart, tt.args.count)
|
|
assert.Equal(t, tt.res.sum, gotSum)
|
|
assert.ErrorIs(t, err, tt.res.err)
|
|
})
|
|
}
|
|
}
|