mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 03:57:32 +00:00
feat: add notification policy and password change message (#5065)
Implementation of new notification policy with functionality to send email when a password is changed
This commit is contained in:
@@ -29,6 +29,7 @@ type MessageTexts struct {
|
||||
VerifyPhone MessageText
|
||||
DomainClaimed MessageText
|
||||
PasswordlessRegistration MessageText
|
||||
PasswordChange MessageText
|
||||
}
|
||||
|
||||
type MessageText struct {
|
||||
@@ -330,6 +331,8 @@ func (m *MessageTexts) GetMessageTextByType(msgType string) *MessageText {
|
||||
return &m.DomainClaimed
|
||||
case domain.PasswordlessRegistrationMessageType:
|
||||
return &m.PasswordlessRegistration
|
||||
case domain.PasswordChangeMessageType:
|
||||
return &m.PasswordChange
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
166
internal/query/notification_policy.go
Normal file
166
internal/query/notification_policy.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
errs "errors"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/query/projection"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
|
||||
type NotificationPolicy struct {
|
||||
ID string
|
||||
Sequence uint64
|
||||
CreationDate time.Time
|
||||
ChangeDate time.Time
|
||||
ResourceOwner string
|
||||
State domain.PolicyState
|
||||
|
||||
PasswordChange bool
|
||||
|
||||
IsDefault bool
|
||||
}
|
||||
|
||||
var (
|
||||
notificationPolicyTable = table{
|
||||
name: projection.NotificationPolicyProjectionTable,
|
||||
instanceIDCol: projection.NotificationPolicyColumnInstanceID,
|
||||
}
|
||||
NotificationPolicyColID = Column{
|
||||
name: projection.NotificationPolicyColumnID,
|
||||
table: notificationPolicyTable,
|
||||
}
|
||||
NotificationPolicyColSequence = Column{
|
||||
name: projection.NotificationPolicyColumnSequence,
|
||||
table: notificationPolicyTable,
|
||||
}
|
||||
NotificationPolicyColCreationDate = Column{
|
||||
name: projection.NotificationPolicyColumnCreationDate,
|
||||
table: notificationPolicyTable,
|
||||
}
|
||||
NotificationPolicyColChangeDate = Column{
|
||||
name: projection.NotificationPolicyColumnChangeDate,
|
||||
table: notificationPolicyTable,
|
||||
}
|
||||
NotificationPolicyColResourceOwner = Column{
|
||||
name: projection.NotificationPolicyColumnResourceOwner,
|
||||
table: notificationPolicyTable,
|
||||
}
|
||||
NotificationPolicyColInstanceID = Column{
|
||||
name: projection.NotificationPolicyColumnInstanceID,
|
||||
table: notificationPolicyTable,
|
||||
}
|
||||
NotificationPolicyColPasswordChange = Column{
|
||||
name: projection.NotificationPolicyColumnPasswordChange,
|
||||
table: notificationPolicyTable,
|
||||
}
|
||||
NotificationPolicyColIsDefault = Column{
|
||||
name: projection.NotificationPolicyColumnIsDefault,
|
||||
table: notificationPolicyTable,
|
||||
}
|
||||
NotificationPolicyColState = Column{
|
||||
name: projection.NotificationPolicyColumnStateCol,
|
||||
table: notificationPolicyTable,
|
||||
}
|
||||
NotificationPolicyColOwnerRemoved = Column{
|
||||
name: projection.NotificationPolicyColumnOwnerRemoved,
|
||||
table: notificationPolicyTable,
|
||||
}
|
||||
)
|
||||
|
||||
func (q *Queries) NotificationPolicyByOrg(ctx context.Context, shouldTriggerBulk bool, orgID string, withOwnerRemoved bool) (_ *NotificationPolicy, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if shouldTriggerBulk {
|
||||
if err := projection.NotificationPolicyProjection.Trigger(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
eq := sq.Eq{NotificationPolicyColInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()}
|
||||
if !withOwnerRemoved {
|
||||
eq[NotificationPolicyColOwnerRemoved.identifier()] = false
|
||||
}
|
||||
stmt, scan := prepareNotificationPolicyQuery()
|
||||
query, args, err := stmt.Where(
|
||||
sq.And{
|
||||
eq,
|
||||
sq.Or{
|
||||
sq.Eq{NotificationPolicyColID.identifier(): orgID},
|
||||
sq.Eq{NotificationPolicyColID.identifier(): authz.GetInstance(ctx).InstanceID()},
|
||||
},
|
||||
}).
|
||||
OrderBy(NotificationPolicyColIsDefault.identifier()).Limit(1).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-Xuoapqm", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
row := q.client.QueryRowContext(ctx, query, args...)
|
||||
return scan(row)
|
||||
}
|
||||
|
||||
func (q *Queries) DefaultNotificationPolicy(ctx context.Context, shouldTriggerBulk bool) (_ *NotificationPolicy, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if shouldTriggerBulk {
|
||||
if err := projection.NotificationPolicyProjection.Trigger(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
stmt, scan := prepareNotificationPolicyQuery()
|
||||
query, args, err := stmt.Where(sq.Eq{
|
||||
NotificationPolicyColID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
NotificationPolicyColInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
}).
|
||||
OrderBy(NotificationPolicyColIsDefault.identifier()).
|
||||
Limit(1).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-xlqp209", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
row := q.client.QueryRowContext(ctx, query, args...)
|
||||
return scan(row)
|
||||
}
|
||||
|
||||
func prepareNotificationPolicyQuery() (sq.SelectBuilder, func(*sql.Row) (*NotificationPolicy, error)) {
|
||||
return sq.Select(
|
||||
NotificationPolicyColID.identifier(),
|
||||
NotificationPolicyColSequence.identifier(),
|
||||
NotificationPolicyColCreationDate.identifier(),
|
||||
NotificationPolicyColChangeDate.identifier(),
|
||||
NotificationPolicyColResourceOwner.identifier(),
|
||||
NotificationPolicyColPasswordChange.identifier(),
|
||||
NotificationPolicyColIsDefault.identifier(),
|
||||
NotificationPolicyColState.identifier(),
|
||||
).
|
||||
From(notificationPolicyTable.identifier()).PlaceholderFormat(sq.Dollar),
|
||||
func(row *sql.Row) (*NotificationPolicy, error) {
|
||||
policy := new(NotificationPolicy)
|
||||
err := row.Scan(
|
||||
&policy.ID,
|
||||
&policy.Sequence,
|
||||
&policy.CreationDate,
|
||||
&policy.ChangeDate,
|
||||
&policy.ResourceOwner,
|
||||
&policy.PasswordChange,
|
||||
&policy.IsDefault,
|
||||
&policy.State,
|
||||
)
|
||||
if err != nil {
|
||||
if errs.Is(err, sql.ErrNoRows) {
|
||||
return nil, errors.ThrowNotFound(err, "QUERY-x0so2p", "Errors.NotificationPolicy.NotFound")
|
||||
}
|
||||
return nil, errors.ThrowInternal(err, "QUERY-Zixoooq", "Errors.Internal")
|
||||
}
|
||||
return policy, nil
|
||||
}
|
||||
}
|
116
internal/query/notification_policy_test.go
Normal file
116
internal/query/notification_policy_test.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
errs "github.com/zitadel/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
var notificationPolicyStmt = regexp.QuoteMeta(`SELECT projections.notification_policies.id,` +
|
||||
` projections.notification_policies.sequence,` +
|
||||
` projections.notification_policies.creation_date,` +
|
||||
` projections.notification_policies.change_date,` +
|
||||
` projections.notification_policies.resource_owner,` +
|
||||
` projections.notification_policies.password_change,` +
|
||||
` projections.notification_policies.is_default,` +
|
||||
` projections.notification_policies.state` +
|
||||
` FROM projections.notification_policies`)
|
||||
|
||||
func Test_NotificationPolicyPrepares(t *testing.T) {
|
||||
type want struct {
|
||||
sqlExpectations sqlExpectation
|
||||
err checkErr
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
prepare interface{}
|
||||
want want
|
||||
object interface{}
|
||||
}{
|
||||
{
|
||||
name: "prepareNotificationPolicyQuery no result",
|
||||
prepare: prepareNotificationPolicyQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
notificationPolicyStmt,
|
||||
nil,
|
||||
nil,
|
||||
),
|
||||
err: func(err error) (error, bool) {
|
||||
if !errs.IsNotFound(err) {
|
||||
return fmt.Errorf("err should be NotFoundError got: %w", err), false
|
||||
}
|
||||
return nil, true
|
||||
},
|
||||
},
|
||||
object: (*NotificationPolicy)(nil),
|
||||
},
|
||||
{
|
||||
name: "prepareNotificationPolicyQuery found",
|
||||
prepare: prepareNotificationPolicyQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQuery(
|
||||
notificationPolicyStmt,
|
||||
[]string{
|
||||
"id",
|
||||
"sequence",
|
||||
"creation_date",
|
||||
"change_date",
|
||||
"resource_owner",
|
||||
"password_change",
|
||||
"is_default",
|
||||
"state",
|
||||
},
|
||||
[]driver.Value{
|
||||
"pol-id",
|
||||
uint64(20211109),
|
||||
testNow,
|
||||
testNow,
|
||||
"ro",
|
||||
true,
|
||||
true,
|
||||
domain.PolicyStateActive,
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &NotificationPolicy{
|
||||
ID: "pol-id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211109,
|
||||
ResourceOwner: "ro",
|
||||
State: domain.PolicyStateActive,
|
||||
PasswordChange: true,
|
||||
IsDefault: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareNotificationPolicyQuery sql err",
|
||||
prepare: prepareNotificationPolicyQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueryErr(
|
||||
notificationPolicyStmt,
|
||||
sql.ErrConnDone,
|
||||
),
|
||||
err: func(err error) (error, bool) {
|
||||
if !errors.Is(err, sql.ErrConnDone) {
|
||||
return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false
|
||||
}
|
||||
return nil, true
|
||||
},
|
||||
},
|
||||
object: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assertPrepare(t, tt.prepare, tt.object, tt.want.sqlExpectations, tt.want.err)
|
||||
})
|
||||
}
|
||||
}
|
@@ -273,7 +273,8 @@ func isMessageTemplate(template string) bool {
|
||||
template == domain.VerifyEmailMessageType ||
|
||||
template == domain.VerifyPhoneMessageType ||
|
||||
template == domain.DomainClaimedMessageType ||
|
||||
template == domain.PasswordlessRegistrationMessageType
|
||||
template == domain.PasswordlessRegistrationMessageType ||
|
||||
template == domain.PasswordChangeMessageType
|
||||
}
|
||||
func isTitle(key string) bool {
|
||||
return key == domain.MessageTitle
|
||||
|
187
internal/query/projection/notification_policy.go
Normal file
187
internal/query/projection/notification_policy.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package projection
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler/crdb"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
"github.com/zitadel/zitadel/internal/repository/policy"
|
||||
)
|
||||
|
||||
const (
|
||||
NotificationPolicyProjectionTable = "projections.notification_policies"
|
||||
|
||||
NotificationPolicyColumnID = "id"
|
||||
NotificationPolicyColumnCreationDate = "creation_date"
|
||||
NotificationPolicyColumnChangeDate = "change_date"
|
||||
NotificationPolicyColumnResourceOwner = "resource_owner"
|
||||
NotificationPolicyColumnInstanceID = "instance_id"
|
||||
NotificationPolicyColumnSequence = "sequence"
|
||||
NotificationPolicyColumnStateCol = "state"
|
||||
NotificationPolicyColumnIsDefault = "is_default"
|
||||
NotificationPolicyColumnPasswordChange = "password_change"
|
||||
NotificationPolicyColumnOwnerRemoved = "owner_removed"
|
||||
)
|
||||
|
||||
type notificationPolicyProjection struct {
|
||||
crdb.StatementHandler
|
||||
}
|
||||
|
||||
func newNotificationPolicyProjection(ctx context.Context, config crdb.StatementHandlerConfig) *notificationPolicyProjection {
|
||||
p := new(notificationPolicyProjection)
|
||||
config.ProjectionName = NotificationPolicyProjectionTable
|
||||
config.Reducers = p.reducers()
|
||||
config.InitCheck = crdb.NewTableCheck(
|
||||
crdb.NewTable([]*crdb.Column{
|
||||
crdb.NewColumn(NotificationPolicyColumnID, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(NotificationPolicyColumnCreationDate, crdb.ColumnTypeTimestamp),
|
||||
crdb.NewColumn(NotificationPolicyColumnChangeDate, crdb.ColumnTypeTimestamp),
|
||||
crdb.NewColumn(NotificationPolicyColumnResourceOwner, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(NotificationPolicyColumnInstanceID, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(NotificationPolicyColumnSequence, crdb.ColumnTypeInt64),
|
||||
crdb.NewColumn(NotificationPolicyColumnStateCol, crdb.ColumnTypeEnum),
|
||||
crdb.NewColumn(NotificationPolicyColumnIsDefault, crdb.ColumnTypeBool),
|
||||
crdb.NewColumn(NotificationPolicyColumnPasswordChange, crdb.ColumnTypeBool),
|
||||
crdb.NewColumn(NotificationPolicyColumnOwnerRemoved, crdb.ColumnTypeBool, crdb.Default(false)),
|
||||
},
|
||||
crdb.NewPrimaryKey(NotificationPolicyColumnInstanceID, NotificationPolicyColumnID),
|
||||
),
|
||||
)
|
||||
p.StatementHandler = crdb.NewStatementHandler(ctx, config)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *notificationPolicyProjection) reducers() []handler.AggregateReducer {
|
||||
return []handler.AggregateReducer{
|
||||
{
|
||||
Aggregate: org.AggregateType,
|
||||
EventRedusers: []handler.EventReducer{
|
||||
{
|
||||
Event: org.NotificationPolicyAddedEventType,
|
||||
Reduce: p.reduceAdded,
|
||||
},
|
||||
{
|
||||
Event: org.NotificationPolicyChangedEventType,
|
||||
Reduce: p.reduceChanged,
|
||||
},
|
||||
{
|
||||
Event: org.NotificationPolicyRemovedEventType,
|
||||
Reduce: p.reduceRemoved,
|
||||
},
|
||||
{
|
||||
Event: org.OrgRemovedEventType,
|
||||
Reduce: p.reduceOwnerRemoved,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Aggregate: instance.AggregateType,
|
||||
EventRedusers: []handler.EventReducer{
|
||||
{
|
||||
Event: instance.InstanceRemovedEventType,
|
||||
Reduce: reduceInstanceRemovedHelper(NotificationPolicyColumnInstanceID),
|
||||
},
|
||||
{
|
||||
Event: instance.NotificationPolicyAddedEventType,
|
||||
Reduce: p.reduceAdded,
|
||||
},
|
||||
{
|
||||
Event: instance.NotificationPolicyChangedEventType,
|
||||
Reduce: p.reduceChanged,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *notificationPolicyProjection) reduceAdded(event eventstore.Event) (*handler.Statement, error) {
|
||||
var policyEvent policy.NotificationPolicyAddedEvent
|
||||
var isDefault bool
|
||||
switch e := event.(type) {
|
||||
case *org.NotificationPolicyAddedEvent:
|
||||
policyEvent = e.NotificationPolicyAddedEvent
|
||||
isDefault = false
|
||||
case *instance.NotificationPolicyAddedEvent:
|
||||
policyEvent = e.NotificationPolicyAddedEvent
|
||||
isDefault = true
|
||||
default:
|
||||
return nil, errors.ThrowInvalidArgumentf(nil, "PROJE-x02s1m", "reduce.wrong.event.type %v", []eventstore.EventType{org.NotificationPolicyAddedEventType, instance.NotificationPolicyAddedEventType})
|
||||
}
|
||||
return crdb.NewCreateStatement(
|
||||
&policyEvent,
|
||||
[]handler.Column{
|
||||
handler.NewCol(NotificationPolicyColumnCreationDate, policyEvent.CreationDate()),
|
||||
handler.NewCol(NotificationPolicyColumnChangeDate, policyEvent.CreationDate()),
|
||||
handler.NewCol(NotificationPolicyColumnSequence, policyEvent.Sequence()),
|
||||
handler.NewCol(NotificationPolicyColumnID, policyEvent.Aggregate().ID),
|
||||
handler.NewCol(NotificationPolicyColumnStateCol, domain.PolicyStateActive),
|
||||
handler.NewCol(NotificationPolicyColumnPasswordChange, policyEvent.PasswordChange),
|
||||
handler.NewCol(NotificationPolicyColumnIsDefault, isDefault),
|
||||
handler.NewCol(NotificationPolicyColumnResourceOwner, policyEvent.Aggregate().ResourceOwner),
|
||||
handler.NewCol(NotificationPolicyColumnInstanceID, policyEvent.Aggregate().InstanceID),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *notificationPolicyProjection) reduceChanged(event eventstore.Event) (*handler.Statement, error) {
|
||||
var policyEvent policy.NotificationPolicyChangedEvent
|
||||
switch e := event.(type) {
|
||||
case *org.NotificationPolicyChangedEvent:
|
||||
policyEvent = e.NotificationPolicyChangedEvent
|
||||
case *instance.NotificationPolicyChangedEvent:
|
||||
policyEvent = e.NotificationPolicyChangedEvent
|
||||
default:
|
||||
return nil, errors.ThrowInvalidArgumentf(nil, "PROJE-psom2h19", "reduce.wrong.event.type %v", []eventstore.EventType{org.NotificationPolicyChangedEventType, instance.NotificationPolicyChangedEventType})
|
||||
}
|
||||
cols := []handler.Column{
|
||||
handler.NewCol(NotificationPolicyColumnChangeDate, policyEvent.CreationDate()),
|
||||
handler.NewCol(NotificationPolicyColumnSequence, policyEvent.Sequence()),
|
||||
}
|
||||
if policyEvent.PasswordChange != nil {
|
||||
cols = append(cols, handler.NewCol(NotificationPolicyColumnPasswordChange, *policyEvent.PasswordChange))
|
||||
}
|
||||
return crdb.NewUpdateStatement(
|
||||
&policyEvent,
|
||||
cols,
|
||||
[]handler.Condition{
|
||||
handler.NewCond(NotificationPolicyColumnID, policyEvent.Aggregate().ID),
|
||||
handler.NewCond(NotificationPolicyColumnInstanceID, policyEvent.Aggregate().InstanceID),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *notificationPolicyProjection) reduceRemoved(event eventstore.Event) (*handler.Statement, error) {
|
||||
policyEvent, ok := event.(*org.NotificationPolicyRemovedEvent)
|
||||
if !ok {
|
||||
return nil, errors.ThrowInvalidArgumentf(nil, "PROJE-Po2iso2", "reduce.wrong.event.type %s", org.NotificationPolicyRemovedEventType)
|
||||
}
|
||||
return crdb.NewDeleteStatement(
|
||||
policyEvent,
|
||||
[]handler.Condition{
|
||||
handler.NewCond(NotificationPolicyColumnID, policyEvent.Aggregate().ID),
|
||||
handler.NewCond(NotificationPolicyColumnInstanceID, policyEvent.Aggregate().InstanceID),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *notificationPolicyProjection) reduceOwnerRemoved(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, ok := event.(*org.OrgRemovedEvent)
|
||||
if !ok {
|
||||
return nil, errors.ThrowInvalidArgumentf(nil, "PROJE-poxi9a", "reduce.wrong.event.type %s", org.OrgRemovedEventType)
|
||||
}
|
||||
|
||||
return crdb.NewUpdateStatement(
|
||||
e,
|
||||
[]handler.Column{
|
||||
handler.NewCol(DomainPolicyChangeDateCol, e.CreationDate()),
|
||||
handler.NewCol(DomainPolicySequenceCol, e.Sequence()),
|
||||
handler.NewCol(DomainPolicyOwnerRemovedCol, true),
|
||||
},
|
||||
[]handler.Condition{
|
||||
handler.NewCond(DomainPolicyInstanceIDCol, e.Aggregate().InstanceID),
|
||||
handler.NewCond(DomainPolicyResourceOwnerCol, e.Aggregate().ID),
|
||||
},
|
||||
), nil
|
||||
}
|
258
internal/query/projection/notification_policy_test.go
Normal file
258
internal/query/projection/notification_policy_test.go
Normal file
@@ -0,0 +1,258 @@
|
||||
package projection
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/repository"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
)
|
||||
|
||||
func TestNotificationPolicyProjection_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: "org reduceAdded",
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(org.NotificationPolicyAddedEventType),
|
||||
org.AggregateType,
|
||||
[]byte(`{
|
||||
"passwordChange": true
|
||||
}`),
|
||||
), org.NotificationPolicyAddedEventMapper),
|
||||
},
|
||||
reduce: (¬ificationPolicyProjection{}).reduceAdded,
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("org"),
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.notification_policies (creation_date, change_date, sequence, id, state, password_change, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
"agg-id",
|
||||
domain.PolicyStateActive,
|
||||
true,
|
||||
false,
|
||||
"ro-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org reduceChanged",
|
||||
reduce: (¬ificationPolicyProjection{}).reduceChanged,
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(org.NotificationPolicyChangedEventType),
|
||||
org.AggregateType,
|
||||
[]byte(`{
|
||||
"passwordChange": true
|
||||
}`),
|
||||
), org.NotificationPolicyChangedEventMapper),
|
||||
},
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("org"),
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.notification_policies SET (change_date, sequence, password_change) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
true,
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org reduceRemoved",
|
||||
reduce: (¬ificationPolicyProjection{}).reduceRemoved,
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(org.NotificationPolicyRemovedEventType),
|
||||
org.AggregateType,
|
||||
nil,
|
||||
), org.NotificationPolicyRemovedEventMapper),
|
||||
},
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("org"),
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.notification_policies WHERE (id = $1) AND (instance_id = $2)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "instance reduceInstanceRemoved",
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(instance.InstanceRemovedEventType),
|
||||
instance.AggregateType,
|
||||
nil,
|
||||
), instance.InstanceRemovedEventMapper),
|
||||
},
|
||||
reduce: reduceInstanceRemovedHelper(NotificationPolicyColumnInstanceID),
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("instance"),
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.notification_policies WHERE (instance_id = $1)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "instance reduceAdded",
|
||||
reduce: (¬ificationPolicyProjection{}).reduceAdded,
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(instance.NotificationPolicyAddedEventType),
|
||||
instance.AggregateType,
|
||||
[]byte(`{
|
||||
"passwordChange": true
|
||||
}`),
|
||||
), instance.NotificationPolicyAddedEventMapper),
|
||||
},
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("instance"),
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.notification_policies (creation_date, change_date, sequence, id, state, password_change, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
"agg-id",
|
||||
domain.PolicyStateActive,
|
||||
true,
|
||||
true,
|
||||
"ro-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "instance reduceChanged",
|
||||
reduce: (¬ificationPolicyProjection{}).reduceChanged,
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(instance.NotificationPolicyChangedEventType),
|
||||
instance.AggregateType,
|
||||
[]byte(`{
|
||||
"passwordChange": true
|
||||
}`),
|
||||
), instance.NotificationPolicyChangedEventMapper),
|
||||
},
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("instance"),
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.notification_policies SET (change_date, sequence, password_change) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
true,
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org.reduceOwnerRemoved",
|
||||
reduce: (¬ificationPolicyProjection{}).reduceOwnerRemoved,
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(org.OrgRemovedEventType),
|
||||
org.AggregateType,
|
||||
nil,
|
||||
), org.OrgRemovedEventMapper),
|
||||
},
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("org"),
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.notification_policies SET (change_date, sequence, owner_removed) = ($1, $2, $3) WHERE (instance_id = $4) AND (resource_owner = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
true,
|
||||
"instance-id",
|
||||
"agg-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
event := baseEvent(t)
|
||||
got, err := tt.reduce(event)
|
||||
|
||||
if ok := errors.IsErrorInvalidArgument(err); !ok {
|
||||
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, NotificationPolicyProjectionTable, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
@@ -60,6 +60,7 @@ var (
|
||||
DebugNotificationProviderProjection *debugNotificationProviderProjection
|
||||
KeyProjection *keyProjection
|
||||
SecurityPolicyProjection *securityPolicyProjection
|
||||
NotificationPolicyProjection *notificationPolicyProjection
|
||||
NotificationsProjection interface{}
|
||||
)
|
||||
|
||||
@@ -133,6 +134,7 @@ func Create(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, c
|
||||
DebugNotificationProviderProjection = newDebugNotificationProviderProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["debug_notification_provider"]))
|
||||
KeyProjection = newKeyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["keys"]), keyEncryptionAlgorithm, certEncryptionAlgorithm)
|
||||
SecurityPolicyProjection = newSecurityPolicyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["security_policies"]))
|
||||
NotificationPolicyProjection = newNotificationPolicyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["notification_policies"]))
|
||||
newProjectionsList()
|
||||
return nil
|
||||
}
|
||||
@@ -224,5 +226,6 @@ func newProjectionsList() {
|
||||
DebugNotificationProviderProjection,
|
||||
KeyProjection,
|
||||
SecurityPolicyProjection,
|
||||
NotificationPolicyProjection,
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user