2023-09-15 16:58:45 +02:00
package projection
import (
"context"
"regexp"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/internal/database"
2024-03-27 14:48:22 +01:00
db_mock "github.com/zitadel/zitadel/internal/database/mock"
2023-09-15 16:58:45 +02:00
"github.com/zitadel/zitadel/internal/eventstore"
2023-10-19 12:19:10 +02:00
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
2023-09-15 16:58:45 +02:00
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/quota"
2023-12-08 16:30:55 +02:00
"github.com/zitadel/zitadel/internal/zerrors"
2023-09-15 16:58:45 +02:00
)
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
} {
{
2023-09-22 11:37:16 +02:00
name : "reduceQuotaSet with added type" ,
2023-09-15 16:58:45 +02:00
args : args {
event : getEvent ( testEvent (
2023-10-19 12:19:10 +02:00
quota . AddedEventType ,
2023-09-15 16:58:45 +02:00
quota . AggregateType ,
[ ] byte ( ` {
"unit" : 1 ,
"amount" : 10 ,
"limit" : true ,
"from" : "2023-01-01T00:00:00Z" ,
"interval" : 300000000000
} ` ) ,
2023-09-22 11:37:16 +02:00
) , quota . SetEventMapper ) ,
2023-09-15 16:58:45 +02:00
} ,
2023-09-22 11:37:16 +02:00
reduce : ( & quotaProjection { } ) . reduceQuotaSet ,
2023-09-15 16:58:45 +02:00
want : wantReduce {
2023-10-19 12:19:10 +02:00
aggregateType : eventstore . AggregateType ( "quota" ) ,
sequence : 15 ,
2023-09-15 16:58:45 +02:00
executer : & testExecuter {
executions : [ ] execution {
{
2023-09-22 11:37:16 +02:00
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)" ,
2023-09-15 16:58:45 +02:00
expectedArgs : [ ] interface { } {
2023-09-22 11:37:16 +02:00
true ,
2023-09-15 16:58:45 +02:00
uint64 ( 10 ) ,
time . Date ( 2023 , 1 , 1 , 0 , 0 , 0 , 0 , time . UTC ) ,
time . Minute * 5 ,
2023-09-22 11:37:16 +02:00
"agg-id" ,
"instance-id" ,
quota . RequestsAllAuthenticated ,
2023-09-15 16:58:45 +02:00
} ,
} ,
} ,
} ,
} ,
} ,
{
2023-09-22 11:37:16 +02:00
name : "reduceQuotaAdded with added type and notification" ,
2023-09-15 16:58:45 +02:00
args : args {
event : getEvent ( testEvent (
2023-10-19 12:19:10 +02:00
quota . AddedEventType ,
2023-09-15 16:58:45 +02:00
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"
}
]
} ` ) ,
2023-09-22 11:37:16 +02:00
) , quota . SetEventMapper ) ,
2023-09-15 16:58:45 +02:00
} ,
2023-09-22 11:37:16 +02:00
reduce : ( & quotaProjection { } ) . reduceQuotaSet ,
2023-09-15 16:58:45 +02:00
want : wantReduce {
2023-10-19 12:19:10 +02:00
aggregateType : eventstore . AggregateType ( "quota" ) ,
sequence : 15 ,
2023-09-15 16:58:45 +02:00
executer : & testExecuter {
executions : [ ] execution {
{
2023-09-22 11:37:16 +02:00
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)" ,
2023-09-15 16:58:45 +02:00
expectedArgs : [ ] interface { } {
2023-09-22 11:37:16 +02:00
true ,
uint64 ( 10 ) ,
time . Date ( 2023 , 1 , 1 , 0 , 0 , 0 , 0 , time . UTC ) ,
time . Minute * 5 ,
2023-09-15 16:58:45 +02:00
"agg-id" ,
"instance-id" ,
quota . RequestsAllAuthenticated ,
2023-09-22 11:37:16 +02:00
} ,
} ,
{
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 (
2023-10-19 12:19:10 +02:00
quota . SetEventType ,
2023-09-22 11:37:16 +02:00
quota . AggregateType ,
[ ] byte ( ` {
"unit" : 1 ,
"amount" : 10 ,
"limit" : true ,
"from" : "2023-01-01T00:00:00Z" ,
"interval" : 300000000000
} ` ) ,
) , quota . SetEventMapper ) ,
} ,
reduce : ( & quotaProjection { } ) . reduceQuotaSet ,
want : wantReduce {
2023-10-19 12:19:10 +02:00
aggregateType : eventstore . AggregateType ( "quota" ) ,
sequence : 15 ,
2023-09-22 11:37:16 +02:00
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 ,
2023-09-15 16:58:45 +02:00
uint64 ( 10 ) ,
time . Date ( 2023 , 1 , 1 , 0 , 0 , 0 , 0 , time . UTC ) ,
time . Minute * 5 ,
2023-09-22 11:37:16 +02:00
"agg-id" ,
"instance-id" ,
quota . RequestsAllAuthenticated ,
} ,
} ,
} ,
} ,
} ,
} ,
{
name : "reduceQuotaAdded with set type and notification" ,
args : args {
event : getEvent ( testEvent (
2023-10-19 12:19:10 +02:00
quota . SetEventType ,
2023-09-22 11:37:16 +02:00
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 : ( & quotaProjection { } ) . reduceQuotaSet ,
want : wantReduce {
2023-10-19 12:19:10 +02:00
aggregateType : eventstore . AggregateType ( "quota" ) ,
sequence : 15 ,
2023-09-22 11:37:16 +02:00
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 { } {
2023-09-15 16:58:45 +02:00
true ,
2023-09-22 11:37:16 +02:00
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 ,
2023-09-15 16:58:45 +02:00
} ,
} ,
{
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 (
2023-10-19 12:19:10 +02:00
quota . NotificationDueEventType ,
2023-09-15 16:58:45 +02:00
quota . AggregateType ,
[ ] byte ( ` {
"id" : "id" ,
"unit" : 1 ,
"callURL" : "url" ,
"periodStart" : "2023-01-01T00:00:00Z" ,
"threshold" : 200 ,
"usage" : 100
} ` ) ,
) , quota . NotificationDueEventMapper ) ,
} ,
reduce : ( & quotaProjection { } ) . reduceQuotaNotificationDue ,
want : wantReduce {
2023-10-19 12:19:10 +02:00
aggregateType : eventstore . AggregateType ( "quota" ) ,
sequence : 15 ,
2023-09-15 16:58:45 +02:00
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 (
2023-10-19 12:19:10 +02:00
quota . RemovedEventType ,
2023-09-15 16:58:45 +02:00
quota . AggregateType ,
[ ] byte ( ` {
"unit" : 1
} ` ) ,
) , quota . RemovedEventMapper ) ,
} ,
reduce : ( & quotaProjection { } ) . reduceQuotaRemoved ,
want : wantReduce {
2023-10-19 12:19:10 +02:00
aggregateType : eventstore . AggregateType ( "quota" ) ,
sequence : 15 ,
2023-09-15 16:58:45 +02:00
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 (
2023-10-19 12:19:10 +02:00
instance . InstanceRemovedEventType ,
2023-09-15 16:58:45 +02:00
instance . AggregateType ,
[ ] byte ( ` {
"name" : "name"
} ` ) ,
) , instance . InstanceRemovedEventMapper ) ,
} ,
reduce : ( & quotaProjection { } ) . reduceInstanceRemoved ,
want : wantReduce {
2023-10-19 12:19:10 +02:00
aggregateType : eventstore . AggregateType ( "instance" ) ,
sequence : 15 ,
2023-09-15 16:58:45 +02:00
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 )
2023-12-08 16:30:55 +02:00
if ! zerrors . IsErrorInvalidArgument ( err ) {
2023-09-15 16:58:45 +02:00
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 {
2024-03-27 14:48:22 +01:00
db , mock , _ := sqlmock . New ( sqlmock . ValueConverterOption ( new ( db_mock . TypeConverter ) ) )
2023-09-15 16:58:45 +02:00
mock . ExpectQuery ( regexp . QuoteMeta ( incrementQuotaStatement ) ) .
WithArgs (
"instance_id" ,
2024-03-27 14:48:22 +01:00
quota . Unit ( 1 ) ,
2023-09-15 16:58:45 +02:00
testNow ,
2024-03-27 14:48:22 +01:00
uint64 ( 2 ) ,
2023-09-15 16:58:45 +02:00
) .
2024-03-27 14:48:22 +01:00
WillReturnRows ( mock . NewRows ( [ ] string { "key" } ) .
2023-09-15 16:58:45 +02:00
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 := & quotaProjection {
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 )
} )
}
}