mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:37:32 +00:00
feat: notifications (#109)
* implement notification providers * email provider * notification handler * notify users * implement code sent on user eventstore * send email implementation * send init code * handle codes * fix project member handler * add some logs for debug * send emails * text changes * send sms * notification process * send password code * format phone number * test format phone * remove fmts * remove unused code * rename files * add mocks * merge master * Update internal/notification/providers/email/message.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/notification/repository/eventsourcing/handler/notification.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/notification/repository/eventsourcing/handler/notification.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/notification/providers/email/provider.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * requested changes of mr * move locker to eventstore pkg * Update internal/notification/providers/chat/message.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * move locker to eventstore pkg * linebreak * Update internal/notification/providers/email/provider.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/notification/repository/eventsourcing/handler/notification.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/notification/repository/eventsourcing/handler/notification.go Co-authored-by: Silvan <silvan.reusser@gmail.com> Co-authored-by: Silvan <silvan.reusser@gmail.com> Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
53
internal/user/model/notify_user.go
Normal file
53
internal/user/model/notify_user.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
type NotifyUser struct {
|
||||
ID string
|
||||
CreationDate time.Time
|
||||
ChangeDate time.Time
|
||||
ResourceOwner string
|
||||
UserName string
|
||||
FirstName string
|
||||
LastName string
|
||||
NickName string
|
||||
DisplayName string
|
||||
PreferredLanguage string
|
||||
Gender Gender
|
||||
LastEmail string
|
||||
VerifiedEmail string
|
||||
LastPhone string
|
||||
VerifiedPhone string
|
||||
Sequence uint64
|
||||
}
|
||||
|
||||
type NotifyUserSearchRequest struct {
|
||||
Offset uint64
|
||||
Limit uint64
|
||||
SortingColumn NotifyUserSearchKey
|
||||
Asc bool
|
||||
Queries []*NotifyUserSearchQuery
|
||||
}
|
||||
|
||||
type NotifyUserSearchKey int32
|
||||
|
||||
const (
|
||||
NOTIFYUSERSEARCHKEY_UNSPECIFIED UserSearchKey = iota
|
||||
NOTIFYUSERSEARCHKEY_USER_ID
|
||||
)
|
||||
|
||||
type NotifyUserSearchQuery struct {
|
||||
Key NotifyUserSearchKey
|
||||
Method model.SearchMethod
|
||||
Value string
|
||||
}
|
||||
|
||||
type NotifyUserSearchResponse struct {
|
||||
Offset uint64
|
||||
Limit uint64
|
||||
TotalResult uint64
|
||||
Result []*UserView
|
||||
}
|
@@ -1,13 +1,18 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/ttacon/libphonenumber"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
//TODO: How do we get region?
|
||||
defaultRegion = "CH"
|
||||
)
|
||||
|
||||
type Phone struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
@@ -23,27 +28,16 @@ type PhoneCode struct {
|
||||
}
|
||||
|
||||
func (p *Phone) IsValid() bool {
|
||||
return p.PhoneNumber != ""
|
||||
err := p.formatPhone()
|
||||
return p.PhoneNumber != "" && err == nil
|
||||
}
|
||||
|
||||
func (u *User) appendUserPhoneChangedEvent(event *es_models.Event) error {
|
||||
u.Phone = new(Phone)
|
||||
u.Phone.setData(event)
|
||||
u.IsPhoneVerified = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) appendUserPhoneVerifiedEvent() error {
|
||||
u.IsPhoneVerified = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Phone) setData(event *es_models.Event) error {
|
||||
p.ObjectRoot.AppendEvent(event)
|
||||
if err := json.Unmarshal(event.Data, p); err != nil {
|
||||
logging.Log("EVEN-dlo9s").WithError(err).Error("could not unmarshal event data")
|
||||
return err
|
||||
func (p *Phone) formatPhone() error {
|
||||
phoneNr, err := libphonenumber.Parse(p.PhoneNumber, defaultRegion)
|
||||
if err != nil {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-so0wa", "Phonenumber is invalid")
|
||||
}
|
||||
p.PhoneNumber = libphonenumber.Format(phoneNr, libphonenumber.E164)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
106
internal/user/model/phone_test.go
Normal file
106
internal/user/model/phone_test.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFormatPhoneNumber(t *testing.T) {
|
||||
type args struct {
|
||||
phone *Phone
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result *Phone
|
||||
errFunc func(err error) bool
|
||||
}{
|
||||
{
|
||||
name: "invalid phone number",
|
||||
args: args{
|
||||
phone: &Phone{
|
||||
PhoneNumber: "PhoneNumber",
|
||||
},
|
||||
},
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
{
|
||||
name: "format phone 071...",
|
||||
args: args{
|
||||
phone: &Phone{
|
||||
PhoneNumber: "0711234567",
|
||||
},
|
||||
},
|
||||
result: &Phone{
|
||||
PhoneNumber: "+41711234567",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "format phone 0041...",
|
||||
args: args{
|
||||
phone: &Phone{
|
||||
PhoneNumber: "0041711234567",
|
||||
},
|
||||
},
|
||||
result: &Phone{
|
||||
PhoneNumber: "+41711234567",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "format phone 071 xxx xx xx",
|
||||
args: args{
|
||||
phone: &Phone{
|
||||
PhoneNumber: "071 123 45 67",
|
||||
},
|
||||
},
|
||||
result: &Phone{
|
||||
PhoneNumber: "+41711234567",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "format phone +4171 xxx xx xx",
|
||||
args: args{
|
||||
phone: &Phone{
|
||||
PhoneNumber: "+4171 123 45 67",
|
||||
},
|
||||
},
|
||||
result: &Phone{
|
||||
PhoneNumber: "+41711234567",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "format phone 004171 xxx xx xx",
|
||||
args: args{
|
||||
phone: &Phone{
|
||||
PhoneNumber: "004171 123 45 67",
|
||||
},
|
||||
},
|
||||
result: &Phone{
|
||||
PhoneNumber: "+41711234567",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "format non swiss phone 004371 xxx xx xx",
|
||||
args: args{
|
||||
phone: &Phone{
|
||||
PhoneNumber: "004371 123 45 67",
|
||||
},
|
||||
},
|
||||
result: &Phone{
|
||||
PhoneNumber: "+43711234567",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.args.phone.formatPhone()
|
||||
|
||||
if tt.errFunc == nil && tt.result.PhoneNumber != tt.args.phone.PhoneNumber {
|
||||
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.args.phone.PhoneNumber, tt.result.PhoneNumber)
|
||||
}
|
||||
if tt.errFunc != nil && !tt.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -58,7 +58,7 @@ func (u *User) SetEmailAsUsername() {
|
||||
}
|
||||
|
||||
func (u *User) IsValid() bool {
|
||||
return u.Profile != nil && u.FirstName != "" && u.LastName != "" && u.UserName != "" && u.Email != nil && u.EmailAddress != ""
|
||||
return u.Profile != nil && u.FirstName != "" && u.LastName != "" && u.UserName != "" && u.Email != nil && u.Email.IsValid() && u.Phone == nil || (u.Phone != nil && u.Phone.IsValid())
|
||||
}
|
||||
|
||||
func (u *User) IsInitialState() bool {
|
||||
|
@@ -96,7 +96,7 @@ func (es *UserEventstore) UserByID(ctx context.Context, id string) (*usr_model.U
|
||||
func (es *UserEventstore) PrepareCreateUser(ctx context.Context, user *usr_model.User, resourceOwner string) (*model.User, *es_models.Aggregate, error) {
|
||||
user.SetEmailAsUsername()
|
||||
if !user.IsValid() {
|
||||
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9dk45", "Name is required")
|
||||
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9dk45", "User is invalid")
|
||||
}
|
||||
//TODO: Check Uniqueness
|
||||
id, err := es.idGenerator.NextID()
|
||||
@@ -303,6 +303,25 @@ func (es *UserEventstore) CreateInitializeUserCodeByID(ctx context.Context, user
|
||||
return model.InitCodeToModel(repoUser.InitCode), nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) InitCodeSent(ctx context.Context, userID string) error {
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-0posw", "userID missing")
|
||||
}
|
||||
user, err := es.UserByID(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repoUser := model.UserFromModel(user)
|
||||
agg := UserInitCodeSentAggregate(es.AggregateCreator(), repoUser)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
es.userCache.cacheUser(repoUser)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) SkipMfaInit(ctx context.Context, userID string) error {
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-dic8s", "userID missing")
|
||||
@@ -446,6 +465,25 @@ func (es *UserEventstore) RequestSetPassword(ctx context.Context, userID string,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) PasswordCodeSent(ctx context.Context, userID string) error {
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-s09ow", "userID missing")
|
||||
}
|
||||
user, err := es.UserByID(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repoUser := model.UserFromModel(user)
|
||||
agg := PasswordCodeSentAggregate(es.AggregateCreator(), repoUser)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
es.userCache.cacheUser(repoUser)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) ProfileByID(ctx context.Context, userID string) (*usr_model.Profile, error) {
|
||||
if userID == "" {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-di834", "userID missing")
|
||||
@@ -584,6 +622,25 @@ func (es *UserEventstore) CreateEmailVerificationCode(ctx context.Context, userI
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) EmailVerificationCodeSent(ctx context.Context, userID string) error {
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-spo0w", "userID missing")
|
||||
}
|
||||
user, err := es.UserByID(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repoUser := model.UserFromModel(user)
|
||||
agg := EmailCodeSentAggregate(es.AggregateCreator(), repoUser)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
es.userCache.cacheUser(repoUser)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) PhoneByID(ctx context.Context, userID string) (*usr_model.Phone, error) {
|
||||
if userID == "" {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-do9se", "userID missing")
|
||||
@@ -686,6 +743,25 @@ func (es *UserEventstore) CreatePhoneVerificationCode(ctx context.Context, userI
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) PhoneVerificationCodeSent(ctx context.Context, userID string) error {
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sp0wa", "userID missing")
|
||||
}
|
||||
user, err := es.UserByID(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repoUser := model.UserFromModel(user)
|
||||
agg := PhoneCodeSentAggregate(es.AggregateCreator(), repoUser)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
es.userCache.cacheUser(repoUser)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) AddressByID(ctx context.Context, userID string) (*usr_model.Address, error) {
|
||||
if userID == "" {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-di8ws", "userID missing")
|
||||
|
@@ -161,7 +161,7 @@ func TestCreateUser(t *testing.T) {
|
||||
{
|
||||
name: "with verified phone number",
|
||||
args: args{
|
||||
es: GetMockManipulateUserWithInitCodeGen(ctrl, repo_model.User{Profile: &repo_model.Profile{UserName: "EmailAddress", FirstName: "FirstName", LastName: "LastName"}, Email: &repo_model.Email{EmailAddress: "EmailAddress", IsEmailVerified: true}, Phone: &repo_model.Phone{PhoneNumber: "PhoneNumber", IsPhoneVerified: true}}),
|
||||
es: GetMockManipulateUserWithInitCodeGen(ctrl, repo_model.User{Profile: &repo_model.Profile{UserName: "EmailAddress", FirstName: "FirstName", LastName: "LastName"}, Email: &repo_model.Email{EmailAddress: "EmailAddress", IsEmailVerified: true}, Phone: &repo_model.Phone{PhoneNumber: "+41711234567", IsPhoneVerified: true}}),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
user: &model.User{ObjectRoot: es_models.ObjectRoot{Sequence: 1},
|
||||
Profile: &model.Profile{
|
||||
@@ -174,7 +174,7 @@ func TestCreateUser(t *testing.T) {
|
||||
IsEmailVerified: true,
|
||||
},
|
||||
Phone: &model.Phone{
|
||||
PhoneNumber: "UserName",
|
||||
PhoneNumber: "+41711234567",
|
||||
IsPhoneVerified: true,
|
||||
},
|
||||
},
|
||||
@@ -191,7 +191,7 @@ func TestCreateUser(t *testing.T) {
|
||||
IsEmailVerified: true,
|
||||
},
|
||||
Phone: &model.Phone{
|
||||
PhoneNumber: "UserName",
|
||||
PhoneNumber: "+41711234567",
|
||||
IsPhoneVerified: true,
|
||||
},
|
||||
},
|
||||
@@ -824,6 +824,67 @@ func TestCreateInitCode(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitCodeSent(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
type args struct {
|
||||
es *UserEventstore
|
||||
ctx context.Context
|
||||
existing *model.User
|
||||
}
|
||||
type res struct {
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "sent init",
|
||||
args: args{
|
||||
es: GetMockManipulateUser(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||
},
|
||||
res: res{},
|
||||
},
|
||||
{
|
||||
name: "empty userid",
|
||||
args: args{
|
||||
es: GetMockManipulateUser(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "", Sequence: 1}},
|
||||
},
|
||||
res: res{
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing user not found",
|
||||
args: args{
|
||||
es: GetMockManipulateUserNoEvents(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||
},
|
||||
res: res{
|
||||
errFunc: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.args.es.InitCodeSent(tt.args.ctx, tt.args.existing.AggregateID)
|
||||
|
||||
if tt.res.errFunc == nil && err != nil {
|
||||
t.Errorf("rshould not get err")
|
||||
}
|
||||
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkipMfaInit(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
type args struct {
|
||||
@@ -1468,6 +1529,67 @@ func TestRequestSetPassword(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordCodeSent(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
type args struct {
|
||||
es *UserEventstore
|
||||
ctx context.Context
|
||||
existing *model.User
|
||||
}
|
||||
type res struct {
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "sent password code",
|
||||
args: args{
|
||||
es: GetMockManipulateUser(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||
},
|
||||
res: res{},
|
||||
},
|
||||
{
|
||||
name: "empty userid",
|
||||
args: args{
|
||||
es: GetMockManipulateUser(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "", Sequence: 1}},
|
||||
},
|
||||
res: res{
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing user not found",
|
||||
args: args{
|
||||
es: GetMockManipulateUserNoEvents(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||
},
|
||||
res: res{
|
||||
errFunc: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.args.es.PasswordCodeSent(tt.args.ctx, tt.args.existing.AggregateID)
|
||||
|
||||
if tt.res.errFunc == nil && err != nil {
|
||||
t.Errorf("rshould not get err")
|
||||
}
|
||||
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProfileByID(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
type args struct {
|
||||
@@ -1907,6 +2029,67 @@ func TestCreateEmailVerificationCode(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmailVerificationCodeSent(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
type args struct {
|
||||
es *UserEventstore
|
||||
ctx context.Context
|
||||
existing *model.User
|
||||
}
|
||||
type res struct {
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "sent email verify code",
|
||||
args: args{
|
||||
es: GetMockManipulateUser(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||
},
|
||||
res: res{},
|
||||
},
|
||||
{
|
||||
name: "empty userid",
|
||||
args: args{
|
||||
es: GetMockManipulateUser(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "", Sequence: 1}},
|
||||
},
|
||||
res: res{
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing user not found",
|
||||
args: args{
|
||||
es: GetMockManipulateUserNoEvents(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||
},
|
||||
res: res{
|
||||
errFunc: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.args.es.EmailVerificationCodeSent(tt.args.ctx, tt.args.existing.AggregateID)
|
||||
|
||||
if tt.res.errFunc == nil && err != nil {
|
||||
t.Errorf("rshould not get err")
|
||||
}
|
||||
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPhoneByID(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
type args struct {
|
||||
@@ -1995,10 +2178,10 @@ func TestChangePhone(t *testing.T) {
|
||||
args: args{
|
||||
es: GetMockManipulateUserFull(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
phone: &model.Phone{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, PhoneNumber: "PhoneNumberChanged", IsPhoneVerified: true},
|
||||
phone: &model.Phone{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, PhoneNumber: "0711234567", IsPhoneVerified: true},
|
||||
},
|
||||
res: res{
|
||||
phone: &model.Phone{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, PhoneNumber: "PhoneNumberChanged", IsPhoneVerified: true},
|
||||
phone: &model.Phone{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, PhoneNumber: "+41711234567", IsPhoneVerified: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -2006,10 +2189,10 @@ func TestChangePhone(t *testing.T) {
|
||||
args: args{
|
||||
es: GetMockManipulateUserWithPhoneCodeGen(ctrl, repo_model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, Profile: &repo_model.Profile{UserName: "UserName"}, Phone: &repo_model.Phone{PhoneNumber: "PhoneNumber"}}),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
phone: &model.Phone{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, PhoneNumber: "PhoneNumberChanged", IsPhoneVerified: false},
|
||||
phone: &model.Phone{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, PhoneNumber: "+41711234567", IsPhoneVerified: false},
|
||||
},
|
||||
res: res{
|
||||
phone: &model.Phone{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, PhoneNumber: "PhoneNumberChanged", IsPhoneVerified: false},
|
||||
phone: &model.Phone{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, PhoneNumber: "+41711234567", IsPhoneVerified: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -2028,7 +2211,7 @@ func TestChangePhone(t *testing.T) {
|
||||
args: args{
|
||||
es: GetMockManipulateUserNoEvents(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
phone: &model.Phone{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, PhoneNumber: "PhoneNumberChanged", IsPhoneVerified: true},
|
||||
phone: &model.Phone{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, PhoneNumber: "+41711234567", IsPhoneVerified: true},
|
||||
},
|
||||
res: res{
|
||||
errFunc: caos_errs.IsNotFound,
|
||||
@@ -2212,6 +2395,67 @@ func TestCreatePhoneVerificationCode(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPhoneVerificationCodeSent(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
type args struct {
|
||||
es *UserEventstore
|
||||
ctx context.Context
|
||||
existing *model.User
|
||||
}
|
||||
type res struct {
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "sent phone verification code",
|
||||
args: args{
|
||||
es: GetMockManipulateUser(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||
},
|
||||
res: res{},
|
||||
},
|
||||
{
|
||||
name: "empty userid",
|
||||
args: args{
|
||||
es: GetMockManipulateUser(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "", Sequence: 1}},
|
||||
},
|
||||
res: res{
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing user not found",
|
||||
args: args{
|
||||
es: GetMockManipulateUserNoEvents(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||
},
|
||||
res: res{
|
||||
errFunc: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.args.es.PhoneVerificationCodeSent(tt.args.ctx, tt.args.existing.AggregateID)
|
||||
|
||||
if tt.res.errFunc == nil && err != nil {
|
||||
t.Errorf("rshould not get err")
|
||||
}
|
||||
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddressByID(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
type args struct {
|
||||
|
@@ -76,7 +76,7 @@ func (u *User) appendUserEmailChangedEvent(event *es_models.Event) error {
|
||||
|
||||
func (u *User) appendUserEmailCodeAddedEvent(event *es_models.Event) error {
|
||||
u.EmailCode = new(EmailCode)
|
||||
return u.EmailCode.setData(event)
|
||||
return u.EmailCode.SetData(event)
|
||||
}
|
||||
|
||||
func (u *User) appendUserEmailVerifiedEvent() {
|
||||
@@ -92,7 +92,7 @@ func (a *Email) setData(event *es_models.Event) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *EmailCode) setData(event *es_models.Event) error {
|
||||
func (a *EmailCode) SetData(event *es_models.Event) error {
|
||||
a.ObjectRoot.AppendEvent(event)
|
||||
if err := json.Unmarshal(event.Data, a); err != nil {
|
||||
logging.Log("EVEN-lo9s").WithError(err).Error("could not unmarshal event data")
|
||||
|
@@ -62,7 +62,7 @@ func (u *User) appendUserPasswordChangedEvent(event *es_models.Event) error {
|
||||
|
||||
func (u *User) appendPasswordSetRequestedEvent(event *es_models.Event) error {
|
||||
u.PasswordCode = new(PasswordCode)
|
||||
return u.PasswordCode.setData(event)
|
||||
return u.PasswordCode.SetData(event)
|
||||
}
|
||||
|
||||
func (pw *Password) setData(event *es_models.Event) error {
|
||||
@@ -74,7 +74,7 @@ func (pw *Password) setData(event *es_models.Event) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *PasswordCode) setData(event *es_models.Event) error {
|
||||
func (a *PasswordCode) SetData(event *es_models.Event) error {
|
||||
a.ObjectRoot.AppendEvent(event)
|
||||
if err := json.Unmarshal(event.Data, a); err != nil {
|
||||
logging.Log("EVEN-lo0y2").WithError(err).Error("could not unmarshal event data")
|
||||
|
@@ -74,7 +74,7 @@ func (u *User) appendUserPhoneChangedEvent(event *es_models.Event) error {
|
||||
|
||||
func (u *User) appendUserPhoneCodeAddedEvent(event *es_models.Event) error {
|
||||
u.PhoneCode = new(PhoneCode)
|
||||
return u.PhoneCode.setData(event)
|
||||
return u.PhoneCode.SetData(event)
|
||||
}
|
||||
|
||||
func (u *User) appendUserPhoneVerifiedEvent() {
|
||||
@@ -90,7 +90,7 @@ func (p *Phone) setData(event *es_models.Event) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *PhoneCode) setData(event *es_models.Event) error {
|
||||
func (a *PhoneCode) SetData(event *es_models.Event) error {
|
||||
a.ObjectRoot.AppendEvent(event)
|
||||
if err := json.Unmarshal(event.Data, a); err != nil {
|
||||
logging.Log("EVEN-sk8ws").WithError(err).Error("could not unmarshal event data")
|
||||
|
@@ -228,7 +228,7 @@ func (u *User) appendUnlockedEvent() {
|
||||
|
||||
func (u *User) appendInitUsercodeCreatedEvent(event *es_models.Event) error {
|
||||
initCode := new(InitUserCode)
|
||||
err := initCode.setData(event)
|
||||
err := initCode.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -237,7 +237,7 @@ func (u *User) appendInitUsercodeCreatedEvent(event *es_models.Event) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *InitUserCode) setData(event *es_models.Event) error {
|
||||
func (c *InitUserCode) SetData(event *es_models.Event) error {
|
||||
c.ObjectRoot.AppendEvent(event)
|
||||
if err := json.Unmarshal(event.Data, c); err != nil {
|
||||
logging.Log("EVEN-7duwe").WithError(err).Error("could not unmarshal event data")
|
||||
|
@@ -68,12 +68,6 @@ func UserCreateAggregate(ctx context.Context, aggCreator *es_models.AggregateCre
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if user.Password != nil {
|
||||
agg, err = agg.AppendEvent(model.UserPasswordCodeAdded, user.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if initCode != nil {
|
||||
agg, err = agg.AppendEvent(model.InitializedUserCodeAdded, initCode)
|
||||
if err != nil {
|
||||
@@ -145,6 +139,16 @@ func UserInitCodeAggregate(aggCreator *es_models.AggregateCreator, existing *mod
|
||||
}
|
||||
}
|
||||
|
||||
func UserInitCodeSentAggregate(aggCreator *es_models.AggregateCreator, existing *model.User) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
agg, err := UserAggregate(ctx, aggCreator, existing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return agg.AppendEvent(model.InitializedUserCodeSent, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func SkipMfaAggregate(aggCreator *es_models.AggregateCreator, existing *model.User) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
agg, err := UserAggregate(ctx, aggCreator, existing)
|
||||
@@ -200,6 +204,16 @@ func RequestSetPassword(aggCreator *es_models.AggregateCreator, existing *model.
|
||||
}
|
||||
}
|
||||
|
||||
func PasswordCodeSentAggregate(aggCreator *es_models.AggregateCreator, existing *model.User) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
agg, err := UserAggregate(ctx, aggCreator, existing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return agg.AppendEvent(model.UserPasswordCodeSent, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func ProfileChangeAggregate(aggCreator *es_models.AggregateCreator, existing *model.User, profile *model.Profile) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
if profile == nil {
|
||||
@@ -210,6 +224,9 @@ func ProfileChangeAggregate(aggCreator *es_models.AggregateCreator, existing *mo
|
||||
return nil, err
|
||||
}
|
||||
changes := existing.Profile.Changes(profile)
|
||||
if len(changes) == 0 {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-0spow", "no changes found")
|
||||
}
|
||||
return agg.AppendEvent(model.UserProfileChanged, changes)
|
||||
}
|
||||
}
|
||||
@@ -227,6 +244,9 @@ func EmailChangeAggregate(aggCreator *es_models.AggregateCreator, existing *mode
|
||||
return nil, err
|
||||
}
|
||||
changes := existing.Email.Changes(email)
|
||||
if len(changes) == 0 {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-s90pw", "no changes found")
|
||||
}
|
||||
agg, err = agg.AppendEvent(model.UserEmailChanged, changes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -266,6 +286,16 @@ func EmailVerificationCodeAggregate(aggCreator *es_models.AggregateCreator, exis
|
||||
}
|
||||
}
|
||||
|
||||
func EmailCodeSentAggregate(aggCreator *es_models.AggregateCreator, existing *model.User) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
agg, err := UserAggregate(ctx, aggCreator, existing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return agg.AppendEvent(model.UserEmailCodeSent, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func PhoneChangeAggregate(aggCreator *es_models.AggregateCreator, existing *model.User, phone *model.Phone, code *model.PhoneCode) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
if phone == nil {
|
||||
@@ -282,6 +312,9 @@ func PhoneChangeAggregate(aggCreator *es_models.AggregateCreator, existing *mode
|
||||
existing.Phone = new(model.Phone)
|
||||
}
|
||||
changes := existing.Phone.Changes(phone)
|
||||
if len(changes) == 0 {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-sp0oc", "no changes found")
|
||||
}
|
||||
agg, err = agg.AppendEvent(model.UserPhoneChanged, changes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -318,6 +351,16 @@ func PhoneVerificationCodeAggregate(aggCreator *es_models.AggregateCreator, exis
|
||||
}
|
||||
}
|
||||
|
||||
func PhoneCodeSentAggregate(aggCreator *es_models.AggregateCreator, existing *model.User) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
agg, err := UserAggregate(ctx, aggCreator, existing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return agg.AppendEvent(model.UserPhoneCodeSent, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func AddressChangeAggregate(aggCreator *es_models.AggregateCreator, existing *model.User, address *model.Address) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
if address == nil {
|
||||
@@ -331,6 +374,9 @@ func AddressChangeAggregate(aggCreator *es_models.AggregateCreator, existing *mo
|
||||
existing.Address = new(model.Address)
|
||||
}
|
||||
changes := existing.Address.Changes(address)
|
||||
if len(changes) == 0 {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-2tszw", "no changes found")
|
||||
}
|
||||
return agg.AppendEvent(model.UserAddressChanged, changes)
|
||||
}
|
||||
}
|
||||
|
@@ -650,6 +650,54 @@ func TestUserInitCodeAggregate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitCodeSentAggregate(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
existing *model.User
|
||||
aggCreator *models.AggregateCreator
|
||||
}
|
||||
type res struct {
|
||||
eventLen int
|
||||
eventTypes []models.EventType
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "user init code sent aggregate ok",
|
||||
args: args{
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.User{ObjectRoot: models.ObjectRoot{AggregateID: "ID"}},
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
},
|
||||
res: res{
|
||||
eventLen: 1,
|
||||
eventTypes: []models.EventType{model.InitializedUserCodeSent},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
agg, err := UserInitCodeSentAggregate(tt.args.aggCreator, tt.args.existing)(tt.args.ctx)
|
||||
|
||||
if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen {
|
||||
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
||||
}
|
||||
for i := 0; i < tt.res.eventLen; i++ {
|
||||
if tt.res.errFunc == nil && agg.Events[i].Type != tt.res.eventTypes[i] {
|
||||
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], agg.Events[i].Type.String())
|
||||
}
|
||||
}
|
||||
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkipMfaAggregate(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -824,6 +872,54 @@ func TestRequestSetPasswordAggregate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordCodeSentAggregate(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
existing *model.User
|
||||
aggCreator *models.AggregateCreator
|
||||
}
|
||||
type res struct {
|
||||
eventLen int
|
||||
eventTypes []models.EventType
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "user password code sent aggregate ok",
|
||||
args: args{
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.User{ObjectRoot: models.ObjectRoot{AggregateID: "ID"}},
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
},
|
||||
res: res{
|
||||
eventLen: 1,
|
||||
eventTypes: []models.EventType{model.UserPasswordCodeSent},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
agg, err := PasswordCodeSentAggregate(tt.args.aggCreator, tt.args.existing)(tt.args.ctx)
|
||||
|
||||
if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen {
|
||||
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
||||
}
|
||||
for i := 0; i < tt.res.eventLen; i++ {
|
||||
if tt.res.errFunc == nil && agg.Events[i].Type != tt.res.eventTypes[i] {
|
||||
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], agg.Events[i].Type.String())
|
||||
}
|
||||
}
|
||||
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangeProfileAggregate(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -846,9 +942,9 @@ func TestChangeProfileAggregate(t *testing.T) {
|
||||
args: args{
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.User{ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
||||
Profile: &model.Profile{UserName: "UserName"},
|
||||
Profile: &model.Profile{FirstName: "FirstName"},
|
||||
},
|
||||
profile: &model.Profile{FirstName: ""},
|
||||
profile: &model.Profile{FirstName: "FirstNameChanged"},
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
},
|
||||
res: res{
|
||||
@@ -1114,6 +1210,55 @@ func TestCreateEmailCodeAggregate(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmailCodeSentAggregate(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
existing *model.User
|
||||
aggCreator *models.AggregateCreator
|
||||
}
|
||||
type res struct {
|
||||
eventLen int
|
||||
eventTypes []models.EventType
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "user email code sent aggregate ok",
|
||||
args: args{
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.User{ObjectRoot: models.ObjectRoot{AggregateID: "ID"}},
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
},
|
||||
res: res{
|
||||
eventLen: 1,
|
||||
eventTypes: []models.EventType{model.UserEmailCodeSent},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
agg, err := EmailCodeSentAggregate(tt.args.aggCreator, tt.args.existing)(tt.args.ctx)
|
||||
|
||||
if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen {
|
||||
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
||||
}
|
||||
for i := 0; i < tt.res.eventLen; i++ {
|
||||
if tt.res.errFunc == nil && agg.Events[i].Type != tt.res.eventTypes[i] {
|
||||
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], agg.Events[i].Type.String())
|
||||
}
|
||||
}
|
||||
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangePhoneAggregate(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -1137,9 +1282,9 @@ func TestChangePhoneAggregate(t *testing.T) {
|
||||
args: args{
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.User{ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
||||
Phone: &model.Phone{PhoneNumber: "PhoneNumber"},
|
||||
Phone: &model.Phone{PhoneNumber: "+41791234567"},
|
||||
},
|
||||
phone: &model.Phone{PhoneNumber: "Changed", IsPhoneVerified: true},
|
||||
phone: &model.Phone{PhoneNumber: "+41799876543", IsPhoneVerified: true},
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
},
|
||||
res: res{
|
||||
@@ -1346,6 +1491,54 @@ func TestCreatePhoneCodeAggregate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPhoneCodeSentAggregate(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
existing *model.User
|
||||
aggCreator *models.AggregateCreator
|
||||
}
|
||||
type res struct {
|
||||
eventLen int
|
||||
eventTypes []models.EventType
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "user phone code sent aggregate ok",
|
||||
args: args{
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.User{ObjectRoot: models.ObjectRoot{AggregateID: "ID"}},
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
},
|
||||
res: res{
|
||||
eventLen: 1,
|
||||
eventTypes: []models.EventType{model.UserPhoneCodeSent},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
agg, err := PhoneCodeSentAggregate(tt.args.aggCreator, tt.args.existing)(tt.args.ctx)
|
||||
|
||||
if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen {
|
||||
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
||||
}
|
||||
for i := 0; i < tt.res.eventLen; i++ {
|
||||
if tt.res.errFunc == nil && agg.Events[i].Type != tt.res.eventTypes[i] {
|
||||
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], agg.Events[i].Type.String())
|
||||
}
|
||||
}
|
||||
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangeAddressAggregate(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
|
112
internal/user/repository/view/model/notify_user.go
Normal file
112
internal/user/repository/view/model/notify_user.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/caos/logging"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/caos/zitadel/internal/user/model"
|
||||
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
NotifyUserKeyUserID = "id"
|
||||
)
|
||||
|
||||
type NotifyUser struct {
|
||||
ID string `json:"-" gorm:"column:id;primary_key"`
|
||||
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
|
||||
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
|
||||
ResourceOwner string `json:"-" gorm:"column:resource_owner"`
|
||||
UserName string `json:"userName" gorm:"column:user_name"`
|
||||
FirstName string `json:"firstName" gorm:"column:first_name"`
|
||||
LastName string `json:"lastName" gorm:"column:last_name"`
|
||||
NickName string `json:"nickName" gorm:"column:nick_name"`
|
||||
DisplayName string `json:"displayName" gorm:"column:display_name"`
|
||||
PreferredLanguage string `json:"preferredLanguage" gorm:"column:preferred_language"`
|
||||
Gender int32 `json:"gender" gorm:"column:gender"`
|
||||
LastEmail string `json:"email" gorm:"column:last_email"`
|
||||
VerifiedEmail string `json:"-" gorm:"column:verified_email"`
|
||||
LastPhone string `json:"phone" gorm:"column:last_phone"`
|
||||
VerifiedPhone string `json:"-" gorm:"column:verified_phone"`
|
||||
Sequence uint64 `json:"-" gorm:"column:sequence"`
|
||||
}
|
||||
|
||||
func NotifyUserFromModel(user *model.NotifyUser) *NotifyUser {
|
||||
return &NotifyUser{
|
||||
ID: user.ID,
|
||||
ChangeDate: user.ChangeDate,
|
||||
CreationDate: user.CreationDate,
|
||||
ResourceOwner: user.ResourceOwner,
|
||||
UserName: user.UserName,
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
NickName: user.NickName,
|
||||
DisplayName: user.DisplayName,
|
||||
PreferredLanguage: user.PreferredLanguage,
|
||||
Gender: int32(user.Gender),
|
||||
LastEmail: user.LastEmail,
|
||||
VerifiedEmail: user.VerifiedEmail,
|
||||
LastPhone: user.LastPhone,
|
||||
VerifiedPhone: user.VerifiedPhone,
|
||||
Sequence: user.Sequence,
|
||||
}
|
||||
}
|
||||
|
||||
func NotifyUserToModel(user *NotifyUser) *model.NotifyUser {
|
||||
return &model.NotifyUser{
|
||||
ID: user.ID,
|
||||
ChangeDate: user.ChangeDate,
|
||||
CreationDate: user.CreationDate,
|
||||
ResourceOwner: user.ResourceOwner,
|
||||
UserName: user.UserName,
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
NickName: user.NickName,
|
||||
DisplayName: user.DisplayName,
|
||||
PreferredLanguage: user.PreferredLanguage,
|
||||
Gender: model.Gender(user.Gender),
|
||||
LastEmail: user.LastEmail,
|
||||
VerifiedEmail: user.VerifiedEmail,
|
||||
LastPhone: user.LastPhone,
|
||||
VerifiedPhone: user.VerifiedPhone,
|
||||
Sequence: user.Sequence,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *NotifyUser) AppendEvent(event *models.Event) (err error) {
|
||||
u.ChangeDate = event.CreationDate
|
||||
u.Sequence = event.Sequence
|
||||
switch event.Type {
|
||||
case es_model.UserAdded,
|
||||
es_model.UserRegistered:
|
||||
u.CreationDate = event.CreationDate
|
||||
u.setRootData(event)
|
||||
err = u.setData(event)
|
||||
case es_model.UserProfileChanged:
|
||||
err = u.setData(event)
|
||||
case es_model.UserEmailChanged:
|
||||
err = u.setData(event)
|
||||
case es_model.UserEmailVerified:
|
||||
u.VerifiedEmail = u.LastEmail
|
||||
case es_model.UserPhoneChanged:
|
||||
err = u.setData(event)
|
||||
case es_model.UserPhoneVerified:
|
||||
u.VerifiedPhone = u.LastPhone
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (u *NotifyUser) setRootData(event *models.Event) {
|
||||
u.ID = event.AggregateID
|
||||
u.ResourceOwner = event.ResourceOwner
|
||||
}
|
||||
|
||||
func (u *NotifyUser) setData(event *models.Event) error {
|
||||
if err := json.Unmarshal(event.Data, u); err != nil {
|
||||
logging.Log("EVEN-lso9e").WithError(err).Error("could not unmarshal event data")
|
||||
return caos_errs.ThrowInternal(nil, "MODEL-8iows", "could not unmarshal data")
|
||||
}
|
||||
return nil
|
||||
}
|
113
internal/user/repository/view/model/notify_user_test.go
Normal file
113
internal/user/repository/view/model/notify_user_test.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNotifyUserAppendEvent(t *testing.T) {
|
||||
type args struct {
|
||||
event *es_models.Event
|
||||
user *NotifyUser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result *NotifyUser
|
||||
}{
|
||||
{
|
||||
name: "append added user event",
|
||||
args: args{
|
||||
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_model.UserAdded, ResourceOwner: "OrgID", Data: mockUserData(getFullUser(nil))},
|
||||
user: &NotifyUser{},
|
||||
},
|
||||
result: &NotifyUser{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", LastEmail: "Email", LastPhone: "Phone"},
|
||||
},
|
||||
{
|
||||
name: "append change user profile event",
|
||||
args: args{
|
||||
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_model.UserProfileChanged, ResourceOwner: "OrgID", Data: mockProfileData(&es_model.Profile{FirstName: "FirstNameChanged"})},
|
||||
user: &NotifyUser{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", LastEmail: "Email", LastPhone: "Phone"},
|
||||
},
|
||||
result: &NotifyUser{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstNameChanged", LastName: "LastName", LastEmail: "Email", LastPhone: "Phone"},
|
||||
},
|
||||
{
|
||||
name: "append change user email event",
|
||||
args: args{
|
||||
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_model.UserEmailChanged, ResourceOwner: "OrgID", Data: mockEmailData(&es_model.Email{EmailAddress: "EmailChanged"})},
|
||||
user: &NotifyUser{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", LastEmail: "Email", LastPhone: "Phone"},
|
||||
},
|
||||
result: &NotifyUser{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", LastEmail: "EmailChanged", LastPhone: "Phone"},
|
||||
},
|
||||
{
|
||||
name: "append change user email event, existing email",
|
||||
args: args{
|
||||
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_model.UserEmailChanged, ResourceOwner: "OrgID", Data: mockEmailData(&es_model.Email{EmailAddress: "EmailChanged"})},
|
||||
user: &NotifyUser{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", LastEmail: "Email", VerifiedEmail: "Email", LastPhone: "Phone"},
|
||||
},
|
||||
result: &NotifyUser{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", LastEmail: "EmailChanged", VerifiedEmail: "Email", LastPhone: "Phone"},
|
||||
},
|
||||
{
|
||||
name: "append verify user email event",
|
||||
args: args{
|
||||
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_model.UserEmailVerified, ResourceOwner: "OrgID"},
|
||||
user: &NotifyUser{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", LastEmail: "Email", LastPhone: "Phone"},
|
||||
},
|
||||
result: &NotifyUser{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", LastEmail: "Email", VerifiedEmail: "Email", LastPhone: "Phone"},
|
||||
},
|
||||
{
|
||||
name: "append change user phone event",
|
||||
args: args{
|
||||
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_model.UserPhoneChanged, ResourceOwner: "OrgID", Data: mockPhoneData(&es_model.Phone{PhoneNumber: "PhoneChanged"})},
|
||||
user: &NotifyUser{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", LastEmail: "Email", LastPhone: "Phone"},
|
||||
},
|
||||
result: &NotifyUser{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", LastEmail: "Email", LastPhone: "PhoneChanged"},
|
||||
},
|
||||
{
|
||||
name: "append change user phone event, existing phone",
|
||||
args: args{
|
||||
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_model.UserPhoneChanged, ResourceOwner: "OrgID", Data: mockPhoneData(&es_model.Phone{PhoneNumber: "PhoneChanged"})},
|
||||
user: &NotifyUser{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", LastEmail: "Email", LastPhone: "Phone", VerifiedPhone: "Phone"},
|
||||
},
|
||||
result: &NotifyUser{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", LastEmail: "Email", LastPhone: "PhoneChanged", VerifiedPhone: "Phone"},
|
||||
},
|
||||
{
|
||||
name: "append verify user phone event",
|
||||
args: args{
|
||||
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_model.UserPhoneVerified, ResourceOwner: "OrgID"},
|
||||
user: &NotifyUser{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", LastEmail: "Email", LastPhone: "Phone"},
|
||||
},
|
||||
result: &NotifyUser{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", FirstName: "FirstName", LastName: "LastName", LastEmail: "Email", LastPhone: "Phone", VerifiedPhone: "Phone"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.args.user.AppendEvent(tt.args.event)
|
||||
if tt.args.user.ID != tt.result.ID {
|
||||
t.Errorf("got wrong result ID: expected: %v, actual: %v ", tt.result.ID, tt.args.user.ID)
|
||||
}
|
||||
if tt.args.user.FirstName != tt.result.FirstName {
|
||||
t.Errorf("got wrong result FirstName: expected: %v, actual: %v ", tt.result.FirstName, tt.args.user.FirstName)
|
||||
}
|
||||
if tt.args.user.LastName != tt.result.LastName {
|
||||
t.Errorf("got wrong result FirstName: expected: %v, actual: %v ", tt.result.FirstName, tt.args.user.FirstName)
|
||||
}
|
||||
if tt.args.user.ResourceOwner != tt.result.ResourceOwner {
|
||||
t.Errorf("got wrong result ResourceOwner: expected: %v, actual: %v ", tt.result.ResourceOwner, tt.args.user.ResourceOwner)
|
||||
}
|
||||
if tt.args.user.LastEmail != tt.result.LastEmail {
|
||||
t.Errorf("got wrong result LastEmail: expected: %v, actual: %v ", tt.result.LastEmail, tt.args.user.LastEmail)
|
||||
}
|
||||
if tt.args.user.VerifiedEmail != tt.result.VerifiedEmail {
|
||||
t.Errorf("got wrong result VerifiedEmail: expected: %v, actual: %v ", tt.result.VerifiedEmail, tt.args.user.VerifiedEmail)
|
||||
}
|
||||
if tt.args.user.LastPhone != tt.result.LastPhone {
|
||||
t.Errorf("got wrong result LastPhone: expected: %v, actual: %v ", tt.result.LastPhone, tt.args.user.LastPhone)
|
||||
}
|
||||
if tt.args.user.VerifiedPhone != tt.result.VerifiedPhone {
|
||||
t.Errorf("got wrong result VerifiedPhone: expected: %v, actual: %v ", tt.result.VerifiedPhone, tt.args.user.VerifiedPhone)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
25
internal/user/repository/view/notify_user.go
Normal file
25
internal/user/repository/view/notify_user.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||
"github.com/caos/zitadel/internal/user/repository/view/model"
|
||||
"github.com/caos/zitadel/internal/view"
|
||||
"github.com/jinzhu/gorm"
|
||||
)
|
||||
|
||||
func NotifyUserByID(db *gorm.DB, table, userID string) (*model.NotifyUser, error) {
|
||||
user := new(model.NotifyUser)
|
||||
query := view.PrepareGetByKey(table, model.UserSearchKey(usr_model.NOTIFYUSERSEARCHKEY_USER_ID), userID)
|
||||
err := query(db, user)
|
||||
return user, err
|
||||
}
|
||||
|
||||
func PutNotifyUser(db *gorm.DB, table string, project *model.NotifyUser) error {
|
||||
save := view.PrepareSave(table)
|
||||
return save(db, project)
|
||||
}
|
||||
|
||||
func DeleteNotifyUser(db *gorm.DB, table, userID string) error {
|
||||
delete := view.PrepareDeleteByKey(table, model.UserSearchKey(usr_model.NOTIFYUSERSEARCHKEY_USER_ID), userID)
|
||||
return delete(db)
|
||||
}
|
Reference in New Issue
Block a user