package command import ( "context" "testing" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "golang.org/x/text/language" "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/http" "github.com/zitadel/zitadel/internal/command/preparation" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/errors" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/repository" "github.com/zitadel/zitadel/internal/eventstore/v1/models" "github.com/zitadel/zitadel/internal/id" id_mock "github.com/zitadel/zitadel/internal/id/mock" "github.com/zitadel/zitadel/internal/repository/org" "github.com/zitadel/zitadel/internal/repository/user" ) func TestAddDomain(t *testing.T) { type args struct { a *org.Aggregate domain string claimedUserIDs []string idGenerator id.Generator filter preparation.FilterToQueryReducer } agg := org.NewAggregate("test") tests := []struct { name string args args want Want }{ { name: "invalid domain", args: args{ a: agg, domain: "", }, want: Want{ ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-r3h4J", "Errors.Invalid.Argument"), }, }, { name: "correct (should verify domain)", args: args{ a: agg, domain: "domain", claimedUserIDs: []string{"userID1"}, filter: func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { return []eventstore.Event{ org.NewDomainPolicyAddedEvent(ctx, &agg.Aggregate, true, true, true), }, nil }, }, want: Want{ Commands: []eventstore.Command{ org.NewDomainAddedEvent(context.Background(), &agg.Aggregate, "domain"), }, }, }, { name: "correct (should not verify domain)", args: args{ a: agg, domain: "domain", claimedUserIDs: []string{"userID1"}, idGenerator: id_mock.ExpectID(t, "newID"), filter: func() func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { i := 0 //TODO: we should fix this in the future to use some kind of mock struct and expect filter calls return func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { if i == 2 { i++ return []eventstore.Event{user.NewHumanAddedEvent( ctx, &user.NewAggregate("userID1", "org2").Aggregate, "username", "firstname", "lastname", "nickname", "displayname", language.Und, domain.GenderUnspecified, "email", false, )}, nil } if i == 3 { i++ return []eventstore.Event{org.NewDomainPolicyAddedEvent(ctx, &agg.Aggregate, false, false, false)}, nil } i++ return []eventstore.Event{org.NewDomainPolicyAddedEvent(ctx, &agg.Aggregate, true, false, false)}, nil } }(), }, want: Want{ Commands: []eventstore.Command{ org.NewDomainAddedEvent(context.Background(), &agg.Aggregate, "domain"), org.NewDomainVerifiedEvent(context.Background(), &agg.Aggregate, "domain"), user.NewDomainClaimedEvent(context.Background(), &user.NewAggregate("userID1", "org2").Aggregate, "newID@temporary.domain", "username", false), }, }, }, { name: "already verified", args: args{ a: agg, domain: "domain", claimedUserIDs: []string{"userID1"}, filter: func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { return []eventstore.Event{ org.NewDomainAddedEvent(ctx, &agg.Aggregate, "domain"), org.NewDomainVerificationAddedEvent(ctx, &agg.Aggregate, "domain", domain.OrgDomainValidationTypeHTTP, nil), org.NewDomainVerifiedEvent(ctx, &agg.Aggregate, "domain"), }, nil }, }, want: Want{ CreateErr: errors.ThrowAlreadyExists(nil, "", ""), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { AssertValidation( t, authz.WithRequestedDomain(context.Background(), "domain"), (&Commands{idGenerator: tt.args.idGenerator}).prepareAddOrgDomain(tt.args.a, tt.args.domain, tt.args.claimedUserIDs), tt.args.filter, tt.want, ) }) } } func TestVerifyDomain(t *testing.T) { type args struct { a *org.Aggregate domain string } tests := []struct { name string args args want Want }{ { name: "invalid domain", args: args{ a: org.NewAggregate("test"), domain: "", }, want: Want{ ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-yqlVQ", "Errors.Invalid.Argument"), }, }, { name: "correct", args: args{ a: org.NewAggregate("test"), domain: "domain", }, want: Want{ Commands: []eventstore.Command{ org.NewDomainVerifiedEvent(context.Background(), &org.NewAggregate("test").Aggregate, "domain"), }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { AssertValidation(t, context.Background(), verifyOrgDomain(tt.args.a, tt.args.domain), nil, tt.want) }) } } func TestSetDomainPrimary(t *testing.T) { type args struct { a *org.Aggregate domain string filter preparation.FilterToQueryReducer } agg := org.NewAggregate("test") tests := []struct { name string args args want Want }{ { name: "invalid domain", args: args{ a: agg, domain: "", }, want: Want{ ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-gmNqY", "Errors.Invalid.Argument"), }, }, { name: "not exists", args: args{ a: agg, domain: "domain", filter: func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { return nil, nil }, }, want: Want{ CreateErr: errors.ThrowNotFound(nil, "", ""), }, }, { name: "not verified", args: args{ a: agg, domain: "domain", filter: func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { return []eventstore.Event{org.NewDomainAddedEvent(ctx, &agg.Aggregate, "domain")}, nil }, }, want: Want{ CreateErr: errors.ThrowPreconditionFailed(nil, "", ""), }, }, { name: "already primary", args: args{ a: agg, domain: "domain", filter: func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { return []eventstore.Event{ org.NewDomainAddedEvent(ctx, &agg.Aggregate, "domain"), org.NewDomainVerificationAddedEvent(ctx, &agg.Aggregate, "domain", domain.OrgDomainValidationTypeHTTP, nil), org.NewDomainVerifiedEvent(ctx, &agg.Aggregate, "domain"), org.NewDomainPrimarySetEvent(ctx, &agg.Aggregate, "domain"), }, nil }, }, want: Want{ CreateErr: errors.ThrowPreconditionFailed(nil, "", ""), }, }, { name: "correct", args: args{ a: agg, domain: "domain", filter: func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { return []eventstore.Event{ org.NewDomainAddedEvent(ctx, &agg.Aggregate, "domain"), org.NewDomainVerificationAddedEvent(ctx, &agg.Aggregate, "domain", domain.OrgDomainValidationTypeHTTP, nil), org.NewDomainVerifiedEvent(ctx, &agg.Aggregate, "domain"), }, nil }, }, want: Want{ Commands: []eventstore.Command{ org.NewDomainPrimarySetEvent(context.Background(), &agg.Aggregate, "domain"), }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { AssertValidation(t, context.Background(), setPrimaryOrgDomain(tt.args.a, tt.args.domain), tt.args.filter, tt.want) }) } } func TestCommandSide_AddOrgDomain(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore } type args struct { ctx context.Context orgID string domain string claimedUserIDs []string } type res struct { want *domain.ObjectDetails err func(error) bool } tests := []struct { name string fields fields args args res res }{ { name: "invalid domain, error", fields: fields{ eventstore: eventstoreExpect( t, ), }, args: args{ ctx: context.Background(), }, res: res{ err: errors.IsErrorInvalidArgument, }, }, { name: "domain already exists, precondition error", fields: fields{ eventstore: eventstoreExpect( t, expectFilter( eventFromEventPusher( org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "name", ), ), eventFromEventPusher( org.NewDomainAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", ), ), ), ), }, args: args{ ctx: context.Background(), orgID: "org1", domain: "domain.ch", }, res: res{ err: errors.IsErrorAlreadyExists, }, }, { name: "domain add, ok", fields: fields{ eventstore: eventstoreExpect( t, expectFilter( eventFromEventPusher( org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "name", ), ), ), expectFilter( eventFromEventPusher( org.NewDomainPolicyAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, true, true, true, ), ), ), expectPush( []*repository.Event{ eventFromEventPusher(org.NewDomainAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", )), }, ), ), }, args: args{ ctx: context.Background(), orgID: "org1", domain: "domain.ch", }, res: res{ want: &domain.ObjectDetails{ ResourceOwner: "org1", }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ eventstore: tt.fields.eventstore, } got, err := r.AddOrgDomain(tt.args.ctx, tt.args.orgID, tt.args.domain, tt.args.claimedUserIDs) if tt.res.err == nil { assert.NoError(t, err) } if tt.res.err != nil && !tt.res.err(err) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { assert.Equal(t, tt.res.want, got) } }) } } func TestCommandSide_GenerateOrgDomainValidation(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore secretGenerator crypto.Generator } type args struct { ctx context.Context domain *domain.OrgDomain } type res struct { wantToken string wantURL string err func(error) bool } tests := []struct { name string fields fields args args res res }{ { name: "invalid domain, error", fields: fields{ eventstore: eventstoreExpect( t, ), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, }, }, res: res{ err: errors.IsErrorInvalidArgument, }, }, { name: "missing aggregateid, error", fields: fields{ eventstore: eventstoreExpect( t, ), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ Domain: "domain.ch", }, }, res: res{ err: errors.IsErrorInvalidArgument, }, }, { name: "invalid validation type, error", fields: fields{ eventstore: eventstoreExpect( t, ), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, Domain: "domain.ch", }, }, res: res{ err: errors.IsErrorInvalidArgument, }, }, { name: "domain not exists, precondition error", fields: fields{ eventstore: eventstoreExpect( t, expectFilter( eventFromEventPusher( org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "name", ), ), ), ), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, Domain: "domain.ch", ValidationType: domain.OrgDomainValidationTypeDNS, }, }, res: res{ err: errors.IsNotFound, }, }, { name: "domain already verified, precondition error", fields: fields{ eventstore: eventstoreExpect( t, expectFilter( eventFromEventPusher( org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "name", ), ), eventFromEventPusher( org.NewDomainAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", ), ), eventFromEventPusher( org.NewDomainVerifiedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", ), ), ), ), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, Domain: "domain.ch", ValidationType: domain.OrgDomainValidationTypeDNS, }, }, res: res{ err: errors.IsPreconditionFailed, }, }, { name: "add dns validation, ok", fields: fields{ eventstore: eventstoreExpect( t, expectFilter( eventFromEventPusher( org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "name", ), ), eventFromEventPusher( org.NewDomainAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", ), ), ), expectPush( []*repository.Event{ eventFromEventPusher(org.NewDomainVerificationAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", domain.OrgDomainValidationTypeDNS, &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("a"), }, )), }, ), ), secretGenerator: GetMockSecretGenerator(t), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, Domain: "domain.ch", ValidationType: domain.OrgDomainValidationTypeDNS, }, }, res: res{ wantToken: "a", wantURL: "_zitadel-challenge.domain.ch", }, }, { name: "add http validation, ok", fields: fields{ eventstore: eventstoreExpect( t, expectFilter( eventFromEventPusher( org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "name", ), ), eventFromEventPusher( org.NewDomainAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", ), ), ), expectPush( []*repository.Event{ eventFromEventPusher(org.NewDomainVerificationAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", domain.OrgDomainValidationTypeHTTP, &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("a"), }, )), }, ), ), secretGenerator: GetMockSecretGenerator(t), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, Domain: "domain.ch", ValidationType: domain.OrgDomainValidationTypeHTTP, }, }, res: res{ wantToken: "a", wantURL: "https://domain.ch/.well-known/zitadel-challenge/a", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ eventstore: tt.fields.eventstore, domainVerificationGenerator: tt.fields.secretGenerator, } token, url, err := r.GenerateOrgDomainValidation(tt.args.ctx, tt.args.domain) if tt.res.err == nil { assert.NoError(t, err) } if tt.res.err != nil && !tt.res.err(err) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { assert.Equal(t, tt.res.wantToken, token) assert.Equal(t, tt.res.wantURL, url) } }) } } func TestCommandSide_ValidateOrgDomain(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore idGenerator id.Generator secretGenerator crypto.Generator alg crypto.EncryptionAlgorithm domainValidationFunc func(domain, token, verifier string, checkType http.CheckType) error } type args struct { ctx context.Context domain *domain.OrgDomain claimedUserIDs []string } type res struct { want *domain.ObjectDetails err func(error) bool } tests := []struct { name string fields fields args args res res }{ { name: "invalid domain, error", fields: fields{ eventstore: eventstoreExpect( t, ), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, }, }, res: res{ err: errors.IsErrorInvalidArgument, }, }, { name: "missing aggregateid, error", fields: fields{ eventstore: eventstoreExpect( t, ), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ Domain: "domain.ch", }, }, res: res{ err: errors.IsErrorInvalidArgument, }, }, { name: "domain not exists, precondition error", fields: fields{ eventstore: eventstoreExpect( t, expectFilter( eventFromEventPusher( org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "name", ), ), ), ), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, Domain: "domain.ch", ValidationType: domain.OrgDomainValidationTypeDNS, }, }, res: res{ err: errors.IsNotFound, }, }, { name: "domain already verified, precondition error", fields: fields{ eventstore: eventstoreExpect( t, expectFilter( eventFromEventPusher( org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "name", ), ), eventFromEventPusher( org.NewDomainAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", ), ), eventFromEventPusher( org.NewDomainVerifiedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", ), ), ), ), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, Domain: "domain.ch", ValidationType: domain.OrgDomainValidationTypeDNS, }, }, res: res{ err: errors.IsPreconditionFailed, }, }, { name: "no code existing, precondition error", fields: fields{ eventstore: eventstoreExpect( t, expectFilter( eventFromEventPusher( org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "name", ), ), eventFromEventPusher( org.NewDomainAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", ), ), ), ), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, Domain: "domain.ch", ValidationType: domain.OrgDomainValidationTypeDNS, }, }, res: res{ err: errors.IsPreconditionFailed, }, }, { name: "invalid domain verification, precondition error", fields: fields{ eventstore: eventstoreExpect( t, expectFilter( eventFromEventPusher( org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "name", ), ), eventFromEventPusher( org.NewDomainAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", ), ), eventFromEventPusher( org.NewDomainVerificationAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", domain.OrgDomainValidationTypeDNS, &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("a"), }, ), ), ), expectPush( []*repository.Event{ eventFromEventPusher(org.NewDomainVerificationFailedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", )), }, ), ), alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), domainValidationFunc: invalidDomainVerification, }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, Domain: "domain.ch", ValidationType: domain.OrgDomainValidationTypeDNS, }, }, res: res{ err: errors.IsErrorInvalidArgument, }, }, { name: "domain verification, ok", fields: fields{ eventstore: eventstoreExpect( t, expectFilter( eventFromEventPusher( org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "name", ), ), eventFromEventPusher( org.NewDomainAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", ), ), eventFromEventPusher( org.NewDomainVerificationAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", domain.OrgDomainValidationTypeDNS, &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("a"), }, ), ), ), expectPush( []*repository.Event{ eventFromEventPusher(org.NewDomainVerifiedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", )), }, uniqueConstraintsFromEventConstraint(org.NewAddOrgDomainUniqueConstraint("domain.ch")), ), ), alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), domainValidationFunc: validDomainVerification, }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, Domain: "domain.ch", ValidationType: domain.OrgDomainValidationTypeDNS, }, }, res: res{ want: &domain.ObjectDetails{ ResourceOwner: "org1", }, }, }, { name: "domain verification, claimed users not found, ok", fields: fields{ eventstore: eventstoreExpect( t, expectFilter( eventFromEventPusher( org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "name", ), ), eventFromEventPusher( org.NewDomainAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", ), ), eventFromEventPusher( org.NewDomainVerificationAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", domain.OrgDomainValidationTypeDNS, &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("a"), }, ), ), ), expectFilter(), expectPush( []*repository.Event{ eventFromEventPusher(org.NewDomainVerifiedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", )), }, uniqueConstraintsFromEventConstraint(org.NewAddOrgDomainUniqueConstraint("domain.ch")), ), ), alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), domainValidationFunc: validDomainVerification, }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, Domain: "domain.ch", ValidationType: domain.OrgDomainValidationTypeDNS, }, claimedUserIDs: []string{"user1"}, }, res: res{ want: &domain.ObjectDetails{ ResourceOwner: "org1", }, }, }, { name: "domain verification, claimed users, ok", fields: fields{ eventstore: eventstoreExpect( t, expectFilter( eventFromEventPusher( org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "name", ), ), eventFromEventPusher( org.NewDomainAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", ), ), eventFromEventPusher( org.NewDomainVerificationAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", domain.OrgDomainValidationTypeDNS, &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "id", Crypted: []byte("a"), }, ), ), ), expectFilter( eventFromEventPusher( user.NewHumanAddedEvent(context.Background(), &user.NewAggregate("user1", "org2").Aggregate, "username@domain.ch", "firstname", "lastname", "nickname", "displayname", language.German, domain.GenderUnspecified, "email", true, ), ), ), expectFilter( eventFromEventPusher( org.NewDomainPolicyAddedEvent(context.Background(), &org.NewAggregate("org2").Aggregate, false, false, false))), expectPush( []*repository.Event{ eventFromEventPusher(org.NewDomainVerifiedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", )), eventFromEventPusher(user.NewDomainClaimedEvent(context.Background(), &user.NewAggregate("user1", "org2").Aggregate, "tempid@temporary.zitadel.ch", "username@domain.ch", false, )), }, uniqueConstraintsFromEventConstraint(org.NewAddOrgDomainUniqueConstraint("domain.ch")), uniqueConstraintsFromEventConstraint(user.NewRemoveUsernameUniqueConstraint("username@domain.ch", "org2", false)), uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("tempid@temporary.zitadel.ch", "org2", false)), ), ), alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), domainValidationFunc: validDomainVerification, idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "tempid"), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, Domain: "domain.ch", ValidationType: domain.OrgDomainValidationTypeDNS, }, claimedUserIDs: []string{"user1"}, }, res: res{ want: &domain.ObjectDetails{ ResourceOwner: "org1", }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ eventstore: tt.fields.eventstore, domainVerificationGenerator: tt.fields.secretGenerator, domainVerificationAlg: tt.fields.alg, domainVerificationValidator: tt.fields.domainValidationFunc, idGenerator: tt.fields.idGenerator, } got, err := r.ValidateOrgDomain(authz.WithRequestedDomain(tt.args.ctx, "zitadel.ch"), tt.args.domain, tt.args.claimedUserIDs) if tt.res.err == nil { assert.NoError(t, err) } if tt.res.err != nil && !tt.res.err(err) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { assert.Equal(t, tt.res.want, got) } }) } } func TestCommandSide_SetPrimaryDomain(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore } type args struct { ctx context.Context domain *domain.OrgDomain } type res struct { want *domain.ObjectDetails err func(error) bool } tests := []struct { name string fields fields args args res res }{ { name: "invalid domain, error", fields: fields{ eventstore: eventstoreExpect( t, ), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, }, }, res: res{ err: errors.IsErrorInvalidArgument, }, }, { name: "missing aggregateid, error", fields: fields{ eventstore: eventstoreExpect( t, ), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ Domain: "domain.ch", }, }, res: res{ err: errors.IsErrorInvalidArgument, }, }, { name: "domain not exists, precondition error", fields: fields{ eventstore: eventstoreExpect( t, expectFilter( eventFromEventPusher( org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "name", ), ), ), ), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, Domain: "domain.ch", ValidationType: domain.OrgDomainValidationTypeDNS, }, }, res: res{ err: errors.IsNotFound, }, }, { name: "domain not verified, precondition error", fields: fields{ eventstore: eventstoreExpect( t, expectFilter( eventFromEventPusher( org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "name", ), ), eventFromEventPusher( org.NewDomainAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", ), ), ), ), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, Domain: "domain.ch", }, }, res: res{ err: errors.IsPreconditionFailed, }, }, { name: "set primary, ok", fields: fields{ eventstore: eventstoreExpect( t, expectFilter( eventFromEventPusher( org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "name", ), ), eventFromEventPusher( org.NewDomainAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", ), ), eventFromEventPusher( org.NewDomainVerifiedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", ), ), ), expectPush( []*repository.Event{ eventFromEventPusher(org.NewDomainPrimarySetEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", )), }, ), ), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, Domain: "domain.ch", }, }, res: res{ want: &domain.ObjectDetails{ ResourceOwner: "org1", }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ eventstore: tt.fields.eventstore, } got, err := r.SetPrimaryOrgDomain(tt.args.ctx, tt.args.domain) if tt.res.err == nil { assert.NoError(t, err) } if tt.res.err != nil && !tt.res.err(err) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { assert.Equal(t, tt.res.want, got) } }) } } func TestCommandSide_RemoveOrgDomain(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore } type args struct { ctx context.Context domain *domain.OrgDomain } type res struct { want *domain.ObjectDetails err func(error) bool } tests := []struct { name string fields fields args args res res }{ { name: "invalid domain, error", fields: fields{ eventstore: eventstoreExpect( t, ), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, }, }, res: res{ err: errors.IsErrorInvalidArgument, }, }, { name: "missing aggregateid, error", fields: fields{ eventstore: eventstoreExpect( t, ), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ Domain: "domain.ch", }, }, res: res{ err: errors.IsErrorInvalidArgument, }, }, { name: "domain not exists, precondition error", fields: fields{ eventstore: eventstoreExpect( t, expectFilter( eventFromEventPusher( org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "name", ), ), ), ), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, Domain: "domain.ch", ValidationType: domain.OrgDomainValidationTypeDNS, }, }, res: res{ err: errors.IsNotFound, }, }, { name: "remove verified domain, ok", fields: fields{ eventstore: eventstoreExpect( t, expectFilter( eventFromEventPusher( org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "name", ), ), eventFromEventPusher( org.NewDomainAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", ), ), eventFromEventPusher( org.NewDomainVerifiedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", ), ), eventFromEventPusher( org.NewDomainPrimarySetEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", ), ), ), ), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, Domain: "domain.ch", }, }, res: res{ err: errors.IsPreconditionFailed, }, }, { name: "remove domain, ok", fields: fields{ eventstore: eventstoreExpect( t, expectFilter( eventFromEventPusher( org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "name", ), ), eventFromEventPusher( org.NewDomainAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", ), ), ), expectPush( []*repository.Event{ eventFromEventPusher(org.NewDomainRemovedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", false, )), }, ), ), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, Domain: "domain.ch", }, }, res: res{ want: &domain.ObjectDetails{ ResourceOwner: "org1", }, }, }, { name: "remove verified domain, ok", fields: fields{ eventstore: eventstoreExpect( t, expectFilter( eventFromEventPusher( org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "name", ), ), eventFromEventPusher( org.NewDomainAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", ), ), eventFromEventPusher( org.NewDomainVerifiedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", ), ), ), expectPush( []*repository.Event{ eventFromEventPusher(org.NewDomainRemovedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, "domain.ch", true, )), }, uniqueConstraintsFromEventConstraint(org.NewRemoveOrgDomainUniqueConstraint("domain.ch")), ), ), }, args: args{ ctx: context.Background(), domain: &domain.OrgDomain{ ObjectRoot: models.ObjectRoot{ AggregateID: "org1", }, Domain: "domain.ch", }, }, res: res{ want: &domain.ObjectDetails{ ResourceOwner: "org1", }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ eventstore: tt.fields.eventstore, } got, err := r.RemoveOrgDomain(tt.args.ctx, tt.args.domain) if tt.res.err == nil { assert.NoError(t, err) } if tt.res.err != nil && !tt.res.err(err) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { assert.Equal(t, tt.res.want, got) } }) } } func invalidDomainVerification(domain, token, verifier string, checkType http.CheckType) error { return errors.ThrowInvalidArgument(nil, "HTTP-GH422", "Errors.Internal") } func validDomainVerification(domain, token, verifier string, checkType http.CheckType) error { return nil }