diff --git a/backend/v3/api/object/object.go b/backend/v3/api/object/object.go new file mode 100644 index 000000000..b6d7c500a --- /dev/null +++ b/backend/v3/api/object/object.go @@ -0,0 +1,55 @@ +package object + +import ( + "github.com/zitadel/zitadel/backend/v3/storage/database" + "github.com/zitadel/zitadel/internal/zerrors" + v2_object "github.com/zitadel/zitadel/pkg/grpc/object/v2" + v2beta_object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta" +) + +// TODO(IAM-Marco): Remove in V5 (see https://github.com/zitadel/zitadel/issues/10877) +func TextQueryMethodBetaToV2(txtMethod v2beta_object.TextQueryMethod) v2_object.TextQueryMethod { + switch txtMethod { + case v2beta_object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS: + return v2_object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS + case v2beta_object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE: + return v2_object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE + case v2beta_object.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH: + return v2_object.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH + case v2beta_object.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH_IGNORE_CASE: + return v2_object.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH_IGNORE_CASE + case v2beta_object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS_IGNORE_CASE: + return v2_object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS_IGNORE_CASE + case v2beta_object.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH: + return v2_object.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH + case v2beta_object.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH_IGNORE_CASE: + return v2_object.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH_IGNORE_CASE + case v2beta_object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS: + fallthrough + default: + return v2_object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS + } +} + +func TextQueryMethodToTextOperation(txtMethod v2_object.TextQueryMethod) (database.TextOperation, error) { + switch txtMethod { + case v2_object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS: + return database.TextOperationContains, nil + case v2_object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE: + return database.TextOperationContainsIgnoreCase, nil + case v2_object.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH: + return database.TextOperationEndsWith, nil + case v2_object.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH_IGNORE_CASE: + return database.TextOperationEndsWithIgnoreCase, nil + case v2_object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS_IGNORE_CASE: + return database.TextOperationEqualIgnoreCase, nil + case v2_object.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH: + return database.TextOperationStartsWith, nil + case v2_object.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH_IGNORE_CASE: + return database.TextOperationStartsWithIgnoreCase, nil + case v2_object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS: + return database.TextOperationEqual, nil + default: + return 0, zerrors.ThrowInvalidArgument(nil, "OBJ-iBRBVe", "invalid text query method") + } +} diff --git a/backend/v3/api/object/object_test.go b/backend/v3/api/object/object_test.go new file mode 100644 index 000000000..33cadc6c0 --- /dev/null +++ b/backend/v3/api/object/object_test.go @@ -0,0 +1,77 @@ +package object + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/zitadel/zitadel/backend/v3/storage/database" + "github.com/zitadel/zitadel/internal/zerrors" + v2_object "github.com/zitadel/zitadel/pkg/grpc/object/v2" +) + +func TestTextQueryMethodToTextOperation(t *testing.T) { + t.Parallel() + tt := []struct { + name string + queryOperation v2_object.TextQueryMethod + + expectedOperation database.TextOperation + expectedError error + }{ + { + name: "contains", + queryOperation: v2_object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS, + expectedOperation: database.TextOperationContains, + }, + { + name: "contains ignore case", + queryOperation: v2_object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE, + expectedOperation: database.TextOperationContainsIgnoreCase, + }, + { + name: "ends with", + queryOperation: v2_object.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH, + expectedOperation: database.TextOperationEndsWith, + }, + { + name: "ends with ignore case", + queryOperation: v2_object.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH_IGNORE_CASE, + expectedOperation: database.TextOperationEndsWithIgnoreCase, + }, + { + name: "equals", + queryOperation: v2_object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS, + expectedOperation: database.TextOperationEqual, + }, + { + name: "equals ignore case", + queryOperation: v2_object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS_IGNORE_CASE, + expectedOperation: database.TextOperationEqualIgnoreCase, + }, + { + name: "starts with", + queryOperation: v2_object.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH, + expectedOperation: database.TextOperationStartsWith, + }, + { + name: "starts with ignore case", + queryOperation: v2_object.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH_IGNORE_CASE, + expectedOperation: database.TextOperationStartsWithIgnoreCase, + }, + { + name: "unknown operation", + queryOperation: v2_object.TextQueryMethod(99), + expectedError: zerrors.ThrowInvalidArgument(nil, "OBJ-iBRBVe", "invalid text query method"), + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + got, err := TextQueryMethodToTextOperation(tc.queryOperation) + assert.Equal(t, tc.expectedError, err) + assert.Equal(t, tc.expectedOperation, got) + }) + } +} diff --git a/backend/v3/api/org/v2/convert/org.go b/backend/v3/api/org/v2/convert/org.go new file mode 100644 index 000000000..f9cd9f956 --- /dev/null +++ b/backend/v3/api/org/v2/convert/org.go @@ -0,0 +1,155 @@ +package convert + +import ( + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/zitadel/zitadel/backend/v3/api/object" + "github.com/zitadel/zitadel/backend/v3/domain" + grpc_object "github.com/zitadel/zitadel/pkg/grpc/object/v2" + v2_org "github.com/zitadel/zitadel/pkg/grpc/org/v2" + v2beta_org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta" +) + +/* + * GRPC Beta v2 to GRPC v2 + */ + +// TODO(IAM-Marco): Remove in V5 (see https://github.com/zitadel/zitadel/issues/10877) +func OrganizationBetaRequestToV2Request(in *v2beta_org.ListOrganizationsRequest) *v2_org.ListOrganizationsRequest { + return &v2_org.ListOrganizationsRequest{ + Query: &grpc_object.ListQuery{ + Offset: in.GetPagination().GetOffset(), + Limit: in.GetPagination().GetLimit(), + Asc: in.GetPagination().GetAsc(), + }, + SortingColumn: organizationSortingColumnBetaToV2(in.GetSortingColumn()), + Queries: organizationQueriesBetaToV2(in.GetFilter()), + } +} + +func organizationSortingColumnBetaToV2(sc v2beta_org.OrgFieldName) v2_org.OrganizationFieldName { + switch sc { + case v2beta_org.OrgFieldName_ORG_FIELD_NAME_NAME: + return v2_org.OrganizationFieldName_ORGANIZATION_FIELD_NAME_NAME + case v2beta_org.OrgFieldName_ORG_FIELD_NAME_CREATION_DATE, v2beta_org.OrgFieldName_ORG_FIELD_NAME_UNSPECIFIED: + fallthrough + default: + return v2_org.OrganizationFieldName_ORGANIZATION_FIELD_NAME_UNSPECIFIED + } +} + +func organizationQueriesBetaToV2(queries []*v2beta_org.OrganizationSearchFilter) []*v2_org.SearchQuery { + toReturn := make([]*v2_org.SearchQuery, len(queries)) + + for i, query := range queries { + toReturn[i] = organizationQueryBetaToV2(query) + } + + return toReturn +} + +func organizationQueryBetaToV2(query *v2beta_org.OrganizationSearchFilter) *v2_org.SearchQuery { + toReturn := &v2_org.SearchQuery{} + + switch assertedType := query.GetFilter().(type) { + case *v2beta_org.OrganizationSearchFilter_DomainFilter: + toReturn.Query = &v2_org.SearchQuery_DomainQuery{ + DomainQuery: &v2_org.OrganizationDomainQuery{ + Domain: assertedType.DomainFilter.GetDomain(), + Method: object.TextQueryMethodBetaToV2(assertedType.DomainFilter.GetMethod()), + }, + } + + case *v2beta_org.OrganizationSearchFilter_IdFilter: + toReturn.Query = &v2_org.SearchQuery_IdQuery{ + IdQuery: &v2_org.OrganizationIDQuery{ + Id: assertedType.IdFilter.GetId(), + }, + } + case *v2beta_org.OrganizationSearchFilter_NameFilter: + toReturn.Query = &v2_org.SearchQuery_NameQuery{ + NameQuery: &v2_org.OrganizationNameQuery{ + Name: assertedType.NameFilter.GetName(), + Method: object.TextQueryMethodBetaToV2(assertedType.NameFilter.GetMethod()), + }, + } + case *v2beta_org.OrganizationSearchFilter_StateFilter: + toReturn.Query = &v2_org.SearchQuery_StateQuery{ + StateQuery: &v2_org.OrganizationStateQuery{ + State: organizationStateBetaToV2(assertedType.StateFilter.GetState()), + }, + } + default: + return toReturn + } + return toReturn +} + +func organizationStateBetaToV2(in v2beta_org.OrgState) v2_org.OrganizationState { + switch in { + case v2beta_org.OrgState_ORG_STATE_ACTIVE: + return v2_org.OrganizationState_ORGANIZATION_STATE_ACTIVE + case v2beta_org.OrgState_ORG_STATE_INACTIVE: + return v2_org.OrganizationState_ORGANIZATION_STATE_INACTIVE + case v2beta_org.OrgState_ORG_STATE_REMOVED: + return v2_org.OrganizationState_ORGANIZATION_STATE_REMOVED + case v2beta_org.OrgState_ORG_STATE_UNSPECIFIED: + fallthrough + default: + return v2_org.OrganizationState_ORGANIZATION_STATE_UNSPECIFIED + } +} + +/* + * Domain Model to GRPC v2 + */ + +func DomainOrganizationListModelToGRPCResponse(orgs []*domain.Organization) []*v2_org.Organization { + toReturn := make([]*v2_org.Organization, len(orgs)) + + for i, org := range orgs { + toReturn[i] = domainOrganizationModelToGRPCResponse(org) + } + + return toReturn +} + +func domainOrganizationModelToGRPCResponse(org *domain.Organization) *v2_org.Organization { + return &v2_org.Organization{ + Id: org.ID, + Details: &grpc_object.Details{ + ChangeDate: timestamppb.New(org.UpdatedAt), + CreationDate: timestamppb.New(org.CreatedAt), + }, + State: v2_org.OrganizationState(org.State), + Name: org.Name, + PrimaryDomain: org.PrimaryDomain(), + } +} + +/* + * Domain Model to GRPC v2 beta + */ + +// TODO(IAM-Marco): Remove in V5 (see https://github.com/zitadel/zitadel/issues/10877) +func DomainOrganizationListModelToGRPCBetaResponse(orgs []*domain.Organization) []*v2beta_org.Organization { + toReturn := make([]*v2beta_org.Organization, len(orgs)) + + for i, org := range orgs { + toReturn[i] = domainOrganizationModelToGRPCBetaResponse(org) + } + + return toReturn +} + +// TODO(IAM-Marco): Remove in V5 (see https://github.com/zitadel/zitadel/issues/10877) +func domainOrganizationModelToGRPCBetaResponse(org *domain.Organization) *v2beta_org.Organization { + return &v2beta_org.Organization{ + Id: org.ID, + ChangedDate: timestamppb.New(org.UpdatedAt), + CreationDate: timestamppb.New(org.CreatedAt), + State: v2beta_org.OrgState(org.State), + Name: org.Name, + PrimaryDomain: org.PrimaryDomain(), + } +} diff --git a/backend/v3/api/org/v2/convert/org_test.go b/backend/v3/api/org/v2/convert/org_test.go new file mode 100644 index 000000000..483c59054 --- /dev/null +++ b/backend/v3/api/org/v2/convert/org_test.go @@ -0,0 +1,89 @@ +package convert + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/zitadel/zitadel/backend/v3/domain" + "github.com/zitadel/zitadel/pkg/grpc/object/v2" + "github.com/zitadel/zitadel/pkg/grpc/org/v2" +) + +func TestDomainOrganizationListModelToGRPCResponse(t *testing.T) { + t.Parallel() + now := time.Now().UTC() + yesterday := now.AddDate(0, 0, -1) + + tt := []struct { + name string + orgs []*domain.Organization + want []*org.Organization + }{ + { + name: "empty result", + orgs: nil, + want: []*org.Organization{}, + }, + { + name: "multiple organizations", + orgs: []*domain.Organization{ + { + ID: "org-1", + Name: "org 1", + State: domain.OrgStateActive, + CreatedAt: yesterday, + UpdatedAt: now, + Domains: []*domain.OrganizationDomain{ + {Domain: "wrong selected domain"}, + {Domain: "domain.example.com", IsPrimary: true}, + }, + }, + { + ID: "org-2", + Name: "org 2", + State: domain.OrgStateInactive, + CreatedAt: yesterday, + UpdatedAt: now, + Domains: []*domain.OrganizationDomain{ + {Domain: "wrong selected domain 2"}, + {Domain: "domain2.example.com", IsPrimary: true}, + }, + }, + }, + want: []*org.Organization{ + { + Id: "org-1", + Name: "org 1", + State: org.OrganizationState_ORGANIZATION_STATE_ACTIVE, + Details: &object.Details{ + ChangeDate: timestamppb.New(now), + CreationDate: timestamppb.New(yesterday), + }, + PrimaryDomain: "domain.example.com", + }, + { + Id: "org-2", + Name: "org 2", + State: org.OrganizationState_ORGANIZATION_STATE_INACTIVE, + Details: &object.Details{ + ChangeDate: timestamppb.New(now), + CreationDate: timestamppb.New(yesterday), + }, + PrimaryDomain: "domain2.example.com", + }, + }, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + got := DomainOrganizationListModelToGRPCResponse(tc.orgs) + assert.Equal(t, tc.want, got) + }) + } +} diff --git a/backend/v3/api/org/v2/org.go b/backend/v3/api/org/v2/org.go index 601846848..7665e3297 100644 --- a/backend/v3/api/org/v2/org.go +++ b/backend/v3/api/org/v2/org.go @@ -1,5 +1,22 @@ package orgv2 +import ( + "context" + "errors" + + "connectrpc.com/connect" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/zitadel/zitadel/backend/v3/api/org/v2/convert" + "github.com/zitadel/zitadel/backend/v3/domain" + "github.com/zitadel/zitadel/backend/v3/storage/database" + "github.com/zitadel/zitadel/backend/v3/storage/database/repository" + filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta" + "github.com/zitadel/zitadel/pkg/grpc/object/v2" + v2_org "github.com/zitadel/zitadel/pkg/grpc/org/v2" + v2beta_org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta" +) + // import ( // "context" @@ -31,3 +48,137 @@ package orgv2 // } // return cmds // } + +func UpdateOrganization(ctx context.Context, request *connect.Request[v2beta_org.UpdateOrganizationRequest]) (*connect.Response[v2beta_org.UpdateOrganizationResponse], error) { + orgUpdateCmd := domain.NewUpdateOrgCommand(request.Msg.GetId(), request.Msg.GetName()) + + // TODO(IAM-Marco) Finish implementation in https://github.com/zitadel/zitadel/issues/10447 + domainAddCmd := domain.NewAddOrgDomainCommand(request.Msg.GetId(), request.Msg.GetName()) + domainSetPrimaryCmd := domain.NewSetPrimaryOrgDomainCommand(request.Msg.GetId(), request.Msg.GetName()) + domainRemoveCmd := domain.NewRemoveOrgDomainCommand(request.Msg.GetId(), orgUpdateCmd.OldDomainName, orgUpdateCmd.IsOldDomainVerified) + + batchCmd := domain.BatchExecutors(orgUpdateCmd, domainAddCmd, domainSetPrimaryCmd, domainRemoveCmd) + + err := domain.Invoke(ctx, batchCmd, domain.WithOrganizationRepo(repository.OrganizationRepository())) + if err != nil { + return nil, err + } + + return &connect.Response[v2beta_org.UpdateOrganizationResponse]{ + Msg: &v2beta_org.UpdateOrganizationResponse{ + // TODO(IAM-Marco): Change this with the real update date when OrganizationRepo.Update() + // returns the timestamp. See https://github.com/zitadel/zitadel/issues/10881 + ChangeDate: timestamppb.Now(), + }, + }, nil +} + +func ListOrganizations(ctx context.Context, request *connect.Request[v2_org.ListOrganizationsRequest]) (*connect.Response[v2_org.ListOrganizationsResponse], error) { + orgListQuery := domain.NewListOrgsQuery(request.Msg) + + err := domain.Invoke(ctx, orgListQuery, + domain.WithOrganizationRepo(repository.OrganizationRepository()), + domain.WithOrganizationDomainRepo(repository.OrganizationDomainRepository()), + ) + if err != nil { + return nil, err + } + + orgs := convert.DomainOrganizationListModelToGRPCResponse(orgListQuery.Result()) + return &connect.Response[v2_org.ListOrganizationsResponse]{ + Msg: &v2_org.ListOrganizationsResponse{ + Result: orgs, + Details: &object.ListDetails{ + // TODO(IAM-Marco): Return correct result once permissions are in place + TotalResult: uint64(len(orgs)), + }, + SortingColumn: request.Msg.GetSortingColumn(), + }, + }, nil +} + +// TODO(IAM-Marco): Remove in V5 (see https://github.com/zitadel/zitadel/issues/10877) +func ListOrganizationsBeta(ctx context.Context, request *connect.Request[v2beta_org.ListOrganizationsRequest]) (*connect.Response[v2beta_org.ListOrganizationsResponse], error) { + orgListQuery := domain.NewListOrgsQuery(convert.OrganizationBetaRequestToV2Request(request.Msg)) + + err := domain.Invoke(ctx, orgListQuery, + domain.WithOrganizationRepo(repository.OrganizationRepository()), + domain.WithOrganizationDomainRepo(repository.OrganizationDomainRepository()), + ) + if err != nil { + return nil, err + } + + orgs := convert.DomainOrganizationListModelToGRPCBetaResponse(orgListQuery.Result()) + return &connect.Response[v2beta_org.ListOrganizationsResponse]{ + Msg: &v2beta_org.ListOrganizationsResponse{ + Organizations: orgs, + Pagination: &filter.PaginationResponse{ + TotalResult: uint64(len(orgs)), + AppliedLimit: uint64(request.Msg.GetPagination().GetLimit()), + }, + }, + }, nil +} + +func DeleteOrganization(ctx context.Context, request *connect.Request[v2beta_org.DeleteOrganizationRequest]) (*connect.Response[v2beta_org.DeleteOrganizationResponse], error) { + orgDeleteCmd := domain.NewDeleteOrgCommand(request.Msg.GetId()) + + err := domain.Invoke(ctx, orgDeleteCmd, + domain.WithOrganizationRepo(repository.OrganizationRepository()), + domain.WithProjectRepo(repository.ProjectRepository()), + ) + if err != nil { + var notFoundError *database.NoRowFoundError + if errors.As(err, ¬FoundError) { + return connect.NewResponse(&v2beta_org.DeleteOrganizationResponse{}), nil + } + return nil, err + } + + return &connect.Response[v2beta_org.DeleteOrganizationResponse]{ + Msg: &v2beta_org.DeleteOrganizationResponse{ + // TODO(IAM-Marco): Change this with the real update date when OrganizationRepo.Delete() + // returns the timestamp. See https://github.com/zitadel/zitadel/issues/10881 + DeletionDate: timestamppb.Now(), + }, + }, nil +} + +func DeactivateOrganization(ctx context.Context, request *connect.Request[v2beta_org.DeactivateOrganizationRequest]) (*connect.Response[v2beta_org.DeactivateOrganizationResponse], error) { + orgDeactivateCmd := domain.NewDeactivateOrgCommand(request.Msg.GetId()) + + err := domain.Invoke(ctx, orgDeactivateCmd, + domain.WithOrganizationRepo(repository.OrganizationRepository()), + ) + if err != nil { + return nil, err + } + + return &connect.Response[v2beta_org.DeactivateOrganizationResponse]{ + Msg: &v2beta_org.DeactivateOrganizationResponse{ + // TODO(IAM-Marco): Change this with the real update date when OrganizationRepo.Update() + // returns the timestamp. See https://github.com/zitadel/zitadel/issues/10881 + ChangeDate: timestamppb.Now(), + }, + }, nil +} + +func ActivateOrganization(ctx context.Context, request *connect.Request[v2beta_org.ActivateOrganizationRequest]) (*connect.Response[v2beta_org.ActivateOrganizationResponse], error) { + orgActivateCmd := domain.NewActivateOrgCommand(request.Msg.GetId()) + + err := domain.Invoke(ctx, orgActivateCmd, + domain.WithOrganizationRepo(repository.OrganizationRepository()), + ) + if err != nil { + return nil, err + } + + return &connect.Response[v2beta_org.ActivateOrganizationResponse]{ + Msg: &v2beta_org.ActivateOrganizationResponse{ + // TODO(IAM-Marco): Change this with the real update date when OrganizationRepo.Update() + // returns the timestamp. See https://github.com/zitadel/zitadel/issues/10881 + ChangeDate: timestamppb.Now(), + }, + }, nil +} diff --git a/backend/v3/domain/command.go b/backend/v3/domain/command.go deleted file mode 100644 index 8dd301f53..000000000 --- a/backend/v3/domain/command.go +++ /dev/null @@ -1,148 +0,0 @@ -package domain - -import ( - "context" - "fmt" - - "github.com/zitadel/zitadel/backend/v3/storage/database" - legacy_es "github.com/zitadel/zitadel/internal/eventstore" -) - -// Commander is all that is needed to implement the command pattern. -// It is the interface all manipulations need to implement. -// If possible it should also be used for queries. We will find out if this is possible in the future. -type Commander interface { - Execute(ctx context.Context, opts *CommandOpts) (err error) - // Events returns the events that should be pushed to the event store after the command is executed. - // If the command does not produce events, it should return nil or an empty slice. - Events(ctx context.Context) []legacy_es.Command - fmt.Stringer -} - -// Invoker is part of the command pattern. -// It is the interface that is used to execute commands. -type Invoker interface { - Invoke(ctx context.Context, command Commander, opts *CommandOpts) error -} - -// CommandOpts are passed to each command -// it provides common fields used by commands like the database client. -type CommandOpts struct { - DB database.QueryExecutor - Invoker Invoker - Permissions PermissionChecker -} - -type ensureTxOpts struct { - *database.TransactionOptions -} - -type EnsureTransactionOpt func(*ensureTxOpts) - -// EnsureTx ensures that the DB is a transaction. If it is not, it will start a new transaction. -// The returned close function will end the transaction. If the DB is already a transaction, the close function -// will do nothing because another [Commander] is already responsible for ending the transaction. -func (o *CommandOpts) EnsureTx(ctx context.Context, opts ...EnsureTransactionOpt) (close func(context.Context, error) error, err error) { - beginner, ok := o.DB.(database.Beginner) - if !ok { - // db is already a transaction - return func(_ context.Context, err error) error { - return err - }, nil - } - - txOpts := &ensureTxOpts{ - TransactionOptions: new(database.TransactionOptions), - } - for _, opt := range opts { - opt(txOpts) - } - - tx, err := beginner.Begin(ctx, txOpts.TransactionOptions) - if err != nil { - return nil, err - } - o.DB = tx - - return func(ctx context.Context, err error) error { - return tx.End(ctx, err) - }, nil -} - -// EnsureClient ensures that the o.DB is a client. If it is not, it will get a new client from the [database.Pool]. -// The returned close function will release the client. If the o.DB is already a client or transaction, the close function -// will do nothing because another [Commander] is already responsible for releasing the client. -func (o *CommandOpts) EnsureClient(ctx context.Context) (close func(_ context.Context) error, err error) { - pool, ok := o.DB.(database.Pool) - if !ok { - // o.DB is already a client - return func(_ context.Context) error { - return nil - }, nil - } - client, err := pool.Acquire(ctx) - if err != nil { - return nil, err - } - o.DB = client - return func(ctx context.Context) error { - return client.Release(ctx) - }, nil -} - -func (o *CommandOpts) Invoke(ctx context.Context, command Commander) error { - if o.Invoker == nil { - return command.Execute(ctx, o) - } - return o.Invoker.Invoke(ctx, command, o) -} - -func DefaultOpts(invoker Invoker) *CommandOpts { - if invoker == nil { - invoker = &noopInvoker{} - } - return &CommandOpts{ - DB: pool, - Invoker: invoker, - Permissions: &noopPermissionChecker{}, // prevent panics for now - } -} - -// commandBatch is a batch of commands. -// It uses the [Invoker] provided by the opts to execute each command. -type commandBatch struct { - Commands []Commander -} - -// Events implements Commander. -func (cmd *commandBatch) Events(ctx context.Context) []legacy_es.Command { - commands := make([]legacy_es.Command, 0, len(cmd.Commands)) - for _, c := range cmd.Commands { - if e := c.Events(ctx); len(e) > 0 { - commands = append(commands, e...) - } - } - return commands -} - -func BatchCommands(cmds ...Commander) *commandBatch { - return &commandBatch{ - Commands: cmds, - } -} - -// String implements [Commander]. -func (cmd *commandBatch) String() string { - return "commandBatch" -} - -func (b *commandBatch) Execute(ctx context.Context, opts *CommandOpts) (err error) { - for _, cmd := range b.Commands { - if err = opts.Invoke(ctx, cmd); err != nil { - return err - } - } - return nil -} - -var _ Commander = (*commandBatch)(nil) diff --git a/backend/v3/domain/defaults.go b/backend/v3/domain/defaults.go index c2e5678af..407e31025 100644 --- a/backend/v3/domain/defaults.go +++ b/backend/v3/domain/defaults.go @@ -18,11 +18,6 @@ var ( logger logging.Logger = *logging.NewLogger(slog.Default()) legacyEventstore eventstore.LegacyEventstore - // userRepo func(database.QueryExecutor) UserRepository - // instanceRepo func(database.QueryExecutor) InstanceRepository - // cryptoRepo func(database.QueryExecutor) CryptoRepository - // orgRepo func(database.QueryExecutor) OrgRepository - // instanceCache cache.Cache[instanceCacheIndex, string, *Instance] // orgCache cache.Cache[orgCacheIndex, string, *Org] @@ -55,10 +50,6 @@ func SetLegacyEventstore(es eventstore.LegacyEventstore) { // userRepo = repo // } -// func SetOrgRepository(repo func(database.QueryExecutor) OrgRepository) { -// orgRepo = repo -// } - // func SetInstanceRepository(repo func(database.QueryExecutor) InstanceRepository) { // instanceRepo = repo // } diff --git a/backend/v3/domain/domain_test.go b/backend/v3/domain/domain_test.go index 503d322a5..19769aae1 100644 --- a/backend/v3/domain/domain_test.go +++ b/backend/v3/domain/domain_test.go @@ -1,67 +1,12 @@ package domain_test -// import ( -// "context" -// "log/slog" -// "testing" +import ( + "os" + "testing" +) -// "github.com/stretchr/testify/assert" -// "github.com/stretchr/testify/require" -// "go.opentelemetry.io/otel" -// "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" -// sdktrace "go.opentelemetry.io/otel/sdk/trace" -// "go.uber.org/mock/gomock" - -// . "github.com/zitadel/zitadel/backend/v3/domain" -// "github.com/zitadel/zitadel/backend/v3/storage/database/dbmock" -// "github.com/zitadel/zitadel/backend/v3/storage/database/repository" -// "github.com/zitadel/zitadel/backend/v3/telemetry/logging" -// "github.com/zitadel/zitadel/backend/v3/telemetry/tracing" -// ) - -// These tests give an overview of how to use the domain package. -// func TestExample(t *testing.T) { -// t.Skip("skip example test because it is not a real test") -// ctx := context.Background() - -// ctrl := gomock.NewController(t) -// pool := dbmock.NewMockPool(ctrl) -// tx := dbmock.NewMockTransaction(ctrl) - -// pool.EXPECT().Begin(gomock.Any(), gomock.Any()).Return(tx, nil) -// tx.EXPECT().End(gomock.Any(), gomock.Any()).Return(nil) -// SetPool(pool) - -// exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint()) -// require.NoError(t, err) -// tracerProvider := sdktrace.NewTracerProvider( -// sdktrace.WithSyncer(exporter), -// ) -// otel.SetTracerProvider(tracerProvider) -// SetTracer(tracing.Tracer{Tracer: tracerProvider.Tracer("test")}) -// defer func() { assert.NoError(t, tracerProvider.Shutdown(ctx)) }() - -// SetLogger(logging.Logger{Logger: slog.Default()}) - -// SetUserRepository(repository.UserRepository) -// SetOrgRepository(repository.OrgRepository) -// // SetInstanceRepository(repository.Instance) -// // SetCryptoRepository(repository.Crypto) - -// t.Run("create org", func(t *testing.T) { -// org := NewAddOrgCommand("testorg", NewAddMemberCommand("testuser", "ORG_OWNER")) -// user := NewCreateHumanCommand("testuser") -// err := Invoke(ctx, BatchCommands(org, user)) -// assert.NoError(t, err) -// }) - -// t.Run("verified email", func(t *testing.T) { -// err := Invoke(ctx, NewSetEmailCommand("u1", "test@example.com", NewEmailVerifiedCommand("u1", true))) -// assert.NoError(t, err) -// }) - -// t.Run("unverified email", func(t *testing.T) { -// err := Invoke(ctx, NewSetEmailCommand("u2", "test2@example.com", NewEmailVerifiedCommand("u2", false))) -// assert.NoError(t, err) -// }) -// } +func TestMain(m *testing.M) { + os.Exit(func() int { + return m.Run() + }()) +} diff --git a/backend/v3/domain/errors.go b/backend/v3/domain/errors.go index cb1a192c8..77ed61312 100644 --- a/backend/v3/domain/errors.go +++ b/backend/v3/domain/errors.go @@ -27,3 +27,48 @@ func (e *wrongIDPTypeError) Is(target error) bool { _, ok := target.(*wrongIDPTypeError) return ok } + +type MultipleObjectsUpdatedError struct { + Msg string + Expected int64 + Actual int64 +} + +func NewMultipleObjectsUpdatedError(expected, actual int64) error { + return &MultipleObjectsUpdatedError{ + Expected: expected, + Actual: actual, + } +} + +func (err *MultipleObjectsUpdatedError) Error() string { + return fmt.Sprintf("Message=expecting %d row(s) updated, got %d", err.Expected, err.Actual) +} + +type UnexpectedQueryTypeError[T any] struct { + assertedType T +} + +func NewUnexpectedQueryTypeError[T any](assertedType T) error { + return &UnexpectedQueryTypeError[T]{ + assertedType: assertedType, + } +} + +func (u *UnexpectedQueryTypeError[T]) Error() string { + return fmt.Sprintf("Message=unexpected query type '%T'", u.assertedType) +} + +type UnexpectedTextQueryOperationError[T any] struct { + assertedType T +} + +func NewUnexpectedTextQueryOperationError[T any](assertedType T) error { + return &UnexpectedTextQueryOperationError[T]{ + assertedType: assertedType, + } +} + +func (u *UnexpectedTextQueryOperationError[T]) Error() string { + return fmt.Sprintf("Message=unexpected text query operation type '%T'", u.assertedType) +} diff --git a/backend/v3/domain/errors_test.go b/backend/v3/domain/errors_test.go new file mode 100644 index 000000000..e0bbe617e --- /dev/null +++ b/backend/v3/domain/errors_test.go @@ -0,0 +1,38 @@ +package domain + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUnexpectedQueryTypeError_Error(t *testing.T) { + tests := []struct { + name string + typeVal any + expected string + }{ + { + name: "string type", + typeVal: "test", + expected: "Message=unexpected query type 'string'", + }, + { + name: "int type", + typeVal: 42, + expected: "Message=unexpected query type 'int'", + }, + { + name: "struct type", + typeVal: struct{}{}, + expected: "Message=unexpected query type 'struct {}'", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := NewUnexpectedQueryTypeError(tt.typeVal) + assert.Equal(t, tt.expected, err.Error()) + }) + } +} diff --git a/backend/v3/domain/executor_batch.go b/backend/v3/domain/executor_batch.go new file mode 100644 index 000000000..101c8a1dd --- /dev/null +++ b/backend/v3/domain/executor_batch.go @@ -0,0 +1,41 @@ +package domain + +import ( + "context" + "fmt" + "strings" +) + +// batchExecutor is a batch of [Executor]s. +// It uses the [Invoker]s provided by the opts to execute each [Executor]. +// The [Executor] is sent to all [Invoker]s before moving to the next [Executor]. +type batchExecutor struct { + executors []Executor +} + +func BatchExecutors(executors ...Executor) *batchExecutor { + return &batchExecutor{ + executors: executors, + } +} + +// String implements [Executor]. +func (cmd *batchExecutor) String() string { + names := make([]string, len(cmd.executors)) + for i, c := range cmd.executors { + names[i] = c.String() + } + return fmt.Sprintf("commandBatch[%s]", strings.Join(names, ", ")) +} + +// Execute implements [Executor]. +func (b *batchExecutor) Execute(ctx context.Context, opts *InvokeOpts) (err error) { + for _, cmd := range b.executors { + if err = opts.Invoke(ctx, cmd); err != nil { + return err + } + } + return nil +} + +var _ Executor = (*batchExecutor)(nil) diff --git a/backend/v3/domain/executor_batch_test.go b/backend/v3/domain/executor_batch_test.go new file mode 100644 index 000000000..57e76cc41 --- /dev/null +++ b/backend/v3/domain/executor_batch_test.go @@ -0,0 +1,45 @@ +package domain_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + + "github.com/zitadel/zitadel/backend/v3/domain" + domainmock "github.com/zitadel/zitadel/backend/v3/domain/mock" +) + +func TestBatchExecutors(t *testing.T) { + // Given + mockCtrl := gomock.NewController(t) + mockCmd1 := domainmock.NewMockCommander(mockCtrl) + mockCmd2 := domainmock.NewMockCommander(mockCtrl) + mockCmd3 := domainmock.NewMockCommander(mockCtrl) + + mockCmd1.EXPECT().String().Return("cmd1").AnyTimes() + mockCmd2.EXPECT().String().Return("cmd2").AnyTimes() + mockCmd3.EXPECT().String().Return("cmd3").AnyTimes() + + gomock.InOrder( + mockCmd1.EXPECT().Validate(gomock.Any(), gomock.Any()).Times(1), + mockCmd1.EXPECT().Execute(gomock.Any(), gomock.Any()).Times(1), + mockCmd1.EXPECT().Events(gomock.Any(), gomock.Any()).Times(1), + mockCmd2.EXPECT().Validate(gomock.Any(), gomock.Any()).Times(1), + mockCmd2.EXPECT().Execute(gomock.Any(), gomock.Any()).Times(1), + mockCmd2.EXPECT().Events(gomock.Any(), gomock.Any()).Times(1), + mockCmd3.EXPECT().Validate(gomock.Any(), gomock.Any()).Times(1), + mockCmd3.EXPECT().Execute(gomock.Any(), gomock.Any()).Times(1), + mockCmd3.EXPECT().Events(gomock.Any(), gomock.Any()).Times(1), + ) + + batcher := domain.BatchExecutors(mockCmd1, mockCmd2, mockCmd3) + + // Test + err := domain.Invoke(context.Background(), batcher) + + // Verify + require.NoError(t, err) + require.True(t, mockCtrl.Satisfied()) +} diff --git a/backend/v3/domain/invoke.go b/backend/v3/domain/invoke.go index 4567ec30e..4163f23bd 100644 --- a/backend/v3/domain/invoke.go +++ b/backend/v3/domain/invoke.go @@ -3,157 +3,59 @@ package domain import ( "context" "fmt" - - "github.com/zitadel/zitadel/backend/v3/storage/eventstore" - legacy_es "github.com/zitadel/zitadel/internal/eventstore" ) +//go:generate mockgen -typed -package domainmock -destination ./mock/executor.mock.go . Executor +type Executor interface { + Execute(ctx context.Context, opts *InvokeOpts) (err error) + fmt.Stringer +} + +// Commander is all that is needed to implement the command pattern. +// It is the interface all manipulations need to implement. +// If possible it should also be used for queries. We will find out if this is possible in the future. +// +//go:generate mockgen -typed -package domainmock -destination ./mock/commander.mock.go . Commander +type Commander interface { + Validator + EventProducer + Executor +} + +// Querier used to query data. +// +//go:generate mockgen -typed -package domainmock -destination ./mock/querier.mock.go . Querier +type Querier[T any] interface { + Validator + Executor + // Result returns the result of the query. + // If `Execute` returns an error, the result is nil. + Result() T +} + // Invoke provides a way to execute commands within the domain package. // It uses a chain of responsibility pattern to handle the command execution. // The default chain includes logging, tracing, and event publishing. -// If you want to invoke multiple commands in a single transaction, you can use the [commandBatch]. -func Invoke(ctx context.Context, cmd Commander) error { - invoker := newEventStoreInvoker(newLoggingInvoker(newTraceInvoker(nil))) - opts := &CommandOpts{ - Invoker: invoker.collector, - DB: pool, +// If you want to invoke multiple commands in a single transaction, you can use the [batchExecutor]. +func Invoke(ctx context.Context, executor Executor, opts ...InvokeOpt) error { + invokeOpts := &InvokeOpts{ + Invoker: newEventStoreInvoker( + newLoggingInvoker( + newTraceInvoker( + newValidatorInvoker(nil), + ), + ), + ), + DB: pool, } - return invoker.Invoke(ctx, cmd, opts) -} - -// eventStoreInvoker checks if the [Commander].Events function returns any events. -// If it does, it collects the events and publishes them to the event store. -type eventStoreInvoker struct { - collector *eventCollector -} - -func newEventStoreInvoker(next Invoker) *eventStoreInvoker { - return &eventStoreInvoker{collector: &eventCollector{next: next}} -} - -func (i *eventStoreInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) { - err = i.collector.Invoke(ctx, command, opts) - if err != nil { - return err + for _, opt := range opts { + opt(invokeOpts) } - if len(i.collector.events) > 0 { - err = eventstore.Publish(ctx, legacyEventstore, opts.DB, i.collector.events...) - if err != nil { - return err - } - } - return nil + return invokeOpts.Invoke(ctx, executor) } -// eventCollector collects events from all commands. The [eventStoreInvoker] pushes the collected events after all commands are executed. -// The events are collected after the command got executed, the collector ensures that the command is executed in the same transaction as writing the events. -type eventCollector struct { - next Invoker - events []legacy_es.Command +// Invoker is part of the command pattern. +// It is the interface that is used to execute commands. +type Invoker interface { + Invoke(ctx context.Context, command Executor, opts *InvokeOpts) error } - -func (i *eventCollector) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) { - close, err := opts.EnsureTx(ctx) - if err != nil { - return err - } - defer func() { err = close(ctx, err) }() - - if i.next != nil { - err = i.next.Invoke(ctx, command, opts) - } else { - err = command.Execute(ctx, opts) - } - if err != nil { - return err - } - i.events = append(command.Events(ctx), i.events...) - - return -} - -// traceInvoker decorates each command with tracing. -type traceInvoker struct { - next Invoker -} - -func newTraceInvoker(next Invoker) *traceInvoker { - return &traceInvoker{next: next} -} - -func (i *traceInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) { - ctx, span := tracer.Start(ctx, fmt.Sprintf("%T", command)) - defer func() { - if err != nil { - span.RecordError(err) - } - span.End() - }() - - if i.next != nil { - return i.next.Invoke(ctx, command, opts) - } - return command.Execute(ctx, opts) -} - -// loggingInvoker decorates each command with logging. -// It is an example implementation and logs the command name at the beginning and success or failure after the command got executed. -type loggingInvoker struct { - next Invoker -} - -func newLoggingInvoker(next Invoker) *loggingInvoker { - return &loggingInvoker{next: next} -} - -func (i *loggingInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) { - logger.InfoContext(ctx, "Invoking command", "command", command.String()) - - if i.next != nil { - err = i.next.Invoke(ctx, command, opts) - } else { - err = command.Execute(ctx, opts) - } - - if err != nil { - logger.ErrorContext(ctx, "Command invocation failed", "command", command.String(), "error", err) - return err - } - logger.InfoContext(ctx, "Command invocation succeeded", "command", command.String()) - return nil -} - -type noopInvoker struct { - next Invoker -} - -func (i *noopInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) error { - if i.next != nil { - return i.next.Invoke(ctx, command, opts) - } - return command.Execute(ctx, opts) -} - -// cacheInvoker could be used in the future to do the caching. -// My goal would be to have two interfaces: -// - cacheSetter: which caches an object -// - cacheGetter: which gets an object from the cache, this should also skip the command execution -// type cacheInvoker struct { -// next Invoker -// } - -// type cacher interface { -// Cache(opts *CommandOpts) -// } - -// func (i *cacheInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) { -// if c, ok := command.(cacher); ok { -// c.Cache(opts) -// } -// if i.next != nil { -// err = i.next.Invoke(ctx, command, opts) -// } else { -// err = command.Execute(ctx, opts) -// } -// return err -// } diff --git a/backend/v3/domain/invoke_option.go b/backend/v3/domain/invoke_option.go new file mode 100644 index 000000000..6bc66844c --- /dev/null +++ b/backend/v3/domain/invoke_option.go @@ -0,0 +1,113 @@ +package domain + +import ( + "context" + + "github.com/zitadel/zitadel/backend/v3/storage/database" +) + +type InvokeOpt func(*InvokeOpts) + +func WithOrganizationRepo(repo OrganizationRepository) InvokeOpt { + return func(opts *InvokeOpts) { + opts.organizationRepo = repo + } +} + +func WithOrganizationDomainRepo(repo OrganizationDomainRepository) InvokeOpt { + return func(opts *InvokeOpts) { + opts.organizationDomainRepo = repo + } +} + +func WithProjectRepo(repo ProjectRepository) InvokeOpt { + return func(opts *InvokeOpts) { + opts.projectRepo = repo + } +} + +// InvokeOpts are passed to each command +// they provide common fields used by commands like the database client. +type InvokeOpts struct { + DB database.QueryExecutor + Invoker Invoker + Permissions PermissionChecker + organizationRepo OrganizationRepository + organizationDomainRepo OrganizationDomainRepository + projectRepo ProjectRepository +} + +type ensureTxOpts struct { + *database.TransactionOptions +} + +type EnsureTransactionOpt func(*ensureTxOpts) + +// EnsureTx ensures that the DB is a transaction. If it is not, it will start a new transaction. +// The returned close function will end the transaction. If the DB is already a transaction, the close function +// will be a noop because another [Executor] is already responsible for ending the transaction. +func (o *InvokeOpts) EnsureTx(ctx context.Context, opts ...EnsureTransactionOpt) (close func(context.Context, error) error, err error) { + beginner, ok := o.DB.(database.Beginner) + if !ok { + // db is already a transaction + return func(_ context.Context, err error) error { + return err + }, nil + } + + txOpts := &ensureTxOpts{ + TransactionOptions: new(database.TransactionOptions), + } + for _, opt := range opts { + opt(txOpts) + } + + tx, err := beginner.Begin(ctx, txOpts.TransactionOptions) + if err != nil { + return nil, err + } + o.DB = tx + + return func(ctx context.Context, err error) error { + return tx.End(ctx, err) + }, nil +} + +// EnsureClient ensures that the o.DB is a client. If it is not, it will get a new client from the [database.Pool]. +// The returned close function will release the client. If the o.DB is already a client or transaction, the close function +// will do nothing because another [Executor] is already responsible for releasing the client. +func (o *InvokeOpts) EnsureClient(ctx context.Context) (close func(_ context.Context) error, err error) { + pool, ok := o.DB.(database.Pool) + if !ok { + // o.DB is already a client + return func(_ context.Context) error { + return nil + }, nil + } + client, err := pool.Acquire(ctx) + if err != nil { + return nil, err + } + o.DB = client + return func(ctx context.Context) error { + return client.Release(ctx) + }, nil +} + +func (o *InvokeOpts) Invoke(ctx context.Context, executor Executor) error { + if o.Invoker == nil { + return executor.Execute(ctx, o) + } + return o.Invoker.Invoke(ctx, executor, o) +} + +func DefaultOpts(invoker Invoker) *InvokeOpts { + if invoker == nil { + invoker = &noopInvoker{} + } + return &InvokeOpts{ + DB: pool, + Invoker: invoker, + Permissions: &noopPermissionChecker{}, // prevent panics for now + } +} diff --git a/backend/v3/domain/invoke_test.go b/backend/v3/domain/invoke_test.go index b392e0478..b46585383 100644 --- a/backend/v3/domain/invoke_test.go +++ b/backend/v3/domain/invoke_test.go @@ -24,27 +24,32 @@ var _ eventstore.LegacyEventstore = (*testLegacyEventstore)(nil) type invokeTestCommand struct { events []legacy_es.Command - execute func(ctx context.Context, opts *CommandOpts) error + execute func(ctx context.Context, opts *InvokeOpts) error } -// Events implements Commander. -func (i *invokeTestCommand) Events(ctx context.Context) []legacy_es.Command { - return i.events +// Events implements [Commander]. +func (i *invokeTestCommand) Events(ctx context.Context, opts *InvokeOpts) ([]legacy_es.Command, error) { + return i.events, nil } -// Execute implements Commander. -func (i *invokeTestCommand) Execute(ctx context.Context, opts *CommandOpts) (err error) { +// Execute implements [Commander]. +func (i *invokeTestCommand) Execute(ctx context.Context, opts *InvokeOpts) (err error) { if i.execute == nil { return nil } return i.execute(ctx, opts) } -// String implements Commander. +// String implements [Commander]. func (i *invokeTestCommand) String() string { return "invokeTestCommand" } +// Validate implements [Commander]. +func (i *invokeTestCommand) Validate(ctx context.Context, opts *InvokeOpts) (err error) { + return nil +} + var _ Commander = (*invokeTestCommand)(nil) type invokeTestEvent struct { @@ -125,7 +130,7 @@ func Test_eventCollector_Invoke(t *testing.T) { expectedErr: nil, command: &invokeTestCommand{ events: []legacy_es.Command{&invokeTestEvent{id: "1"}}, - execute: func(ctx context.Context, opts *CommandOpts) error { + execute: func(ctx context.Context, opts *InvokeOpts) error { return opts.Invoke(ctx, &invokeTestCommand{ events: []legacy_es.Command{&invokeTestEvent{id: "2"}}, }) @@ -133,15 +138,15 @@ func Test_eventCollector_Invoke(t *testing.T) { }, assertCollectedEvents: func(t *testing.T, events []legacy_es.Command) { require.Len(t, events, 2) - assert.Equal(t, "1", events[0].Aggregate().ID) - assert.Equal(t, "2", events[1].Aggregate().ID) + assert.Equal(t, "1", events[1].Aggregate().ID) + assert.Equal(t, "2", events[0].Aggregate().ID) }, }, { name: "only sub commands with events", expectedErr: nil, command: &invokeTestCommand{ - execute: func(ctx context.Context, opts *CommandOpts) error { + execute: func(ctx context.Context, opts *InvokeOpts) error { return opts.Invoke(ctx, &invokeTestCommand{ events: []legacy_es.Command{&invokeTestEvent{id: "2"}}, }) @@ -152,6 +157,27 @@ func Test_eventCollector_Invoke(t *testing.T) { assert.Equal(t, "2", events[0].Aggregate().ID) }, }, + { + name: "executor batch all commands", + expectedErr: nil, + command: &invokeTestCommand{ + execute: func(ctx context.Context, opts *InvokeOpts) error { + return opts.Invoke(ctx, BatchExecutors( + &invokeTestCommand{ + events: []legacy_es.Command{&invokeTestEvent{id: "2"}}, + }, + &invokeTestCommand{ + events: []legacy_es.Command{&invokeTestEvent{id: "3"}}, + }, + )) + }, + }, + assertCollectedEvents: func(t *testing.T, events []legacy_es.Command) { + require.Len(t, events, 2) + assert.Equal(t, "2", events[0].Aggregate().ID) + assert.Equal(t, "3", events[1].Aggregate().ID) + }, + }, } SetLegacyEventstore(new(testLegacyEventstore)) @@ -160,15 +186,13 @@ func Test_eventCollector_Invoke(t *testing.T) { t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) - pool := dbmock.NewMockPool(ctrl) - tx := dbmock.NewMockTransaction(ctrl) tx.EXPECT().End(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() pool.EXPECT().Begin(gomock.Any(), gomock.Any()).Return(tx, nil).AnyTimes() i := newEventStoreInvoker(nil) - opts := CommandOpts{ + opts := InvokeOpts{ Invoker: i, DB: pool, } diff --git a/backend/v3/domain/invoker_event.go b/backend/v3/domain/invoker_event.go new file mode 100644 index 000000000..17c0d0b4c --- /dev/null +++ b/backend/v3/domain/invoker_event.go @@ -0,0 +1,106 @@ +package domain + +import ( + "context" + + "github.com/zitadel/zitadel/backend/v3/storage/eventstore" + legacy_es "github.com/zitadel/zitadel/internal/eventstore" +) + +// eventStoreInvoker checks if the [EventProducer].Events function returns any events. +// If it does, it collects the events and publishes them to the event store. +type eventStoreInvoker struct { + next Invoker + collector *eventCollector +} + +func newEventStoreInvoker(next Invoker) *eventStoreInvoker { + return &eventStoreInvoker{next: next} +} + +type EventProducer interface { + // Events returns the events that should be pushed to the event store after the command is executed. + // If the command does not produce events, it should `return nil, nil`. + Events(ctx context.Context, opts *InvokeOpts) ([]legacy_es.Command, error) +} + +func (i *eventStoreInvoker) Invoke(ctx context.Context, executor Executor, opts *InvokeOpts) (err error) { + if _, ok := executor.(EventProducer); !ok { + return i.execute(ctx, executor, opts) + } + + if i.collector != nil { + return i.collector.Invoke(ctx, executor, opts) + } + + close, err := opts.EnsureTx(ctx) + if err != nil { + return err + } + defer func() { err = close(ctx, err) }() + + i.collector = &eventCollector{next: i.next} + + if err = i.collector.Invoke(ctx, executor, opts); err != nil { + return err + } + + if len(i.collector.events) > 0 { + err = eventstore.Publish(ctx, legacyEventstore, opts.DB, i.collector.events...) + if err != nil { + return err + } + } + return nil +} + +func (i *eventStoreInvoker) execute(ctx context.Context, executor Executor, opts *InvokeOpts) error { + if i.next != nil { + return i.next.Invoke(ctx, executor, opts) + } + return executor.Execute(ctx, opts) +} + +// eventCollector collects events from all commands. The [eventStoreInvoker] pushes the collected events after all commands are executed. +// The events are collected after the command got executed, the collector ensures that the command is executed in the same transaction as writing the events. +type eventCollector struct { + next Invoker + events []legacy_es.Command + shouldPrepend bool +} + +func (i *eventCollector) Invoke(ctx context.Context, executor Executor, opts *InvokeOpts) (err error) { + command, ok := executor.(EventProducer) + if !ok { + if i.next != nil { + return i.next.Invoke(ctx, executor, opts) + } + return executor.Execute(ctx, opts) + } + + shouldPrepend := i.shouldPrepend + i.shouldPrepend = false + + if i.next != nil { + i.shouldPrepend = true + err = i.next.Invoke(ctx, executor, opts) + i.shouldPrepend = false + } else { + err = executor.Execute(ctx, opts) + } + if err != nil { + return err + } + collectedEvents, err := command.Events(ctx, opts) + if err != nil { + return err + } + + if shouldPrepend { + i.events = append(collectedEvents, i.events...) + } else { + i.events = append(i.events, collectedEvents...) + } + + return err +} diff --git a/backend/v3/domain/invoker_logging.go b/backend/v3/domain/invoker_logging.go new file mode 100644 index 000000000..8ce337ac5 --- /dev/null +++ b/backend/v3/domain/invoker_logging.go @@ -0,0 +1,34 @@ +package domain + +import ( + "context" + "time" +) + +// loggingInvoker decorates each command with logging. +// It is an example implementation and logs the command name at the beginning and success or failure after the command got executed. +type loggingInvoker struct { + next Invoker +} + +func newLoggingInvoker(next Invoker) *loggingInvoker { + return &loggingInvoker{next: next} +} + +func (i *loggingInvoker) Invoke(ctx context.Context, executor Executor, opts *InvokeOpts) (err error) { + start := time.Now() + logger.InfoContext(ctx, "invoking", "name", executor.String()) + + if i.next != nil { + err = i.next.Invoke(ctx, executor, opts) + } else { + err = executor.Execute(ctx, opts) + } + + if err != nil { + logger.ErrorContext(ctx, "invocation failed", "name", executor.String(), "error", err, "took", time.Since(start)) + return err + } + logger.InfoContext(ctx, "invocation succeeded", "name", executor.String(), "took", time.Since(start)) + return nil +} diff --git a/backend/v3/domain/invoker_noop.go b/backend/v3/domain/invoker_noop.go new file mode 100644 index 000000000..92935a515 --- /dev/null +++ b/backend/v3/domain/invoker_noop.go @@ -0,0 +1,14 @@ +package domain + +import "context" + +type noopInvoker struct { + next Invoker +} + +func (i *noopInvoker) Invoke(ctx context.Context, command Executor, opts *InvokeOpts) error { + if i.next != nil { + return i.next.Invoke(ctx, command, opts) + } + return command.Execute(ctx, opts) +} diff --git a/backend/v3/domain/invoker_tracing.go b/backend/v3/domain/invoker_tracing.go new file mode 100644 index 000000000..4cfced0ba --- /dev/null +++ b/backend/v3/domain/invoker_tracing.go @@ -0,0 +1,30 @@ +package domain + +import ( + "context" + "fmt" +) + +// traceInvoker decorates each command with tracing. +type traceInvoker struct { + next Invoker +} + +func newTraceInvoker(next Invoker) *traceInvoker { + return &traceInvoker{next: next} +} + +func (i *traceInvoker) Invoke(ctx context.Context, executor Executor, opts *InvokeOpts) (err error) { + ctx, span := tracer.Start(ctx, fmt.Sprintf("%T", executor)) + defer func() { + if err != nil { + span.RecordError(err) + } + span.End() + }() + + if i.next != nil { + return i.next.Invoke(ctx, executor, opts) + } + return executor.Execute(ctx, opts) +} diff --git a/backend/v3/domain/invoker_validation.go b/backend/v3/domain/invoker_validation.go new file mode 100644 index 000000000..6b601517a --- /dev/null +++ b/backend/v3/domain/invoker_validation.go @@ -0,0 +1,31 @@ +package domain + +import ( + "context" +) + +type validatorInvoker struct { + next Invoker +} + +func newValidatorInvoker(next Invoker) *validatorInvoker { + return &validatorInvoker{next: next} +} + +type Validator interface { + Validate(ctx context.Context, opts *InvokeOpts) (err error) +} + +func (i *validatorInvoker) Invoke(ctx context.Context, executor Executor, opts *InvokeOpts) error { + if validator, ok := executor.(Validator); ok { + if err := validator.Validate(ctx, opts); err != nil { + return err + } + } + + if i.next != nil { + return i.next.Invoke(ctx, executor, opts) + } + + return executor.Execute(ctx, opts) +} diff --git a/backend/v3/domain/mock/commander.mock.go b/backend/v3/domain/mock/commander.mock.go new file mode 100644 index 000000000..dc5d2b360 --- /dev/null +++ b/backend/v3/domain/mock/commander.mock.go @@ -0,0 +1,196 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/zitadel/zitadel/backend/v3/domain (interfaces: Commander) +// +// Generated by this command: +// +// mockgen -typed -package domainmock -destination ./mock/commander.mock.go . Commander +// + +// Package domainmock is a generated GoMock package. +package domainmock + +import ( + context "context" + reflect "reflect" + + domain "github.com/zitadel/zitadel/backend/v3/domain" + eventstore "github.com/zitadel/zitadel/internal/eventstore" + gomock "go.uber.org/mock/gomock" +) + +// MockCommander is a mock of Commander interface. +type MockCommander struct { + ctrl *gomock.Controller + recorder *MockCommanderMockRecorder + isgomock struct{} +} + +// MockCommanderMockRecorder is the mock recorder for MockCommander. +type MockCommanderMockRecorder struct { + mock *MockCommander +} + +// NewMockCommander creates a new mock instance. +func NewMockCommander(ctrl *gomock.Controller) *MockCommander { + mock := &MockCommander{ctrl: ctrl} + mock.recorder = &MockCommanderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCommander) EXPECT() *MockCommanderMockRecorder { + return m.recorder +} + +// Events mocks base method. +func (m *MockCommander) Events(ctx context.Context, opts *domain.InvokeOpts) ([]eventstore.Command, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Events", ctx, opts) + ret0, _ := ret[0].([]eventstore.Command) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Events indicates an expected call of Events. +func (mr *MockCommanderMockRecorder) Events(ctx, opts any) *MockCommanderEventsCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Events", reflect.TypeOf((*MockCommander)(nil).Events), ctx, opts) + return &MockCommanderEventsCall{Call: call} +} + +// MockCommanderEventsCall wrap *gomock.Call +type MockCommanderEventsCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockCommanderEventsCall) Return(arg0 []eventstore.Command, arg1 error) *MockCommanderEventsCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockCommanderEventsCall) Do(f func(context.Context, *domain.InvokeOpts) ([]eventstore.Command, error)) *MockCommanderEventsCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockCommanderEventsCall) DoAndReturn(f func(context.Context, *domain.InvokeOpts) ([]eventstore.Command, error)) *MockCommanderEventsCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Execute mocks base method. +func (m *MockCommander) Execute(ctx context.Context, opts *domain.InvokeOpts) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Execute", ctx, opts) + ret0, _ := ret[0].(error) + return ret0 +} + +// Execute indicates an expected call of Execute. +func (mr *MockCommanderMockRecorder) Execute(ctx, opts any) *MockCommanderExecuteCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockCommander)(nil).Execute), ctx, opts) + return &MockCommanderExecuteCall{Call: call} +} + +// MockCommanderExecuteCall wrap *gomock.Call +type MockCommanderExecuteCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockCommanderExecuteCall) Return(err error) *MockCommanderExecuteCall { + c.Call = c.Call.Return(err) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockCommanderExecuteCall) Do(f func(context.Context, *domain.InvokeOpts) error) *MockCommanderExecuteCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockCommanderExecuteCall) DoAndReturn(f func(context.Context, *domain.InvokeOpts) error) *MockCommanderExecuteCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// String mocks base method. +func (m *MockCommander) String() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "String") + ret0, _ := ret[0].(string) + return ret0 +} + +// String indicates an expected call of String. +func (mr *MockCommanderMockRecorder) String() *MockCommanderStringCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "String", reflect.TypeOf((*MockCommander)(nil).String)) + return &MockCommanderStringCall{Call: call} +} + +// MockCommanderStringCall wrap *gomock.Call +type MockCommanderStringCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockCommanderStringCall) Return(arg0 string) *MockCommanderStringCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockCommanderStringCall) Do(f func() string) *MockCommanderStringCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockCommanderStringCall) DoAndReturn(f func() string) *MockCommanderStringCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Validate mocks base method. +func (m *MockCommander) Validate(ctx context.Context, opts *domain.InvokeOpts) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Validate", ctx, opts) + ret0, _ := ret[0].(error) + return ret0 +} + +// Validate indicates an expected call of Validate. +func (mr *MockCommanderMockRecorder) Validate(ctx, opts any) *MockCommanderValidateCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockCommander)(nil).Validate), ctx, opts) + return &MockCommanderValidateCall{Call: call} +} + +// MockCommanderValidateCall wrap *gomock.Call +type MockCommanderValidateCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockCommanderValidateCall) Return(err error) *MockCommanderValidateCall { + c.Call = c.Call.Return(err) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockCommanderValidateCall) Do(f func(context.Context, *domain.InvokeOpts) error) *MockCommanderValidateCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockCommanderValidateCall) DoAndReturn(f func(context.Context, *domain.InvokeOpts) error) *MockCommanderValidateCall { + c.Call = c.Call.DoAndReturn(f) + return c +} diff --git a/backend/v3/domain/mock/org.mock.go b/backend/v3/domain/mock/org.mock.go new file mode 100644 index 000000000..363c04eed --- /dev/null +++ b/backend/v3/domain/mock/org.mock.go @@ -0,0 +1,860 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/zitadel/zitadel/backend/v3/domain (interfaces: OrganizationRepository) +// +// Generated by this command: +// +// mockgen -typed -package domainmock -destination ./mock/org.mock.go . OrganizationRepository +// + +// Package domainmock is a generated GoMock package. +package domainmock + +import ( + context "context" + reflect "reflect" + + domain "github.com/zitadel/zitadel/backend/v3/domain" + database "github.com/zitadel/zitadel/backend/v3/storage/database" + gomock "go.uber.org/mock/gomock" +) + +// MockOrganizationRepository is a mock of OrganizationRepository interface. +type MockOrganizationRepository struct { + ctrl *gomock.Controller + recorder *MockOrganizationRepositoryMockRecorder + isgomock struct{} +} + +// MockOrganizationRepositoryMockRecorder is the mock recorder for MockOrganizationRepository. +type MockOrganizationRepositoryMockRecorder struct { + mock *MockOrganizationRepository +} + +// NewMockOrganizationRepository creates a new mock instance. +func NewMockOrganizationRepository(ctrl *gomock.Controller) *MockOrganizationRepository { + mock := &MockOrganizationRepository{ctrl: ctrl} + mock.recorder = &MockOrganizationRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockOrganizationRepository) EXPECT() *MockOrganizationRepositoryMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockOrganizationRepository) Create(ctx context.Context, client database.QueryExecutor, org *domain.Organization) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", ctx, client, org) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockOrganizationRepositoryMockRecorder) Create(ctx, client, org any) *MockOrganizationRepositoryCreateCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockOrganizationRepository)(nil).Create), ctx, client, org) + return &MockOrganizationRepositoryCreateCall{Call: call} +} + +// MockOrganizationRepositoryCreateCall wrap *gomock.Call +type MockOrganizationRepositoryCreateCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationRepositoryCreateCall) Return(arg0 error) *MockOrganizationRepositoryCreateCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationRepositoryCreateCall) Do(f func(context.Context, database.QueryExecutor, *domain.Organization) error) *MockOrganizationRepositoryCreateCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationRepositoryCreateCall) DoAndReturn(f func(context.Context, database.QueryExecutor, *domain.Organization) error) *MockOrganizationRepositoryCreateCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// CreatedAtColumn mocks base method. +func (m *MockOrganizationRepository) CreatedAtColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreatedAtColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// CreatedAtColumn indicates an expected call of CreatedAtColumn. +func (mr *MockOrganizationRepositoryMockRecorder) CreatedAtColumn() *MockOrganizationRepositoryCreatedAtColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatedAtColumn", reflect.TypeOf((*MockOrganizationRepository)(nil).CreatedAtColumn)) + return &MockOrganizationRepositoryCreatedAtColumnCall{Call: call} +} + +// MockOrganizationRepositoryCreatedAtColumnCall wrap *gomock.Call +type MockOrganizationRepositoryCreatedAtColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationRepositoryCreatedAtColumnCall) Return(arg0 database.Column) *MockOrganizationRepositoryCreatedAtColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationRepositoryCreatedAtColumnCall) Do(f func() database.Column) *MockOrganizationRepositoryCreatedAtColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationRepositoryCreatedAtColumnCall) DoAndReturn(f func() database.Column) *MockOrganizationRepositoryCreatedAtColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Delete mocks base method. +func (m *MockOrganizationRepository) Delete(ctx context.Context, client database.QueryExecutor, condition database.Condition) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", ctx, client, condition) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Delete indicates an expected call of Delete. +func (mr *MockOrganizationRepositoryMockRecorder) Delete(ctx, client, condition any) *MockOrganizationRepositoryDeleteCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockOrganizationRepository)(nil).Delete), ctx, client, condition) + return &MockOrganizationRepositoryDeleteCall{Call: call} +} + +// MockOrganizationRepositoryDeleteCall wrap *gomock.Call +type MockOrganizationRepositoryDeleteCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationRepositoryDeleteCall) Return(arg0 int64, arg1 error) *MockOrganizationRepositoryDeleteCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationRepositoryDeleteCall) Do(f func(context.Context, database.QueryExecutor, database.Condition) (int64, error)) *MockOrganizationRepositoryDeleteCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationRepositoryDeleteCall) DoAndReturn(f func(context.Context, database.QueryExecutor, database.Condition) (int64, error)) *MockOrganizationRepositoryDeleteCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// ExistsDomain mocks base method. +func (m *MockOrganizationRepository) ExistsDomain(cond database.Condition) database.Condition { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExistsDomain", cond) + ret0, _ := ret[0].(database.Condition) + return ret0 +} + +// ExistsDomain indicates an expected call of ExistsDomain. +func (mr *MockOrganizationRepositoryMockRecorder) ExistsDomain(cond any) *MockOrganizationRepositoryExistsDomainCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExistsDomain", reflect.TypeOf((*MockOrganizationRepository)(nil).ExistsDomain), cond) + return &MockOrganizationRepositoryExistsDomainCall{Call: call} +} + +// MockOrganizationRepositoryExistsDomainCall wrap *gomock.Call +type MockOrganizationRepositoryExistsDomainCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationRepositoryExistsDomainCall) Return(arg0 database.Condition) *MockOrganizationRepositoryExistsDomainCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationRepositoryExistsDomainCall) Do(f func(database.Condition) database.Condition) *MockOrganizationRepositoryExistsDomainCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationRepositoryExistsDomainCall) DoAndReturn(f func(database.Condition) database.Condition) *MockOrganizationRepositoryExistsDomainCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// ExistsMetadata mocks base method. +func (m *MockOrganizationRepository) ExistsMetadata(cond database.Condition) database.Condition { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExistsMetadata", cond) + ret0, _ := ret[0].(database.Condition) + return ret0 +} + +// ExistsMetadata indicates an expected call of ExistsMetadata. +func (mr *MockOrganizationRepositoryMockRecorder) ExistsMetadata(cond any) *MockOrganizationRepositoryExistsMetadataCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExistsMetadata", reflect.TypeOf((*MockOrganizationRepository)(nil).ExistsMetadata), cond) + return &MockOrganizationRepositoryExistsMetadataCall{Call: call} +} + +// MockOrganizationRepositoryExistsMetadataCall wrap *gomock.Call +type MockOrganizationRepositoryExistsMetadataCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationRepositoryExistsMetadataCall) Return(arg0 database.Condition) *MockOrganizationRepositoryExistsMetadataCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationRepositoryExistsMetadataCall) Do(f func(database.Condition) database.Condition) *MockOrganizationRepositoryExistsMetadataCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationRepositoryExistsMetadataCall) DoAndReturn(f func(database.Condition) database.Condition) *MockOrganizationRepositoryExistsMetadataCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Get mocks base method. +func (m *MockOrganizationRepository) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.Organization, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, client} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Get", varargs...) + ret0, _ := ret[0].(*domain.Organization) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockOrganizationRepositoryMockRecorder) Get(ctx, client any, opts ...any) *MockOrganizationRepositoryGetCall { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, client}, opts...) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockOrganizationRepository)(nil).Get), varargs...) + return &MockOrganizationRepositoryGetCall{Call: call} +} + +// MockOrganizationRepositoryGetCall wrap *gomock.Call +type MockOrganizationRepositoryGetCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationRepositoryGetCall) Return(arg0 *domain.Organization, arg1 error) *MockOrganizationRepositoryGetCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationRepositoryGetCall) Do(f func(context.Context, database.QueryExecutor, ...database.QueryOption) (*domain.Organization, error)) *MockOrganizationRepositoryGetCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationRepositoryGetCall) DoAndReturn(f func(context.Context, database.QueryExecutor, ...database.QueryOption) (*domain.Organization, error)) *MockOrganizationRepositoryGetCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// IDColumn mocks base method. +func (m *MockOrganizationRepository) IDColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IDColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// IDColumn indicates an expected call of IDColumn. +func (mr *MockOrganizationRepositoryMockRecorder) IDColumn() *MockOrganizationRepositoryIDColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IDColumn", reflect.TypeOf((*MockOrganizationRepository)(nil).IDColumn)) + return &MockOrganizationRepositoryIDColumnCall{Call: call} +} + +// MockOrganizationRepositoryIDColumnCall wrap *gomock.Call +type MockOrganizationRepositoryIDColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationRepositoryIDColumnCall) Return(arg0 database.Column) *MockOrganizationRepositoryIDColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationRepositoryIDColumnCall) Do(f func() database.Column) *MockOrganizationRepositoryIDColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationRepositoryIDColumnCall) DoAndReturn(f func() database.Column) *MockOrganizationRepositoryIDColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// IDCondition mocks base method. +func (m *MockOrganizationRepository) IDCondition(instanceID string) database.Condition { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IDCondition", instanceID) + ret0, _ := ret[0].(database.Condition) + return ret0 +} + +// IDCondition indicates an expected call of IDCondition. +func (mr *MockOrganizationRepositoryMockRecorder) IDCondition(instanceID any) *MockOrganizationRepositoryIDConditionCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IDCondition", reflect.TypeOf((*MockOrganizationRepository)(nil).IDCondition), instanceID) + return &MockOrganizationRepositoryIDConditionCall{Call: call} +} + +// MockOrganizationRepositoryIDConditionCall wrap *gomock.Call +type MockOrganizationRepositoryIDConditionCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationRepositoryIDConditionCall) Return(arg0 database.Condition) *MockOrganizationRepositoryIDConditionCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationRepositoryIDConditionCall) Do(f func(string) database.Condition) *MockOrganizationRepositoryIDConditionCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationRepositoryIDConditionCall) DoAndReturn(f func(string) database.Condition) *MockOrganizationRepositoryIDConditionCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// InstanceIDColumn mocks base method. +func (m *MockOrganizationRepository) InstanceIDColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InstanceIDColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// InstanceIDColumn indicates an expected call of InstanceIDColumn. +func (mr *MockOrganizationRepositoryMockRecorder) InstanceIDColumn() *MockOrganizationRepositoryInstanceIDColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceIDColumn", reflect.TypeOf((*MockOrganizationRepository)(nil).InstanceIDColumn)) + return &MockOrganizationRepositoryInstanceIDColumnCall{Call: call} +} + +// MockOrganizationRepositoryInstanceIDColumnCall wrap *gomock.Call +type MockOrganizationRepositoryInstanceIDColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationRepositoryInstanceIDColumnCall) Return(arg0 database.Column) *MockOrganizationRepositoryInstanceIDColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationRepositoryInstanceIDColumnCall) Do(f func() database.Column) *MockOrganizationRepositoryInstanceIDColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationRepositoryInstanceIDColumnCall) DoAndReturn(f func() database.Column) *MockOrganizationRepositoryInstanceIDColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// InstanceIDCondition mocks base method. +func (m *MockOrganizationRepository) InstanceIDCondition(instanceID string) database.Condition { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InstanceIDCondition", instanceID) + ret0, _ := ret[0].(database.Condition) + return ret0 +} + +// InstanceIDCondition indicates an expected call of InstanceIDCondition. +func (mr *MockOrganizationRepositoryMockRecorder) InstanceIDCondition(instanceID any) *MockOrganizationRepositoryInstanceIDConditionCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceIDCondition", reflect.TypeOf((*MockOrganizationRepository)(nil).InstanceIDCondition), instanceID) + return &MockOrganizationRepositoryInstanceIDConditionCall{Call: call} +} + +// MockOrganizationRepositoryInstanceIDConditionCall wrap *gomock.Call +type MockOrganizationRepositoryInstanceIDConditionCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationRepositoryInstanceIDConditionCall) Return(arg0 database.Condition) *MockOrganizationRepositoryInstanceIDConditionCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationRepositoryInstanceIDConditionCall) Do(f func(string) database.Condition) *MockOrganizationRepositoryInstanceIDConditionCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationRepositoryInstanceIDConditionCall) DoAndReturn(f func(string) database.Condition) *MockOrganizationRepositoryInstanceIDConditionCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// List mocks base method. +func (m *MockOrganizationRepository) List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*domain.Organization, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, client} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "List", varargs...) + ret0, _ := ret[0].([]*domain.Organization) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockOrganizationRepositoryMockRecorder) List(ctx, client any, opts ...any) *MockOrganizationRepositoryListCall { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, client}, opts...) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockOrganizationRepository)(nil).List), varargs...) + return &MockOrganizationRepositoryListCall{Call: call} +} + +// MockOrganizationRepositoryListCall wrap *gomock.Call +type MockOrganizationRepositoryListCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationRepositoryListCall) Return(arg0 []*domain.Organization, arg1 error) *MockOrganizationRepositoryListCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationRepositoryListCall) Do(f func(context.Context, database.QueryExecutor, ...database.QueryOption) ([]*domain.Organization, error)) *MockOrganizationRepositoryListCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationRepositoryListCall) DoAndReturn(f func(context.Context, database.QueryExecutor, ...database.QueryOption) ([]*domain.Organization, error)) *MockOrganizationRepositoryListCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// LoadDomains mocks base method. +func (m *MockOrganizationRepository) LoadDomains() domain.OrganizationRepository { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LoadDomains") + ret0, _ := ret[0].(domain.OrganizationRepository) + return ret0 +} + +// LoadDomains indicates an expected call of LoadDomains. +func (mr *MockOrganizationRepositoryMockRecorder) LoadDomains() *MockOrganizationRepositoryLoadDomainsCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadDomains", reflect.TypeOf((*MockOrganizationRepository)(nil).LoadDomains)) + return &MockOrganizationRepositoryLoadDomainsCall{Call: call} +} + +// MockOrganizationRepositoryLoadDomainsCall wrap *gomock.Call +type MockOrganizationRepositoryLoadDomainsCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationRepositoryLoadDomainsCall) Return(arg0 domain.OrganizationRepository) *MockOrganizationRepositoryLoadDomainsCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationRepositoryLoadDomainsCall) Do(f func() domain.OrganizationRepository) *MockOrganizationRepositoryLoadDomainsCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationRepositoryLoadDomainsCall) DoAndReturn(f func() domain.OrganizationRepository) *MockOrganizationRepositoryLoadDomainsCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// LoadMetadata mocks base method. +func (m *MockOrganizationRepository) LoadMetadata() domain.OrganizationRepository { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LoadMetadata") + ret0, _ := ret[0].(domain.OrganizationRepository) + return ret0 +} + +// LoadMetadata indicates an expected call of LoadMetadata. +func (mr *MockOrganizationRepositoryMockRecorder) LoadMetadata() *MockOrganizationRepositoryLoadMetadataCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadMetadata", reflect.TypeOf((*MockOrganizationRepository)(nil).LoadMetadata)) + return &MockOrganizationRepositoryLoadMetadataCall{Call: call} +} + +// MockOrganizationRepositoryLoadMetadataCall wrap *gomock.Call +type MockOrganizationRepositoryLoadMetadataCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationRepositoryLoadMetadataCall) Return(arg0 domain.OrganizationRepository) *MockOrganizationRepositoryLoadMetadataCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationRepositoryLoadMetadataCall) Do(f func() domain.OrganizationRepository) *MockOrganizationRepositoryLoadMetadataCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationRepositoryLoadMetadataCall) DoAndReturn(f func() domain.OrganizationRepository) *MockOrganizationRepositoryLoadMetadataCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// NameColumn mocks base method. +func (m *MockOrganizationRepository) NameColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NameColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// NameColumn indicates an expected call of NameColumn. +func (mr *MockOrganizationRepositoryMockRecorder) NameColumn() *MockOrganizationRepositoryNameColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NameColumn", reflect.TypeOf((*MockOrganizationRepository)(nil).NameColumn)) + return &MockOrganizationRepositoryNameColumnCall{Call: call} +} + +// MockOrganizationRepositoryNameColumnCall wrap *gomock.Call +type MockOrganizationRepositoryNameColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationRepositoryNameColumnCall) Return(arg0 database.Column) *MockOrganizationRepositoryNameColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationRepositoryNameColumnCall) Do(f func() database.Column) *MockOrganizationRepositoryNameColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationRepositoryNameColumnCall) DoAndReturn(f func() database.Column) *MockOrganizationRepositoryNameColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// NameCondition mocks base method. +func (m *MockOrganizationRepository) NameCondition(op database.TextOperation, name string) database.Condition { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NameCondition", op, name) + ret0, _ := ret[0].(database.Condition) + return ret0 +} + +// NameCondition indicates an expected call of NameCondition. +func (mr *MockOrganizationRepositoryMockRecorder) NameCondition(op, name any) *MockOrganizationRepositoryNameConditionCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NameCondition", reflect.TypeOf((*MockOrganizationRepository)(nil).NameCondition), op, name) + return &MockOrganizationRepositoryNameConditionCall{Call: call} +} + +// MockOrganizationRepositoryNameConditionCall wrap *gomock.Call +type MockOrganizationRepositoryNameConditionCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationRepositoryNameConditionCall) Return(arg0 database.Condition) *MockOrganizationRepositoryNameConditionCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationRepositoryNameConditionCall) Do(f func(database.TextOperation, string) database.Condition) *MockOrganizationRepositoryNameConditionCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationRepositoryNameConditionCall) DoAndReturn(f func(database.TextOperation, string) database.Condition) *MockOrganizationRepositoryNameConditionCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// SetName mocks base method. +func (m *MockOrganizationRepository) SetName(name string) database.Change { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetName", name) + ret0, _ := ret[0].(database.Change) + return ret0 +} + +// SetName indicates an expected call of SetName. +func (mr *MockOrganizationRepositoryMockRecorder) SetName(name any) *MockOrganizationRepositorySetNameCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetName", reflect.TypeOf((*MockOrganizationRepository)(nil).SetName), name) + return &MockOrganizationRepositorySetNameCall{Call: call} +} + +// MockOrganizationRepositorySetNameCall wrap *gomock.Call +type MockOrganizationRepositorySetNameCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationRepositorySetNameCall) Return(arg0 database.Change) *MockOrganizationRepositorySetNameCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationRepositorySetNameCall) Do(f func(string) database.Change) *MockOrganizationRepositorySetNameCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationRepositorySetNameCall) DoAndReturn(f func(string) database.Change) *MockOrganizationRepositorySetNameCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// SetState mocks base method. +func (m *MockOrganizationRepository) SetState(state domain.OrgState) database.Change { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetState", state) + ret0, _ := ret[0].(database.Change) + return ret0 +} + +// SetState indicates an expected call of SetState. +func (mr *MockOrganizationRepositoryMockRecorder) SetState(state any) *MockOrganizationRepositorySetStateCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetState", reflect.TypeOf((*MockOrganizationRepository)(nil).SetState), state) + return &MockOrganizationRepositorySetStateCall{Call: call} +} + +// MockOrganizationRepositorySetStateCall wrap *gomock.Call +type MockOrganizationRepositorySetStateCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationRepositorySetStateCall) Return(arg0 database.Change) *MockOrganizationRepositorySetStateCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationRepositorySetStateCall) Do(f func(domain.OrgState) database.Change) *MockOrganizationRepositorySetStateCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationRepositorySetStateCall) DoAndReturn(f func(domain.OrgState) database.Change) *MockOrganizationRepositorySetStateCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// StateColumn mocks base method. +func (m *MockOrganizationRepository) StateColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// StateColumn indicates an expected call of StateColumn. +func (mr *MockOrganizationRepositoryMockRecorder) StateColumn() *MockOrganizationRepositoryStateColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateColumn", reflect.TypeOf((*MockOrganizationRepository)(nil).StateColumn)) + return &MockOrganizationRepositoryStateColumnCall{Call: call} +} + +// MockOrganizationRepositoryStateColumnCall wrap *gomock.Call +type MockOrganizationRepositoryStateColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationRepositoryStateColumnCall) Return(arg0 database.Column) *MockOrganizationRepositoryStateColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationRepositoryStateColumnCall) Do(f func() database.Column) *MockOrganizationRepositoryStateColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationRepositoryStateColumnCall) DoAndReturn(f func() database.Column) *MockOrganizationRepositoryStateColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// StateCondition mocks base method. +func (m *MockOrganizationRepository) StateCondition(state domain.OrgState) database.Condition { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateCondition", state) + ret0, _ := ret[0].(database.Condition) + return ret0 +} + +// StateCondition indicates an expected call of StateCondition. +func (mr *MockOrganizationRepositoryMockRecorder) StateCondition(state any) *MockOrganizationRepositoryStateConditionCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateCondition", reflect.TypeOf((*MockOrganizationRepository)(nil).StateCondition), state) + return &MockOrganizationRepositoryStateConditionCall{Call: call} +} + +// MockOrganizationRepositoryStateConditionCall wrap *gomock.Call +type MockOrganizationRepositoryStateConditionCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationRepositoryStateConditionCall) Return(arg0 database.Condition) *MockOrganizationRepositoryStateConditionCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationRepositoryStateConditionCall) Do(f func(domain.OrgState) database.Condition) *MockOrganizationRepositoryStateConditionCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationRepositoryStateConditionCall) DoAndReturn(f func(domain.OrgState) database.Condition) *MockOrganizationRepositoryStateConditionCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Update mocks base method. +func (m *MockOrganizationRepository) Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, changes ...database.Change) (int64, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, client, condition} + for _, a := range changes { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Update", varargs...) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Update indicates an expected call of Update. +func (mr *MockOrganizationRepositoryMockRecorder) Update(ctx, client, condition any, changes ...any) *MockOrganizationRepositoryUpdateCall { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, client, condition}, changes...) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockOrganizationRepository)(nil).Update), varargs...) + return &MockOrganizationRepositoryUpdateCall{Call: call} +} + +// MockOrganizationRepositoryUpdateCall wrap *gomock.Call +type MockOrganizationRepositoryUpdateCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationRepositoryUpdateCall) Return(arg0 int64, arg1 error) *MockOrganizationRepositoryUpdateCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationRepositoryUpdateCall) Do(f func(context.Context, database.QueryExecutor, database.Condition, ...database.Change) (int64, error)) *MockOrganizationRepositoryUpdateCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationRepositoryUpdateCall) DoAndReturn(f func(context.Context, database.QueryExecutor, database.Condition, ...database.Change) (int64, error)) *MockOrganizationRepositoryUpdateCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// UpdatedAtColumn mocks base method. +func (m *MockOrganizationRepository) UpdatedAtColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdatedAtColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// UpdatedAtColumn indicates an expected call of UpdatedAtColumn. +func (mr *MockOrganizationRepositoryMockRecorder) UpdatedAtColumn() *MockOrganizationRepositoryUpdatedAtColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatedAtColumn", reflect.TypeOf((*MockOrganizationRepository)(nil).UpdatedAtColumn)) + return &MockOrganizationRepositoryUpdatedAtColumnCall{Call: call} +} + +// MockOrganizationRepositoryUpdatedAtColumnCall wrap *gomock.Call +type MockOrganizationRepositoryUpdatedAtColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationRepositoryUpdatedAtColumnCall) Return(arg0 database.Column) *MockOrganizationRepositoryUpdatedAtColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationRepositoryUpdatedAtColumnCall) Do(f func() database.Column) *MockOrganizationRepositoryUpdatedAtColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationRepositoryUpdatedAtColumnCall) DoAndReturn(f func() database.Column) *MockOrganizationRepositoryUpdatedAtColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} diff --git a/backend/v3/domain/mock/org_domain.mock.go b/backend/v3/domain/mock/org_domain.mock.go new file mode 100644 index 000000000..68e73b038 --- /dev/null +++ b/backend/v3/domain/mock/org_domain.mock.go @@ -0,0 +1,899 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/zitadel/zitadel/backend/v3/domain (interfaces: OrganizationDomainRepository) +// +// Generated by this command: +// +// mockgen -typed -package domainmock -destination ./mock/org_domain.mock.go . OrganizationDomainRepository +// + +// Package domainmock is a generated GoMock package. +package domainmock + +import ( + context "context" + reflect "reflect" + time "time" + + domain "github.com/zitadel/zitadel/backend/v3/domain" + database "github.com/zitadel/zitadel/backend/v3/storage/database" + gomock "go.uber.org/mock/gomock" +) + +// MockOrganizationDomainRepository is a mock of OrganizationDomainRepository interface. +type MockOrganizationDomainRepository struct { + ctrl *gomock.Controller + recorder *MockOrganizationDomainRepositoryMockRecorder + isgomock struct{} +} + +// MockOrganizationDomainRepositoryMockRecorder is the mock recorder for MockOrganizationDomainRepository. +type MockOrganizationDomainRepositoryMockRecorder struct { + mock *MockOrganizationDomainRepository +} + +// NewMockOrganizationDomainRepository creates a new mock instance. +func NewMockOrganizationDomainRepository(ctrl *gomock.Controller) *MockOrganizationDomainRepository { + mock := &MockOrganizationDomainRepository{ctrl: ctrl} + mock.recorder = &MockOrganizationDomainRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockOrganizationDomainRepository) EXPECT() *MockOrganizationDomainRepositoryMockRecorder { + return m.recorder +} + +// Add mocks base method. +func (m *MockOrganizationDomainRepository) Add(ctx context.Context, client database.QueryExecutor, arg2 *domain.AddOrganizationDomain) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Add", ctx, client, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// Add indicates an expected call of Add. +func (mr *MockOrganizationDomainRepositoryMockRecorder) Add(ctx, client, arg2 any) *MockOrganizationDomainRepositoryAddCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).Add), ctx, client, arg2) + return &MockOrganizationDomainRepositoryAddCall{Call: call} +} + +// MockOrganizationDomainRepositoryAddCall wrap *gomock.Call +type MockOrganizationDomainRepositoryAddCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositoryAddCall) Return(arg0 error) *MockOrganizationDomainRepositoryAddCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositoryAddCall) Do(f func(context.Context, database.QueryExecutor, *domain.AddOrganizationDomain) error) *MockOrganizationDomainRepositoryAddCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositoryAddCall) DoAndReturn(f func(context.Context, database.QueryExecutor, *domain.AddOrganizationDomain) error) *MockOrganizationDomainRepositoryAddCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// CreatedAtColumn mocks base method. +func (m *MockOrganizationDomainRepository) CreatedAtColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreatedAtColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// CreatedAtColumn indicates an expected call of CreatedAtColumn. +func (mr *MockOrganizationDomainRepositoryMockRecorder) CreatedAtColumn() *MockOrganizationDomainRepositoryCreatedAtColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatedAtColumn", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).CreatedAtColumn)) + return &MockOrganizationDomainRepositoryCreatedAtColumnCall{Call: call} +} + +// MockOrganizationDomainRepositoryCreatedAtColumnCall wrap *gomock.Call +type MockOrganizationDomainRepositoryCreatedAtColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositoryCreatedAtColumnCall) Return(arg0 database.Column) *MockOrganizationDomainRepositoryCreatedAtColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositoryCreatedAtColumnCall) Do(f func() database.Column) *MockOrganizationDomainRepositoryCreatedAtColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositoryCreatedAtColumnCall) DoAndReturn(f func() database.Column) *MockOrganizationDomainRepositoryCreatedAtColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// DomainColumn mocks base method. +func (m *MockOrganizationDomainRepository) DomainColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DomainColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// DomainColumn indicates an expected call of DomainColumn. +func (mr *MockOrganizationDomainRepositoryMockRecorder) DomainColumn() *MockOrganizationDomainRepositoryDomainColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DomainColumn", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).DomainColumn)) + return &MockOrganizationDomainRepositoryDomainColumnCall{Call: call} +} + +// MockOrganizationDomainRepositoryDomainColumnCall wrap *gomock.Call +type MockOrganizationDomainRepositoryDomainColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositoryDomainColumnCall) Return(arg0 database.Column) *MockOrganizationDomainRepositoryDomainColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositoryDomainColumnCall) Do(f func() database.Column) *MockOrganizationDomainRepositoryDomainColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositoryDomainColumnCall) DoAndReturn(f func() database.Column) *MockOrganizationDomainRepositoryDomainColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// DomainCondition mocks base method. +func (m *MockOrganizationDomainRepository) DomainCondition(op database.TextOperation, arg1 string) database.Condition { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DomainCondition", op, arg1) + ret0, _ := ret[0].(database.Condition) + return ret0 +} + +// DomainCondition indicates an expected call of DomainCondition. +func (mr *MockOrganizationDomainRepositoryMockRecorder) DomainCondition(op, arg1 any) *MockOrganizationDomainRepositoryDomainConditionCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DomainCondition", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).DomainCondition), op, arg1) + return &MockOrganizationDomainRepositoryDomainConditionCall{Call: call} +} + +// MockOrganizationDomainRepositoryDomainConditionCall wrap *gomock.Call +type MockOrganizationDomainRepositoryDomainConditionCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositoryDomainConditionCall) Return(arg0 database.Condition) *MockOrganizationDomainRepositoryDomainConditionCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositoryDomainConditionCall) Do(f func(database.TextOperation, string) database.Condition) *MockOrganizationDomainRepositoryDomainConditionCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositoryDomainConditionCall) DoAndReturn(f func(database.TextOperation, string) database.Condition) *MockOrganizationDomainRepositoryDomainConditionCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Get mocks base method. +func (m *MockOrganizationDomainRepository) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.OrganizationDomain, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, client} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Get", varargs...) + ret0, _ := ret[0].(*domain.OrganizationDomain) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockOrganizationDomainRepositoryMockRecorder) Get(ctx, client any, opts ...any) *MockOrganizationDomainRepositoryGetCall { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, client}, opts...) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).Get), varargs...) + return &MockOrganizationDomainRepositoryGetCall{Call: call} +} + +// MockOrganizationDomainRepositoryGetCall wrap *gomock.Call +type MockOrganizationDomainRepositoryGetCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositoryGetCall) Return(arg0 *domain.OrganizationDomain, arg1 error) *MockOrganizationDomainRepositoryGetCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositoryGetCall) Do(f func(context.Context, database.QueryExecutor, ...database.QueryOption) (*domain.OrganizationDomain, error)) *MockOrganizationDomainRepositoryGetCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositoryGetCall) DoAndReturn(f func(context.Context, database.QueryExecutor, ...database.QueryOption) (*domain.OrganizationDomain, error)) *MockOrganizationDomainRepositoryGetCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// InstanceIDColumn mocks base method. +func (m *MockOrganizationDomainRepository) InstanceIDColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InstanceIDColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// InstanceIDColumn indicates an expected call of InstanceIDColumn. +func (mr *MockOrganizationDomainRepositoryMockRecorder) InstanceIDColumn() *MockOrganizationDomainRepositoryInstanceIDColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceIDColumn", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).InstanceIDColumn)) + return &MockOrganizationDomainRepositoryInstanceIDColumnCall{Call: call} +} + +// MockOrganizationDomainRepositoryInstanceIDColumnCall wrap *gomock.Call +type MockOrganizationDomainRepositoryInstanceIDColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositoryInstanceIDColumnCall) Return(arg0 database.Column) *MockOrganizationDomainRepositoryInstanceIDColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositoryInstanceIDColumnCall) Do(f func() database.Column) *MockOrganizationDomainRepositoryInstanceIDColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositoryInstanceIDColumnCall) DoAndReturn(f func() database.Column) *MockOrganizationDomainRepositoryInstanceIDColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// InstanceIDCondition mocks base method. +func (m *MockOrganizationDomainRepository) InstanceIDCondition(instanceID string) database.Condition { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InstanceIDCondition", instanceID) + ret0, _ := ret[0].(database.Condition) + return ret0 +} + +// InstanceIDCondition indicates an expected call of InstanceIDCondition. +func (mr *MockOrganizationDomainRepositoryMockRecorder) InstanceIDCondition(instanceID any) *MockOrganizationDomainRepositoryInstanceIDConditionCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceIDCondition", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).InstanceIDCondition), instanceID) + return &MockOrganizationDomainRepositoryInstanceIDConditionCall{Call: call} +} + +// MockOrganizationDomainRepositoryInstanceIDConditionCall wrap *gomock.Call +type MockOrganizationDomainRepositoryInstanceIDConditionCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositoryInstanceIDConditionCall) Return(arg0 database.Condition) *MockOrganizationDomainRepositoryInstanceIDConditionCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositoryInstanceIDConditionCall) Do(f func(string) database.Condition) *MockOrganizationDomainRepositoryInstanceIDConditionCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositoryInstanceIDConditionCall) DoAndReturn(f func(string) database.Condition) *MockOrganizationDomainRepositoryInstanceIDConditionCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// IsPrimaryColumn mocks base method. +func (m *MockOrganizationDomainRepository) IsPrimaryColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsPrimaryColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// IsPrimaryColumn indicates an expected call of IsPrimaryColumn. +func (mr *MockOrganizationDomainRepositoryMockRecorder) IsPrimaryColumn() *MockOrganizationDomainRepositoryIsPrimaryColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsPrimaryColumn", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).IsPrimaryColumn)) + return &MockOrganizationDomainRepositoryIsPrimaryColumnCall{Call: call} +} + +// MockOrganizationDomainRepositoryIsPrimaryColumnCall wrap *gomock.Call +type MockOrganizationDomainRepositoryIsPrimaryColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositoryIsPrimaryColumnCall) Return(arg0 database.Column) *MockOrganizationDomainRepositoryIsPrimaryColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositoryIsPrimaryColumnCall) Do(f func() database.Column) *MockOrganizationDomainRepositoryIsPrimaryColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositoryIsPrimaryColumnCall) DoAndReturn(f func() database.Column) *MockOrganizationDomainRepositoryIsPrimaryColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// IsPrimaryCondition mocks base method. +func (m *MockOrganizationDomainRepository) IsPrimaryCondition(isPrimary bool) database.Condition { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsPrimaryCondition", isPrimary) + ret0, _ := ret[0].(database.Condition) + return ret0 +} + +// IsPrimaryCondition indicates an expected call of IsPrimaryCondition. +func (mr *MockOrganizationDomainRepositoryMockRecorder) IsPrimaryCondition(isPrimary any) *MockOrganizationDomainRepositoryIsPrimaryConditionCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsPrimaryCondition", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).IsPrimaryCondition), isPrimary) + return &MockOrganizationDomainRepositoryIsPrimaryConditionCall{Call: call} +} + +// MockOrganizationDomainRepositoryIsPrimaryConditionCall wrap *gomock.Call +type MockOrganizationDomainRepositoryIsPrimaryConditionCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositoryIsPrimaryConditionCall) Return(arg0 database.Condition) *MockOrganizationDomainRepositoryIsPrimaryConditionCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositoryIsPrimaryConditionCall) Do(f func(bool) database.Condition) *MockOrganizationDomainRepositoryIsPrimaryConditionCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositoryIsPrimaryConditionCall) DoAndReturn(f func(bool) database.Condition) *MockOrganizationDomainRepositoryIsPrimaryConditionCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// IsVerifiedColumn mocks base method. +func (m *MockOrganizationDomainRepository) IsVerifiedColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsVerifiedColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// IsVerifiedColumn indicates an expected call of IsVerifiedColumn. +func (mr *MockOrganizationDomainRepositoryMockRecorder) IsVerifiedColumn() *MockOrganizationDomainRepositoryIsVerifiedColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsVerifiedColumn", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).IsVerifiedColumn)) + return &MockOrganizationDomainRepositoryIsVerifiedColumnCall{Call: call} +} + +// MockOrganizationDomainRepositoryIsVerifiedColumnCall wrap *gomock.Call +type MockOrganizationDomainRepositoryIsVerifiedColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositoryIsVerifiedColumnCall) Return(arg0 database.Column) *MockOrganizationDomainRepositoryIsVerifiedColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositoryIsVerifiedColumnCall) Do(f func() database.Column) *MockOrganizationDomainRepositoryIsVerifiedColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositoryIsVerifiedColumnCall) DoAndReturn(f func() database.Column) *MockOrganizationDomainRepositoryIsVerifiedColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// IsVerifiedCondition mocks base method. +func (m *MockOrganizationDomainRepository) IsVerifiedCondition(isVerified bool) database.Condition { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsVerifiedCondition", isVerified) + ret0, _ := ret[0].(database.Condition) + return ret0 +} + +// IsVerifiedCondition indicates an expected call of IsVerifiedCondition. +func (mr *MockOrganizationDomainRepositoryMockRecorder) IsVerifiedCondition(isVerified any) *MockOrganizationDomainRepositoryIsVerifiedConditionCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsVerifiedCondition", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).IsVerifiedCondition), isVerified) + return &MockOrganizationDomainRepositoryIsVerifiedConditionCall{Call: call} +} + +// MockOrganizationDomainRepositoryIsVerifiedConditionCall wrap *gomock.Call +type MockOrganizationDomainRepositoryIsVerifiedConditionCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositoryIsVerifiedConditionCall) Return(arg0 database.Condition) *MockOrganizationDomainRepositoryIsVerifiedConditionCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositoryIsVerifiedConditionCall) Do(f func(bool) database.Condition) *MockOrganizationDomainRepositoryIsVerifiedConditionCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositoryIsVerifiedConditionCall) DoAndReturn(f func(bool) database.Condition) *MockOrganizationDomainRepositoryIsVerifiedConditionCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// List mocks base method. +func (m *MockOrganizationDomainRepository) List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*domain.OrganizationDomain, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, client} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "List", varargs...) + ret0, _ := ret[0].([]*domain.OrganizationDomain) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockOrganizationDomainRepositoryMockRecorder) List(ctx, client any, opts ...any) *MockOrganizationDomainRepositoryListCall { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, client}, opts...) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).List), varargs...) + return &MockOrganizationDomainRepositoryListCall{Call: call} +} + +// MockOrganizationDomainRepositoryListCall wrap *gomock.Call +type MockOrganizationDomainRepositoryListCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositoryListCall) Return(arg0 []*domain.OrganizationDomain, arg1 error) *MockOrganizationDomainRepositoryListCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositoryListCall) Do(f func(context.Context, database.QueryExecutor, ...database.QueryOption) ([]*domain.OrganizationDomain, error)) *MockOrganizationDomainRepositoryListCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositoryListCall) DoAndReturn(f func(context.Context, database.QueryExecutor, ...database.QueryOption) ([]*domain.OrganizationDomain, error)) *MockOrganizationDomainRepositoryListCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// OrgIDColumn mocks base method. +func (m *MockOrganizationDomainRepository) OrgIDColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OrgIDColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// OrgIDColumn indicates an expected call of OrgIDColumn. +func (mr *MockOrganizationDomainRepositoryMockRecorder) OrgIDColumn() *MockOrganizationDomainRepositoryOrgIDColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OrgIDColumn", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).OrgIDColumn)) + return &MockOrganizationDomainRepositoryOrgIDColumnCall{Call: call} +} + +// MockOrganizationDomainRepositoryOrgIDColumnCall wrap *gomock.Call +type MockOrganizationDomainRepositoryOrgIDColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositoryOrgIDColumnCall) Return(arg0 database.Column) *MockOrganizationDomainRepositoryOrgIDColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositoryOrgIDColumnCall) Do(f func() database.Column) *MockOrganizationDomainRepositoryOrgIDColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositoryOrgIDColumnCall) DoAndReturn(f func() database.Column) *MockOrganizationDomainRepositoryOrgIDColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// OrgIDCondition mocks base method. +func (m *MockOrganizationDomainRepository) OrgIDCondition(orgID string) database.Condition { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OrgIDCondition", orgID) + ret0, _ := ret[0].(database.Condition) + return ret0 +} + +// OrgIDCondition indicates an expected call of OrgIDCondition. +func (mr *MockOrganizationDomainRepositoryMockRecorder) OrgIDCondition(orgID any) *MockOrganizationDomainRepositoryOrgIDConditionCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OrgIDCondition", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).OrgIDCondition), orgID) + return &MockOrganizationDomainRepositoryOrgIDConditionCall{Call: call} +} + +// MockOrganizationDomainRepositoryOrgIDConditionCall wrap *gomock.Call +type MockOrganizationDomainRepositoryOrgIDConditionCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositoryOrgIDConditionCall) Return(arg0 database.Condition) *MockOrganizationDomainRepositoryOrgIDConditionCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositoryOrgIDConditionCall) Do(f func(string) database.Condition) *MockOrganizationDomainRepositoryOrgIDConditionCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositoryOrgIDConditionCall) DoAndReturn(f func(string) database.Condition) *MockOrganizationDomainRepositoryOrgIDConditionCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Remove mocks base method. +func (m *MockOrganizationDomainRepository) Remove(ctx context.Context, client database.QueryExecutor, condition database.Condition) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Remove", ctx, client, condition) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Remove indicates an expected call of Remove. +func (mr *MockOrganizationDomainRepositoryMockRecorder) Remove(ctx, client, condition any) *MockOrganizationDomainRepositoryRemoveCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).Remove), ctx, client, condition) + return &MockOrganizationDomainRepositoryRemoveCall{Call: call} +} + +// MockOrganizationDomainRepositoryRemoveCall wrap *gomock.Call +type MockOrganizationDomainRepositoryRemoveCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositoryRemoveCall) Return(arg0 int64, arg1 error) *MockOrganizationDomainRepositoryRemoveCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositoryRemoveCall) Do(f func(context.Context, database.QueryExecutor, database.Condition) (int64, error)) *MockOrganizationDomainRepositoryRemoveCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositoryRemoveCall) DoAndReturn(f func(context.Context, database.QueryExecutor, database.Condition) (int64, error)) *MockOrganizationDomainRepositoryRemoveCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// SetPrimary mocks base method. +func (m *MockOrganizationDomainRepository) SetPrimary() database.Change { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetPrimary") + ret0, _ := ret[0].(database.Change) + return ret0 +} + +// SetPrimary indicates an expected call of SetPrimary. +func (mr *MockOrganizationDomainRepositoryMockRecorder) SetPrimary() *MockOrganizationDomainRepositorySetPrimaryCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPrimary", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).SetPrimary)) + return &MockOrganizationDomainRepositorySetPrimaryCall{Call: call} +} + +// MockOrganizationDomainRepositorySetPrimaryCall wrap *gomock.Call +type MockOrganizationDomainRepositorySetPrimaryCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositorySetPrimaryCall) Return(arg0 database.Change) *MockOrganizationDomainRepositorySetPrimaryCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositorySetPrimaryCall) Do(f func() database.Change) *MockOrganizationDomainRepositorySetPrimaryCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositorySetPrimaryCall) DoAndReturn(f func() database.Change) *MockOrganizationDomainRepositorySetPrimaryCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// SetUpdatedAt mocks base method. +func (m *MockOrganizationDomainRepository) SetUpdatedAt(t time.Time) database.Change { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetUpdatedAt", t) + ret0, _ := ret[0].(database.Change) + return ret0 +} + +// SetUpdatedAt indicates an expected call of SetUpdatedAt. +func (mr *MockOrganizationDomainRepositoryMockRecorder) SetUpdatedAt(t any) *MockOrganizationDomainRepositorySetUpdatedAtCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetUpdatedAt", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).SetUpdatedAt), t) + return &MockOrganizationDomainRepositorySetUpdatedAtCall{Call: call} +} + +// MockOrganizationDomainRepositorySetUpdatedAtCall wrap *gomock.Call +type MockOrganizationDomainRepositorySetUpdatedAtCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositorySetUpdatedAtCall) Return(arg0 database.Change) *MockOrganizationDomainRepositorySetUpdatedAtCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositorySetUpdatedAtCall) Do(f func(time.Time) database.Change) *MockOrganizationDomainRepositorySetUpdatedAtCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositorySetUpdatedAtCall) DoAndReturn(f func(time.Time) database.Change) *MockOrganizationDomainRepositorySetUpdatedAtCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// SetValidationType mocks base method. +func (m *MockOrganizationDomainRepository) SetValidationType(verificationType domain.DomainValidationType) database.Change { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetValidationType", verificationType) + ret0, _ := ret[0].(database.Change) + return ret0 +} + +// SetValidationType indicates an expected call of SetValidationType. +func (mr *MockOrganizationDomainRepositoryMockRecorder) SetValidationType(verificationType any) *MockOrganizationDomainRepositorySetValidationTypeCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetValidationType", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).SetValidationType), verificationType) + return &MockOrganizationDomainRepositorySetValidationTypeCall{Call: call} +} + +// MockOrganizationDomainRepositorySetValidationTypeCall wrap *gomock.Call +type MockOrganizationDomainRepositorySetValidationTypeCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositorySetValidationTypeCall) Return(arg0 database.Change) *MockOrganizationDomainRepositorySetValidationTypeCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositorySetValidationTypeCall) Do(f func(domain.DomainValidationType) database.Change) *MockOrganizationDomainRepositorySetValidationTypeCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositorySetValidationTypeCall) DoAndReturn(f func(domain.DomainValidationType) database.Change) *MockOrganizationDomainRepositorySetValidationTypeCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// SetVerified mocks base method. +func (m *MockOrganizationDomainRepository) SetVerified() database.Change { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetVerified") + ret0, _ := ret[0].(database.Change) + return ret0 +} + +// SetVerified indicates an expected call of SetVerified. +func (mr *MockOrganizationDomainRepositoryMockRecorder) SetVerified() *MockOrganizationDomainRepositorySetVerifiedCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetVerified", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).SetVerified)) + return &MockOrganizationDomainRepositorySetVerifiedCall{Call: call} +} + +// MockOrganizationDomainRepositorySetVerifiedCall wrap *gomock.Call +type MockOrganizationDomainRepositorySetVerifiedCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositorySetVerifiedCall) Return(arg0 database.Change) *MockOrganizationDomainRepositorySetVerifiedCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositorySetVerifiedCall) Do(f func() database.Change) *MockOrganizationDomainRepositorySetVerifiedCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositorySetVerifiedCall) DoAndReturn(f func() database.Change) *MockOrganizationDomainRepositorySetVerifiedCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Update mocks base method. +func (m *MockOrganizationDomainRepository) Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, changes ...database.Change) (int64, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, client, condition} + for _, a := range changes { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Update", varargs...) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Update indicates an expected call of Update. +func (mr *MockOrganizationDomainRepositoryMockRecorder) Update(ctx, client, condition any, changes ...any) *MockOrganizationDomainRepositoryUpdateCall { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, client, condition}, changes...) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).Update), varargs...) + return &MockOrganizationDomainRepositoryUpdateCall{Call: call} +} + +// MockOrganizationDomainRepositoryUpdateCall wrap *gomock.Call +type MockOrganizationDomainRepositoryUpdateCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositoryUpdateCall) Return(arg0 int64, arg1 error) *MockOrganizationDomainRepositoryUpdateCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositoryUpdateCall) Do(f func(context.Context, database.QueryExecutor, database.Condition, ...database.Change) (int64, error)) *MockOrganizationDomainRepositoryUpdateCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositoryUpdateCall) DoAndReturn(f func(context.Context, database.QueryExecutor, database.Condition, ...database.Change) (int64, error)) *MockOrganizationDomainRepositoryUpdateCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// UpdatedAtColumn mocks base method. +func (m *MockOrganizationDomainRepository) UpdatedAtColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdatedAtColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// UpdatedAtColumn indicates an expected call of UpdatedAtColumn. +func (mr *MockOrganizationDomainRepositoryMockRecorder) UpdatedAtColumn() *MockOrganizationDomainRepositoryUpdatedAtColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatedAtColumn", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).UpdatedAtColumn)) + return &MockOrganizationDomainRepositoryUpdatedAtColumnCall{Call: call} +} + +// MockOrganizationDomainRepositoryUpdatedAtColumnCall wrap *gomock.Call +type MockOrganizationDomainRepositoryUpdatedAtColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositoryUpdatedAtColumnCall) Return(arg0 database.Column) *MockOrganizationDomainRepositoryUpdatedAtColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositoryUpdatedAtColumnCall) Do(f func() database.Column) *MockOrganizationDomainRepositoryUpdatedAtColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositoryUpdatedAtColumnCall) DoAndReturn(f func() database.Column) *MockOrganizationDomainRepositoryUpdatedAtColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// ValidationTypeColumn mocks base method. +func (m *MockOrganizationDomainRepository) ValidationTypeColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidationTypeColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// ValidationTypeColumn indicates an expected call of ValidationTypeColumn. +func (mr *MockOrganizationDomainRepositoryMockRecorder) ValidationTypeColumn() *MockOrganizationDomainRepositoryValidationTypeColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidationTypeColumn", reflect.TypeOf((*MockOrganizationDomainRepository)(nil).ValidationTypeColumn)) + return &MockOrganizationDomainRepositoryValidationTypeColumnCall{Call: call} +} + +// MockOrganizationDomainRepositoryValidationTypeColumnCall wrap *gomock.Call +type MockOrganizationDomainRepositoryValidationTypeColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockOrganizationDomainRepositoryValidationTypeColumnCall) Return(arg0 database.Column) *MockOrganizationDomainRepositoryValidationTypeColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockOrganizationDomainRepositoryValidationTypeColumnCall) Do(f func() database.Column) *MockOrganizationDomainRepositoryValidationTypeColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockOrganizationDomainRepositoryValidationTypeColumnCall) DoAndReturn(f func() database.Column) *MockOrganizationDomainRepositoryValidationTypeColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} diff --git a/backend/v3/domain/mock/org_domain_repo.go b/backend/v3/domain/mock/org_domain_repo.go new file mode 100644 index 000000000..0b29f4d98 --- /dev/null +++ b/backend/v3/domain/mock/org_domain_repo.go @@ -0,0 +1,48 @@ +package domainmock + +import ( + context "context" + + gomock "go.uber.org/mock/gomock" + + domain "github.com/zitadel/zitadel/backend/v3/domain" + database "github.com/zitadel/zitadel/backend/v3/storage/database" + "github.com/zitadel/zitadel/backend/v3/storage/database/repository" +) + +type OrgDomainRepo struct { + domain.OrganizationDomainRepository + mock *MockOrganizationDomainRepository +} + +func NewOrgDomainRepo(ctrl *gomock.Controller) *OrgDomainRepo { + return &OrgDomainRepo{ + mock: NewMockOrganizationDomainRepository(ctrl), + OrganizationDomainRepository: repository.OrganizationDomainRepository(), + } +} + +func (r *OrgDomainRepo) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.OrganizationDomain, error) { + return r.mock.Get(ctx, client, opts...) + +} + +func (r *OrgDomainRepo) List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*domain.OrganizationDomain, error) { + return r.mock.List(ctx, client, opts...) + +} + +func (r *OrgDomainRepo) Add(ctx context.Context, client database.QueryExecutor, domain *domain.AddOrganizationDomain) error { + return r.mock.Add(ctx, client, domain) + +} + +func (r *OrgDomainRepo) Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, changes ...database.Change) (int64, error) { + return r.mock.Update(ctx, client, condition, changes...) + +} + +func (r *OrgDomainRepo) Remove(ctx context.Context, client database.QueryExecutor, condition database.Condition) (int64, error) { + return r.mock.Remove(ctx, client, condition) + +} diff --git a/backend/v3/domain/mock/org_repo.go b/backend/v3/domain/mock/org_repo.go new file mode 100644 index 000000000..b55dd7dd9 --- /dev/null +++ b/backend/v3/domain/mock/org_repo.go @@ -0,0 +1,69 @@ +package domainmock + +import ( + context "context" + + gomock "go.uber.org/mock/gomock" + + "github.com/zitadel/zitadel/backend/v3/domain" + database "github.com/zitadel/zitadel/backend/v3/storage/database" + "github.com/zitadel/zitadel/backend/v3/storage/database/repository" +) + +func NewOrgRepo(ctrl *gomock.Controller) *OrgRepo { + return &OrgRepo{ + mock: NewMockOrganizationRepository(ctrl), + OrganizationRepository: repository.OrganizationRepository(), + } +} + +type OrgRepo struct { + domain.OrganizationRepository + mock *MockOrganizationRepository + domainRepo *OrgDomainRepo + + existDomain database.Condition +} + +func (r *OrgRepo) SetDomains(repo *OrgDomainRepo) { + r.domainRepo = repo +} + +// SetExistsDomain is a helper function that allows to skip calling EXISTS() when ExistsDomain() is called. +// You will not be able to mock the ExistsDomain() call though +func (r *OrgRepo) SetExistsDomain(existsDomainCondition database.Condition) { + r.existDomain = existsDomainCondition + +} + +func (r *OrgRepo) ExistsDomain(_ database.Condition) database.Condition { + return r.existDomain +} + +func (r *OrgRepo) EXPECT() *MockOrganizationRepositoryMockRecorder { + return r.mock.EXPECT() +} + +func (r *OrgRepo) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.Organization, error) { + return r.mock.Get(ctx, client, opts...) +} + +func (r *OrgRepo) List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*domain.Organization, error) { + return r.mock.List(ctx, client, opts...) +} + +func (r *OrgRepo) Create(ctx context.Context, client database.QueryExecutor, org *domain.Organization) error { + return r.mock.Create(ctx, client, org) +} + +func (r *OrgRepo) Update(ctx context.Context, client database.QueryExecutor, conditions database.Condition, changes ...database.Change) (int64, error) { + return r.mock.Update(ctx, client, conditions, changes...) +} + +func (r *OrgRepo) Delete(ctx context.Context, client database.QueryExecutor, conditions database.Condition) (int64, error) { + return r.mock.Delete(ctx, client, conditions) +} + +func (r *OrgRepo) LoadDomains() domain.OrganizationRepository { + return r.mock.LoadDomains() +} diff --git a/backend/v3/domain/mock/project.mock.go b/backend/v3/domain/mock/project.mock.go new file mode 100644 index 000000000..9fcb2d4db --- /dev/null +++ b/backend/v3/domain/mock/project.mock.go @@ -0,0 +1,1241 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/zitadel/zitadel/backend/v3/domain (interfaces: ProjectRepository) +// +// Generated by this command: +// +// mockgen -typed -package domainmock -destination ./mock/project.mock.go . ProjectRepository +// + +// Package domainmock is a generated GoMock package. +package domainmock + +import ( + context "context" + reflect "reflect" + time "time" + + domain "github.com/zitadel/zitadel/backend/v3/domain" + database "github.com/zitadel/zitadel/backend/v3/storage/database" + gomock "go.uber.org/mock/gomock" +) + +// MockProjectRepository is a mock of ProjectRepository interface. +type MockProjectRepository struct { + ctrl *gomock.Controller + recorder *MockProjectRepositoryMockRecorder + isgomock struct{} +} + +// MockProjectRepositoryMockRecorder is the mock recorder for MockProjectRepository. +type MockProjectRepositoryMockRecorder struct { + mock *MockProjectRepository +} + +// NewMockProjectRepository creates a new mock instance. +func NewMockProjectRepository(ctrl *gomock.Controller) *MockProjectRepository { + mock := &MockProjectRepository{ctrl: ctrl} + mock.recorder = &MockProjectRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockProjectRepository) EXPECT() *MockProjectRepositoryMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockProjectRepository) Create(ctx context.Context, client database.QueryExecutor, project *domain.Project) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", ctx, client, project) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockProjectRepositoryMockRecorder) Create(ctx, client, project any) *MockProjectRepositoryCreateCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockProjectRepository)(nil).Create), ctx, client, project) + return &MockProjectRepositoryCreateCall{Call: call} +} + +// MockProjectRepositoryCreateCall wrap *gomock.Call +type MockProjectRepositoryCreateCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryCreateCall) Return(arg0 error) *MockProjectRepositoryCreateCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryCreateCall) Do(f func(context.Context, database.QueryExecutor, *domain.Project) error) *MockProjectRepositoryCreateCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryCreateCall) DoAndReturn(f func(context.Context, database.QueryExecutor, *domain.Project) error) *MockProjectRepositoryCreateCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// CreatedAtColumn mocks base method. +func (m *MockProjectRepository) CreatedAtColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreatedAtColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// CreatedAtColumn indicates an expected call of CreatedAtColumn. +func (mr *MockProjectRepositoryMockRecorder) CreatedAtColumn() *MockProjectRepositoryCreatedAtColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatedAtColumn", reflect.TypeOf((*MockProjectRepository)(nil).CreatedAtColumn)) + return &MockProjectRepositoryCreatedAtColumnCall{Call: call} +} + +// MockProjectRepositoryCreatedAtColumnCall wrap *gomock.Call +type MockProjectRepositoryCreatedAtColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryCreatedAtColumnCall) Return(arg0 database.Column) *MockProjectRepositoryCreatedAtColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryCreatedAtColumnCall) Do(f func() database.Column) *MockProjectRepositoryCreatedAtColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryCreatedAtColumnCall) DoAndReturn(f func() database.Column) *MockProjectRepositoryCreatedAtColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Delete mocks base method. +func (m *MockProjectRepository) Delete(ctx context.Context, client database.QueryExecutor, condition database.Condition) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", ctx, client, condition) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Delete indicates an expected call of Delete. +func (mr *MockProjectRepositoryMockRecorder) Delete(ctx, client, condition any) *MockProjectRepositoryDeleteCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockProjectRepository)(nil).Delete), ctx, client, condition) + return &MockProjectRepositoryDeleteCall{Call: call} +} + +// MockProjectRepositoryDeleteCall wrap *gomock.Call +type MockProjectRepositoryDeleteCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryDeleteCall) Return(arg0 int64, arg1 error) *MockProjectRepositoryDeleteCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryDeleteCall) Do(f func(context.Context, database.QueryExecutor, database.Condition) (int64, error)) *MockProjectRepositoryDeleteCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryDeleteCall) DoAndReturn(f func(context.Context, database.QueryExecutor, database.Condition) (int64, error)) *MockProjectRepositoryDeleteCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Get mocks base method. +func (m *MockProjectRepository) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.Project, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, client} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Get", varargs...) + ret0, _ := ret[0].(*domain.Project) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockProjectRepositoryMockRecorder) Get(ctx, client any, opts ...any) *MockProjectRepositoryGetCall { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, client}, opts...) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockProjectRepository)(nil).Get), varargs...) + return &MockProjectRepositoryGetCall{Call: call} +} + +// MockProjectRepositoryGetCall wrap *gomock.Call +type MockProjectRepositoryGetCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryGetCall) Return(arg0 *domain.Project, arg1 error) *MockProjectRepositoryGetCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryGetCall) Do(f func(context.Context, database.QueryExecutor, ...database.QueryOption) (*domain.Project, error)) *MockProjectRepositoryGetCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryGetCall) DoAndReturn(f func(context.Context, database.QueryExecutor, ...database.QueryOption) (*domain.Project, error)) *MockProjectRepositoryGetCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// IDColumn mocks base method. +func (m *MockProjectRepository) IDColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IDColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// IDColumn indicates an expected call of IDColumn. +func (mr *MockProjectRepositoryMockRecorder) IDColumn() *MockProjectRepositoryIDColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IDColumn", reflect.TypeOf((*MockProjectRepository)(nil).IDColumn)) + return &MockProjectRepositoryIDColumnCall{Call: call} +} + +// MockProjectRepositoryIDColumnCall wrap *gomock.Call +type MockProjectRepositoryIDColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryIDColumnCall) Return(arg0 database.Column) *MockProjectRepositoryIDColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryIDColumnCall) Do(f func() database.Column) *MockProjectRepositoryIDColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryIDColumnCall) DoAndReturn(f func() database.Column) *MockProjectRepositoryIDColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// IDCondition mocks base method. +func (m *MockProjectRepository) IDCondition(projectID string) database.Condition { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IDCondition", projectID) + ret0, _ := ret[0].(database.Condition) + return ret0 +} + +// IDCondition indicates an expected call of IDCondition. +func (mr *MockProjectRepositoryMockRecorder) IDCondition(projectID any) *MockProjectRepositoryIDConditionCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IDCondition", reflect.TypeOf((*MockProjectRepository)(nil).IDCondition), projectID) + return &MockProjectRepositoryIDConditionCall{Call: call} +} + +// MockProjectRepositoryIDConditionCall wrap *gomock.Call +type MockProjectRepositoryIDConditionCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryIDConditionCall) Return(arg0 database.Condition) *MockProjectRepositoryIDConditionCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryIDConditionCall) Do(f func(string) database.Condition) *MockProjectRepositoryIDConditionCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryIDConditionCall) DoAndReturn(f func(string) database.Condition) *MockProjectRepositoryIDConditionCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// InstanceIDColumn mocks base method. +func (m *MockProjectRepository) InstanceIDColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InstanceIDColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// InstanceIDColumn indicates an expected call of InstanceIDColumn. +func (mr *MockProjectRepositoryMockRecorder) InstanceIDColumn() *MockProjectRepositoryInstanceIDColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceIDColumn", reflect.TypeOf((*MockProjectRepository)(nil).InstanceIDColumn)) + return &MockProjectRepositoryInstanceIDColumnCall{Call: call} +} + +// MockProjectRepositoryInstanceIDColumnCall wrap *gomock.Call +type MockProjectRepositoryInstanceIDColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryInstanceIDColumnCall) Return(arg0 database.Column) *MockProjectRepositoryInstanceIDColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryInstanceIDColumnCall) Do(f func() database.Column) *MockProjectRepositoryInstanceIDColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryInstanceIDColumnCall) DoAndReturn(f func() database.Column) *MockProjectRepositoryInstanceIDColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// InstanceIDCondition mocks base method. +func (m *MockProjectRepository) InstanceIDCondition(instanceID string) database.Condition { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InstanceIDCondition", instanceID) + ret0, _ := ret[0].(database.Condition) + return ret0 +} + +// InstanceIDCondition indicates an expected call of InstanceIDCondition. +func (mr *MockProjectRepositoryMockRecorder) InstanceIDCondition(instanceID any) *MockProjectRepositoryInstanceIDConditionCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceIDCondition", reflect.TypeOf((*MockProjectRepository)(nil).InstanceIDCondition), instanceID) + return &MockProjectRepositoryInstanceIDConditionCall{Call: call} +} + +// MockProjectRepositoryInstanceIDConditionCall wrap *gomock.Call +type MockProjectRepositoryInstanceIDConditionCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryInstanceIDConditionCall) Return(arg0 database.Condition) *MockProjectRepositoryInstanceIDConditionCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryInstanceIDConditionCall) Do(f func(string) database.Condition) *MockProjectRepositoryInstanceIDConditionCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryInstanceIDConditionCall) DoAndReturn(f func(string) database.Condition) *MockProjectRepositoryInstanceIDConditionCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// IsAuthorizationRequiredColumn mocks base method. +func (m *MockProjectRepository) IsAuthorizationRequiredColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsAuthorizationRequiredColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// IsAuthorizationRequiredColumn indicates an expected call of IsAuthorizationRequiredColumn. +func (mr *MockProjectRepositoryMockRecorder) IsAuthorizationRequiredColumn() *MockProjectRepositoryIsAuthorizationRequiredColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAuthorizationRequiredColumn", reflect.TypeOf((*MockProjectRepository)(nil).IsAuthorizationRequiredColumn)) + return &MockProjectRepositoryIsAuthorizationRequiredColumnCall{Call: call} +} + +// MockProjectRepositoryIsAuthorizationRequiredColumnCall wrap *gomock.Call +type MockProjectRepositoryIsAuthorizationRequiredColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryIsAuthorizationRequiredColumnCall) Return(arg0 database.Column) *MockProjectRepositoryIsAuthorizationRequiredColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryIsAuthorizationRequiredColumnCall) Do(f func() database.Column) *MockProjectRepositoryIsAuthorizationRequiredColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryIsAuthorizationRequiredColumnCall) DoAndReturn(f func() database.Column) *MockProjectRepositoryIsAuthorizationRequiredColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// IsProjectAccessRequiredColumn mocks base method. +func (m *MockProjectRepository) IsProjectAccessRequiredColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsProjectAccessRequiredColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// IsProjectAccessRequiredColumn indicates an expected call of IsProjectAccessRequiredColumn. +func (mr *MockProjectRepositoryMockRecorder) IsProjectAccessRequiredColumn() *MockProjectRepositoryIsProjectAccessRequiredColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsProjectAccessRequiredColumn", reflect.TypeOf((*MockProjectRepository)(nil).IsProjectAccessRequiredColumn)) + return &MockProjectRepositoryIsProjectAccessRequiredColumnCall{Call: call} +} + +// MockProjectRepositoryIsProjectAccessRequiredColumnCall wrap *gomock.Call +type MockProjectRepositoryIsProjectAccessRequiredColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryIsProjectAccessRequiredColumnCall) Return(arg0 database.Column) *MockProjectRepositoryIsProjectAccessRequiredColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryIsProjectAccessRequiredColumnCall) Do(f func() database.Column) *MockProjectRepositoryIsProjectAccessRequiredColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryIsProjectAccessRequiredColumnCall) DoAndReturn(f func() database.Column) *MockProjectRepositoryIsProjectAccessRequiredColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// List mocks base method. +func (m *MockProjectRepository) List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*domain.Project, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, client} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "List", varargs...) + ret0, _ := ret[0].([]*domain.Project) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockProjectRepositoryMockRecorder) List(ctx, client any, opts ...any) *MockProjectRepositoryListCall { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, client}, opts...) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockProjectRepository)(nil).List), varargs...) + return &MockProjectRepositoryListCall{Call: call} +} + +// MockProjectRepositoryListCall wrap *gomock.Call +type MockProjectRepositoryListCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryListCall) Return(arg0 []*domain.Project, arg1 error) *MockProjectRepositoryListCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryListCall) Do(f func(context.Context, database.QueryExecutor, ...database.QueryOption) ([]*domain.Project, error)) *MockProjectRepositoryListCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryListCall) DoAndReturn(f func(context.Context, database.QueryExecutor, ...database.QueryOption) ([]*domain.Project, error)) *MockProjectRepositoryListCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// NameColumn mocks base method. +func (m *MockProjectRepository) NameColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NameColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// NameColumn indicates an expected call of NameColumn. +func (mr *MockProjectRepositoryMockRecorder) NameColumn() *MockProjectRepositoryNameColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NameColumn", reflect.TypeOf((*MockProjectRepository)(nil).NameColumn)) + return &MockProjectRepositoryNameColumnCall{Call: call} +} + +// MockProjectRepositoryNameColumnCall wrap *gomock.Call +type MockProjectRepositoryNameColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryNameColumnCall) Return(arg0 database.Column) *MockProjectRepositoryNameColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryNameColumnCall) Do(f func() database.Column) *MockProjectRepositoryNameColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryNameColumnCall) DoAndReturn(f func() database.Column) *MockProjectRepositoryNameColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// NameCondition mocks base method. +func (m *MockProjectRepository) NameCondition(op database.TextOperation, name string) database.Condition { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NameCondition", op, name) + ret0, _ := ret[0].(database.Condition) + return ret0 +} + +// NameCondition indicates an expected call of NameCondition. +func (mr *MockProjectRepositoryMockRecorder) NameCondition(op, name any) *MockProjectRepositoryNameConditionCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NameCondition", reflect.TypeOf((*MockProjectRepository)(nil).NameCondition), op, name) + return &MockProjectRepositoryNameConditionCall{Call: call} +} + +// MockProjectRepositoryNameConditionCall wrap *gomock.Call +type MockProjectRepositoryNameConditionCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryNameConditionCall) Return(arg0 database.Condition) *MockProjectRepositoryNameConditionCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryNameConditionCall) Do(f func(database.TextOperation, string) database.Condition) *MockProjectRepositoryNameConditionCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryNameConditionCall) DoAndReturn(f func(database.TextOperation, string) database.Condition) *MockProjectRepositoryNameConditionCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// OrganizationIDColumn mocks base method. +func (m *MockProjectRepository) OrganizationIDColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OrganizationIDColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// OrganizationIDColumn indicates an expected call of OrganizationIDColumn. +func (mr *MockProjectRepositoryMockRecorder) OrganizationIDColumn() *MockProjectRepositoryOrganizationIDColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OrganizationIDColumn", reflect.TypeOf((*MockProjectRepository)(nil).OrganizationIDColumn)) + return &MockProjectRepositoryOrganizationIDColumnCall{Call: call} +} + +// MockProjectRepositoryOrganizationIDColumnCall wrap *gomock.Call +type MockProjectRepositoryOrganizationIDColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryOrganizationIDColumnCall) Return(arg0 database.Column) *MockProjectRepositoryOrganizationIDColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryOrganizationIDColumnCall) Do(f func() database.Column) *MockProjectRepositoryOrganizationIDColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryOrganizationIDColumnCall) DoAndReturn(f func() database.Column) *MockProjectRepositoryOrganizationIDColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// OrganizationIDCondition mocks base method. +func (m *MockProjectRepository) OrganizationIDCondition(organizationID string) database.Condition { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OrganizationIDCondition", organizationID) + ret0, _ := ret[0].(database.Condition) + return ret0 +} + +// OrganizationIDCondition indicates an expected call of OrganizationIDCondition. +func (mr *MockProjectRepositoryMockRecorder) OrganizationIDCondition(organizationID any) *MockProjectRepositoryOrganizationIDConditionCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OrganizationIDCondition", reflect.TypeOf((*MockProjectRepository)(nil).OrganizationIDCondition), organizationID) + return &MockProjectRepositoryOrganizationIDConditionCall{Call: call} +} + +// MockProjectRepositoryOrganizationIDConditionCall wrap *gomock.Call +type MockProjectRepositoryOrganizationIDConditionCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryOrganizationIDConditionCall) Return(arg0 database.Condition) *MockProjectRepositoryOrganizationIDConditionCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryOrganizationIDConditionCall) Do(f func(string) database.Condition) *MockProjectRepositoryOrganizationIDConditionCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryOrganizationIDConditionCall) DoAndReturn(f func(string) database.Condition) *MockProjectRepositoryOrganizationIDConditionCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// PrimaryKeyColumns mocks base method. +func (m *MockProjectRepository) PrimaryKeyColumns() []database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PrimaryKeyColumns") + ret0, _ := ret[0].([]database.Column) + return ret0 +} + +// PrimaryKeyColumns indicates an expected call of PrimaryKeyColumns. +func (mr *MockProjectRepositoryMockRecorder) PrimaryKeyColumns() *MockProjectRepositoryPrimaryKeyColumnsCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrimaryKeyColumns", reflect.TypeOf((*MockProjectRepository)(nil).PrimaryKeyColumns)) + return &MockProjectRepositoryPrimaryKeyColumnsCall{Call: call} +} + +// MockProjectRepositoryPrimaryKeyColumnsCall wrap *gomock.Call +type MockProjectRepositoryPrimaryKeyColumnsCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryPrimaryKeyColumnsCall) Return(arg0 []database.Column) *MockProjectRepositoryPrimaryKeyColumnsCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryPrimaryKeyColumnsCall) Do(f func() []database.Column) *MockProjectRepositoryPrimaryKeyColumnsCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryPrimaryKeyColumnsCall) DoAndReturn(f func() []database.Column) *MockProjectRepositoryPrimaryKeyColumnsCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// PrimaryKeyCondition mocks base method. +func (m *MockProjectRepository) PrimaryKeyCondition(instanceID, projectID string) database.Condition { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PrimaryKeyCondition", instanceID, projectID) + ret0, _ := ret[0].(database.Condition) + return ret0 +} + +// PrimaryKeyCondition indicates an expected call of PrimaryKeyCondition. +func (mr *MockProjectRepositoryMockRecorder) PrimaryKeyCondition(instanceID, projectID any) *MockProjectRepositoryPrimaryKeyConditionCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrimaryKeyCondition", reflect.TypeOf((*MockProjectRepository)(nil).PrimaryKeyCondition), instanceID, projectID) + return &MockProjectRepositoryPrimaryKeyConditionCall{Call: call} +} + +// MockProjectRepositoryPrimaryKeyConditionCall wrap *gomock.Call +type MockProjectRepositoryPrimaryKeyConditionCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryPrimaryKeyConditionCall) Return(arg0 database.Condition) *MockProjectRepositoryPrimaryKeyConditionCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryPrimaryKeyConditionCall) Do(f func(string, string) database.Condition) *MockProjectRepositoryPrimaryKeyConditionCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryPrimaryKeyConditionCall) DoAndReturn(f func(string, string) database.Condition) *MockProjectRepositoryPrimaryKeyConditionCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Role mocks base method. +func (m *MockProjectRepository) Role() domain.ProjectRoleRepository { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Role") + ret0, _ := ret[0].(domain.ProjectRoleRepository) + return ret0 +} + +// Role indicates an expected call of Role. +func (mr *MockProjectRepositoryMockRecorder) Role() *MockProjectRepositoryRoleCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Role", reflect.TypeOf((*MockProjectRepository)(nil).Role)) + return &MockProjectRepositoryRoleCall{Call: call} +} + +// MockProjectRepositoryRoleCall wrap *gomock.Call +type MockProjectRepositoryRoleCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryRoleCall) Return(arg0 domain.ProjectRoleRepository) *MockProjectRepositoryRoleCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryRoleCall) Do(f func() domain.ProjectRoleRepository) *MockProjectRepositoryRoleCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryRoleCall) DoAndReturn(f func() domain.ProjectRoleRepository) *MockProjectRepositoryRoleCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// SetIsAuthorizationRequired mocks base method. +func (m *MockProjectRepository) SetIsAuthorizationRequired(isAuthorizationRequired bool) database.Change { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetIsAuthorizationRequired", isAuthorizationRequired) + ret0, _ := ret[0].(database.Change) + return ret0 +} + +// SetIsAuthorizationRequired indicates an expected call of SetIsAuthorizationRequired. +func (mr *MockProjectRepositoryMockRecorder) SetIsAuthorizationRequired(isAuthorizationRequired any) *MockProjectRepositorySetIsAuthorizationRequiredCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetIsAuthorizationRequired", reflect.TypeOf((*MockProjectRepository)(nil).SetIsAuthorizationRequired), isAuthorizationRequired) + return &MockProjectRepositorySetIsAuthorizationRequiredCall{Call: call} +} + +// MockProjectRepositorySetIsAuthorizationRequiredCall wrap *gomock.Call +type MockProjectRepositorySetIsAuthorizationRequiredCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositorySetIsAuthorizationRequiredCall) Return(arg0 database.Change) *MockProjectRepositorySetIsAuthorizationRequiredCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositorySetIsAuthorizationRequiredCall) Do(f func(bool) database.Change) *MockProjectRepositorySetIsAuthorizationRequiredCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositorySetIsAuthorizationRequiredCall) DoAndReturn(f func(bool) database.Change) *MockProjectRepositorySetIsAuthorizationRequiredCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// SetIsProjectAccessRequired mocks base method. +func (m *MockProjectRepository) SetIsProjectAccessRequired(isProjectAccessRequired bool) database.Change { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetIsProjectAccessRequired", isProjectAccessRequired) + ret0, _ := ret[0].(database.Change) + return ret0 +} + +// SetIsProjectAccessRequired indicates an expected call of SetIsProjectAccessRequired. +func (mr *MockProjectRepositoryMockRecorder) SetIsProjectAccessRequired(isProjectAccessRequired any) *MockProjectRepositorySetIsProjectAccessRequiredCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetIsProjectAccessRequired", reflect.TypeOf((*MockProjectRepository)(nil).SetIsProjectAccessRequired), isProjectAccessRequired) + return &MockProjectRepositorySetIsProjectAccessRequiredCall{Call: call} +} + +// MockProjectRepositorySetIsProjectAccessRequiredCall wrap *gomock.Call +type MockProjectRepositorySetIsProjectAccessRequiredCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositorySetIsProjectAccessRequiredCall) Return(arg0 database.Change) *MockProjectRepositorySetIsProjectAccessRequiredCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositorySetIsProjectAccessRequiredCall) Do(f func(bool) database.Change) *MockProjectRepositorySetIsProjectAccessRequiredCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositorySetIsProjectAccessRequiredCall) DoAndReturn(f func(bool) database.Change) *MockProjectRepositorySetIsProjectAccessRequiredCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// SetName mocks base method. +func (m *MockProjectRepository) SetName(name string) database.Change { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetName", name) + ret0, _ := ret[0].(database.Change) + return ret0 +} + +// SetName indicates an expected call of SetName. +func (mr *MockProjectRepositoryMockRecorder) SetName(name any) *MockProjectRepositorySetNameCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetName", reflect.TypeOf((*MockProjectRepository)(nil).SetName), name) + return &MockProjectRepositorySetNameCall{Call: call} +} + +// MockProjectRepositorySetNameCall wrap *gomock.Call +type MockProjectRepositorySetNameCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositorySetNameCall) Return(arg0 database.Change) *MockProjectRepositorySetNameCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositorySetNameCall) Do(f func(string) database.Change) *MockProjectRepositorySetNameCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositorySetNameCall) DoAndReturn(f func(string) database.Change) *MockProjectRepositorySetNameCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// SetShouldAssertRole mocks base method. +func (m *MockProjectRepository) SetShouldAssertRole(shouldAssertRole bool) database.Change { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetShouldAssertRole", shouldAssertRole) + ret0, _ := ret[0].(database.Change) + return ret0 +} + +// SetShouldAssertRole indicates an expected call of SetShouldAssertRole. +func (mr *MockProjectRepositoryMockRecorder) SetShouldAssertRole(shouldAssertRole any) *MockProjectRepositorySetShouldAssertRoleCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetShouldAssertRole", reflect.TypeOf((*MockProjectRepository)(nil).SetShouldAssertRole), shouldAssertRole) + return &MockProjectRepositorySetShouldAssertRoleCall{Call: call} +} + +// MockProjectRepositorySetShouldAssertRoleCall wrap *gomock.Call +type MockProjectRepositorySetShouldAssertRoleCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositorySetShouldAssertRoleCall) Return(arg0 database.Change) *MockProjectRepositorySetShouldAssertRoleCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositorySetShouldAssertRoleCall) Do(f func(bool) database.Change) *MockProjectRepositorySetShouldAssertRoleCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositorySetShouldAssertRoleCall) DoAndReturn(f func(bool) database.Change) *MockProjectRepositorySetShouldAssertRoleCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// SetState mocks base method. +func (m *MockProjectRepository) SetState(state domain.ProjectState) database.Change { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetState", state) + ret0, _ := ret[0].(database.Change) + return ret0 +} + +// SetState indicates an expected call of SetState. +func (mr *MockProjectRepositoryMockRecorder) SetState(state any) *MockProjectRepositorySetStateCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetState", reflect.TypeOf((*MockProjectRepository)(nil).SetState), state) + return &MockProjectRepositorySetStateCall{Call: call} +} + +// MockProjectRepositorySetStateCall wrap *gomock.Call +type MockProjectRepositorySetStateCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositorySetStateCall) Return(arg0 database.Change) *MockProjectRepositorySetStateCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositorySetStateCall) Do(f func(domain.ProjectState) database.Change) *MockProjectRepositorySetStateCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositorySetStateCall) DoAndReturn(f func(domain.ProjectState) database.Change) *MockProjectRepositorySetStateCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// SetUpdatedAt mocks base method. +func (m *MockProjectRepository) SetUpdatedAt(updatedAt time.Time) database.Change { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetUpdatedAt", updatedAt) + ret0, _ := ret[0].(database.Change) + return ret0 +} + +// SetUpdatedAt indicates an expected call of SetUpdatedAt. +func (mr *MockProjectRepositoryMockRecorder) SetUpdatedAt(updatedAt any) *MockProjectRepositorySetUpdatedAtCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetUpdatedAt", reflect.TypeOf((*MockProjectRepository)(nil).SetUpdatedAt), updatedAt) + return &MockProjectRepositorySetUpdatedAtCall{Call: call} +} + +// MockProjectRepositorySetUpdatedAtCall wrap *gomock.Call +type MockProjectRepositorySetUpdatedAtCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositorySetUpdatedAtCall) Return(arg0 database.Change) *MockProjectRepositorySetUpdatedAtCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositorySetUpdatedAtCall) Do(f func(time.Time) database.Change) *MockProjectRepositorySetUpdatedAtCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositorySetUpdatedAtCall) DoAndReturn(f func(time.Time) database.Change) *MockProjectRepositorySetUpdatedAtCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// SetUsedLabelingSettingOwner mocks base method. +func (m *MockProjectRepository) SetUsedLabelingSettingOwner(usedLabelingSettingOwner int16) database.Change { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetUsedLabelingSettingOwner", usedLabelingSettingOwner) + ret0, _ := ret[0].(database.Change) + return ret0 +} + +// SetUsedLabelingSettingOwner indicates an expected call of SetUsedLabelingSettingOwner. +func (mr *MockProjectRepositoryMockRecorder) SetUsedLabelingSettingOwner(usedLabelingSettingOwner any) *MockProjectRepositorySetUsedLabelingSettingOwnerCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetUsedLabelingSettingOwner", reflect.TypeOf((*MockProjectRepository)(nil).SetUsedLabelingSettingOwner), usedLabelingSettingOwner) + return &MockProjectRepositorySetUsedLabelingSettingOwnerCall{Call: call} +} + +// MockProjectRepositorySetUsedLabelingSettingOwnerCall wrap *gomock.Call +type MockProjectRepositorySetUsedLabelingSettingOwnerCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositorySetUsedLabelingSettingOwnerCall) Return(arg0 database.Change) *MockProjectRepositorySetUsedLabelingSettingOwnerCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositorySetUsedLabelingSettingOwnerCall) Do(f func(int16) database.Change) *MockProjectRepositorySetUsedLabelingSettingOwnerCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositorySetUsedLabelingSettingOwnerCall) DoAndReturn(f func(int16) database.Change) *MockProjectRepositorySetUsedLabelingSettingOwnerCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// ShouldAssertRoleColumn mocks base method. +func (m *MockProjectRepository) ShouldAssertRoleColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ShouldAssertRoleColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// ShouldAssertRoleColumn indicates an expected call of ShouldAssertRoleColumn. +func (mr *MockProjectRepositoryMockRecorder) ShouldAssertRoleColumn() *MockProjectRepositoryShouldAssertRoleColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ShouldAssertRoleColumn", reflect.TypeOf((*MockProjectRepository)(nil).ShouldAssertRoleColumn)) + return &MockProjectRepositoryShouldAssertRoleColumnCall{Call: call} +} + +// MockProjectRepositoryShouldAssertRoleColumnCall wrap *gomock.Call +type MockProjectRepositoryShouldAssertRoleColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryShouldAssertRoleColumnCall) Return(arg0 database.Column) *MockProjectRepositoryShouldAssertRoleColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryShouldAssertRoleColumnCall) Do(f func() database.Column) *MockProjectRepositoryShouldAssertRoleColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryShouldAssertRoleColumnCall) DoAndReturn(f func() database.Column) *MockProjectRepositoryShouldAssertRoleColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// StateColumn mocks base method. +func (m *MockProjectRepository) StateColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// StateColumn indicates an expected call of StateColumn. +func (mr *MockProjectRepositoryMockRecorder) StateColumn() *MockProjectRepositoryStateColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateColumn", reflect.TypeOf((*MockProjectRepository)(nil).StateColumn)) + return &MockProjectRepositoryStateColumnCall{Call: call} +} + +// MockProjectRepositoryStateColumnCall wrap *gomock.Call +type MockProjectRepositoryStateColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryStateColumnCall) Return(arg0 database.Column) *MockProjectRepositoryStateColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryStateColumnCall) Do(f func() database.Column) *MockProjectRepositoryStateColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryStateColumnCall) DoAndReturn(f func() database.Column) *MockProjectRepositoryStateColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// StateCondition mocks base method. +func (m *MockProjectRepository) StateCondition(state domain.ProjectState) database.Condition { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateCondition", state) + ret0, _ := ret[0].(database.Condition) + return ret0 +} + +// StateCondition indicates an expected call of StateCondition. +func (mr *MockProjectRepositoryMockRecorder) StateCondition(state any) *MockProjectRepositoryStateConditionCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateCondition", reflect.TypeOf((*MockProjectRepository)(nil).StateCondition), state) + return &MockProjectRepositoryStateConditionCall{Call: call} +} + +// MockProjectRepositoryStateConditionCall wrap *gomock.Call +type MockProjectRepositoryStateConditionCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryStateConditionCall) Return(arg0 database.Condition) *MockProjectRepositoryStateConditionCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryStateConditionCall) Do(f func(domain.ProjectState) database.Condition) *MockProjectRepositoryStateConditionCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryStateConditionCall) DoAndReturn(f func(domain.ProjectState) database.Condition) *MockProjectRepositoryStateConditionCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Update mocks base method. +func (m *MockProjectRepository) Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, changes ...database.Change) (int64, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, client, condition} + for _, a := range changes { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Update", varargs...) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Update indicates an expected call of Update. +func (mr *MockProjectRepositoryMockRecorder) Update(ctx, client, condition any, changes ...any) *MockProjectRepositoryUpdateCall { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, client, condition}, changes...) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockProjectRepository)(nil).Update), varargs...) + return &MockProjectRepositoryUpdateCall{Call: call} +} + +// MockProjectRepositoryUpdateCall wrap *gomock.Call +type MockProjectRepositoryUpdateCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryUpdateCall) Return(arg0 int64, arg1 error) *MockProjectRepositoryUpdateCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryUpdateCall) Do(f func(context.Context, database.QueryExecutor, database.Condition, ...database.Change) (int64, error)) *MockProjectRepositoryUpdateCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryUpdateCall) DoAndReturn(f func(context.Context, database.QueryExecutor, database.Condition, ...database.Change) (int64, error)) *MockProjectRepositoryUpdateCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// UpdatedAtColumn mocks base method. +func (m *MockProjectRepository) UpdatedAtColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdatedAtColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// UpdatedAtColumn indicates an expected call of UpdatedAtColumn. +func (mr *MockProjectRepositoryMockRecorder) UpdatedAtColumn() *MockProjectRepositoryUpdatedAtColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatedAtColumn", reflect.TypeOf((*MockProjectRepository)(nil).UpdatedAtColumn)) + return &MockProjectRepositoryUpdatedAtColumnCall{Call: call} +} + +// MockProjectRepositoryUpdatedAtColumnCall wrap *gomock.Call +type MockProjectRepositoryUpdatedAtColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryUpdatedAtColumnCall) Return(arg0 database.Column) *MockProjectRepositoryUpdatedAtColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryUpdatedAtColumnCall) Do(f func() database.Column) *MockProjectRepositoryUpdatedAtColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryUpdatedAtColumnCall) DoAndReturn(f func() database.Column) *MockProjectRepositoryUpdatedAtColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// UsedLabelingSettingOwnerColumn mocks base method. +func (m *MockProjectRepository) UsedLabelingSettingOwnerColumn() database.Column { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UsedLabelingSettingOwnerColumn") + ret0, _ := ret[0].(database.Column) + return ret0 +} + +// UsedLabelingSettingOwnerColumn indicates an expected call of UsedLabelingSettingOwnerColumn. +func (mr *MockProjectRepositoryMockRecorder) UsedLabelingSettingOwnerColumn() *MockProjectRepositoryUsedLabelingSettingOwnerColumnCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UsedLabelingSettingOwnerColumn", reflect.TypeOf((*MockProjectRepository)(nil).UsedLabelingSettingOwnerColumn)) + return &MockProjectRepositoryUsedLabelingSettingOwnerColumnCall{Call: call} +} + +// MockProjectRepositoryUsedLabelingSettingOwnerColumnCall wrap *gomock.Call +type MockProjectRepositoryUsedLabelingSettingOwnerColumnCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProjectRepositoryUsedLabelingSettingOwnerColumnCall) Return(arg0 database.Column) *MockProjectRepositoryUsedLabelingSettingOwnerColumnCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProjectRepositoryUsedLabelingSettingOwnerColumnCall) Do(f func() database.Column) *MockProjectRepositoryUsedLabelingSettingOwnerColumnCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProjectRepositoryUsedLabelingSettingOwnerColumnCall) DoAndReturn(f func() database.Column) *MockProjectRepositoryUsedLabelingSettingOwnerColumnCall { + c.Call = c.Call.DoAndReturn(f) + return c +} diff --git a/backend/v3/domain/mock/project_repo.go b/backend/v3/domain/mock/project_repo.go new file mode 100644 index 000000000..43bf6338f --- /dev/null +++ b/backend/v3/domain/mock/project_repo.go @@ -0,0 +1,47 @@ +package domainmock + +import ( + context "context" + + gomock "go.uber.org/mock/gomock" + + "github.com/zitadel/zitadel/backend/v3/domain" + database "github.com/zitadel/zitadel/backend/v3/storage/database" + "github.com/zitadel/zitadel/backend/v3/storage/database/repository" +) + +func NewProjectRepo(ctrl *gomock.Controller) *ProjectRepo { + return &ProjectRepo{ + mock: NewMockProjectRepository(ctrl), + ProjectRepository: repository.ProjectRepository(), + } +} + +type ProjectRepo struct { + domain.ProjectRepository + mock *MockProjectRepository +} + +func (r *ProjectRepo) EXPECT() *MockProjectRepositoryMockRecorder { + return r.mock.EXPECT() +} + +func (r *ProjectRepo) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.Project, error) { + return r.mock.Get(ctx, client, opts...) +} + +func (r *ProjectRepo) List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*domain.Project, error) { + return r.mock.List(ctx, client, opts...) +} + +func (r *ProjectRepo) Create(ctx context.Context, client database.QueryExecutor, org *domain.Project) error { + return r.mock.Create(ctx, client, org) +} + +func (r *ProjectRepo) Update(ctx context.Context, client database.QueryExecutor, conditions database.Condition, changes ...database.Change) (int64, error) { + return r.mock.Update(ctx, client, conditions, changes...) +} + +func (r *ProjectRepo) Delete(ctx context.Context, client database.QueryExecutor, conditions database.Condition) (int64, error) { + return r.mock.Delete(ctx, client, conditions) +} diff --git a/backend/v3/domain/mock/querier.mock.go b/backend/v3/domain/mock/querier.mock.go new file mode 100644 index 000000000..ec9a02613 --- /dev/null +++ b/backend/v3/domain/mock/querier.mock.go @@ -0,0 +1,194 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/zitadel/zitadel/backend/v3/domain (interfaces: Querier) +// +// Generated by this command: +// +// mockgen -typed -package domainmock -destination ./mock/querier.mock.go . Querier +// + +// Package domainmock is a generated GoMock package. +package domainmock + +import ( + context "context" + reflect "reflect" + + domain "github.com/zitadel/zitadel/backend/v3/domain" + gomock "go.uber.org/mock/gomock" +) + +// MockQuerier is a mock of Querier interface. +type MockQuerier[T any] struct { + ctrl *gomock.Controller + recorder *MockQuerierMockRecorder[T] + isgomock struct{} +} + +// MockQuerierMockRecorder is the mock recorder for MockQuerier. +type MockQuerierMockRecorder[T any] struct { + mock *MockQuerier[T] +} + +// NewMockQuerier creates a new mock instance. +func NewMockQuerier[T any](ctrl *gomock.Controller) *MockQuerier[T] { + mock := &MockQuerier[T]{ctrl: ctrl} + mock.recorder = &MockQuerierMockRecorder[T]{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockQuerier[T]) EXPECT() *MockQuerierMockRecorder[T] { + return m.recorder +} + +// Execute mocks base method. +func (m *MockQuerier[T]) Execute(ctx context.Context, opts *domain.InvokeOpts) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Execute", ctx, opts) + ret0, _ := ret[0].(error) + return ret0 +} + +// Execute indicates an expected call of Execute. +func (mr *MockQuerierMockRecorder[T]) Execute(ctx, opts any) *MockQuerierExecuteCall[T] { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockQuerier[T])(nil).Execute), ctx, opts) + return &MockQuerierExecuteCall[T]{Call: call} +} + +// MockQuerierExecuteCall wrap *gomock.Call +type MockQuerierExecuteCall[T any] struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockQuerierExecuteCall[T]) Return(err error) *MockQuerierExecuteCall[T] { + c.Call = c.Call.Return(err) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockQuerierExecuteCall[T]) Do(f func(context.Context, *domain.InvokeOpts) error) *MockQuerierExecuteCall[T] { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockQuerierExecuteCall[T]) DoAndReturn(f func(context.Context, *domain.InvokeOpts) error) *MockQuerierExecuteCall[T] { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Result mocks base method. +func (m *MockQuerier[T]) Result() T { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Result") + ret0, _ := ret[0].(T) + return ret0 +} + +// Result indicates an expected call of Result. +func (mr *MockQuerierMockRecorder[T]) Result() *MockQuerierResultCall[T] { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Result", reflect.TypeOf((*MockQuerier[T])(nil).Result)) + return &MockQuerierResultCall[T]{Call: call} +} + +// MockQuerierResultCall wrap *gomock.Call +type MockQuerierResultCall[T any] struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockQuerierResultCall[T]) Return(arg0 T) *MockQuerierResultCall[T] { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockQuerierResultCall[T]) Do(f func() T) *MockQuerierResultCall[T] { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockQuerierResultCall[T]) DoAndReturn(f func() T) *MockQuerierResultCall[T] { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// String mocks base method. +func (m *MockQuerier[T]) String() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "String") + ret0, _ := ret[0].(string) + return ret0 +} + +// String indicates an expected call of String. +func (mr *MockQuerierMockRecorder[T]) String() *MockQuerierStringCall[T] { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "String", reflect.TypeOf((*MockQuerier[T])(nil).String)) + return &MockQuerierStringCall[T]{Call: call} +} + +// MockQuerierStringCall wrap *gomock.Call +type MockQuerierStringCall[T any] struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockQuerierStringCall[T]) Return(arg0 string) *MockQuerierStringCall[T] { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockQuerierStringCall[T]) Do(f func() string) *MockQuerierStringCall[T] { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockQuerierStringCall[T]) DoAndReturn(f func() string) *MockQuerierStringCall[T] { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Validate mocks base method. +func (m *MockQuerier[T]) Validate(ctx context.Context, opts *domain.InvokeOpts) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Validate", ctx, opts) + ret0, _ := ret[0].(error) + return ret0 +} + +// Validate indicates an expected call of Validate. +func (mr *MockQuerierMockRecorder[T]) Validate(ctx, opts any) *MockQuerierValidateCall[T] { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockQuerier[T])(nil).Validate), ctx, opts) + return &MockQuerierValidateCall[T]{Call: call} +} + +// MockQuerierValidateCall wrap *gomock.Call +type MockQuerierValidateCall[T any] struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockQuerierValidateCall[T]) Return(err error) *MockQuerierValidateCall[T] { + c.Call = c.Call.Return(err) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockQuerierValidateCall[T]) Do(f func(context.Context, *domain.InvokeOpts) error) *MockQuerierValidateCall[T] { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockQuerierValidateCall[T]) DoAndReturn(f func(context.Context, *domain.InvokeOpts) error) *MockQuerierValidateCall[T] { + c.Call = c.Call.DoAndReturn(f) + return c +} diff --git a/backend/v3/domain/org_activate.go b/backend/v3/domain/org_activate.go new file mode 100644 index 000000000..6e75caf68 --- /dev/null +++ b/backend/v3/domain/org_activate.go @@ -0,0 +1,98 @@ +package domain + +import ( + "context" + "errors" + "strings" + + "github.com/zitadel/zitadel/backend/v3/storage/database" + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/repository/org" + "github.com/zitadel/zitadel/internal/zerrors" +) + +var _ Commander = (*ActivateOrgCommand)(nil) + +type ActivateOrgCommand struct { + ID string `json:"id"` +} + +func NewActivateOrgCommand(organizationID string) *ActivateOrgCommand { + return &ActivateOrgCommand{ID: organizationID} +} + +// Events implements [Commander]. +func (d *ActivateOrgCommand) Events(ctx context.Context, opts *InvokeOpts) ([]eventstore.Command, error) { + return []eventstore.Command{org.NewOrgReactivatedEvent(ctx, &org.NewAggregate(d.ID).Aggregate)}, nil +} + +// Execute implements [Commander]. +func (d *ActivateOrgCommand) Execute(ctx context.Context, opts *InvokeOpts) (err error) { + close, err := opts.EnsureTx(ctx) + if err != nil { + return err + } + defer func() { err = close(ctx, err) }() + + organizationRepo := opts.organizationRepo + + updateCount, err := organizationRepo.Update(ctx, pool, + database.And( + organizationRepo.IDCondition(d.ID), + organizationRepo.InstanceIDCondition(authz.GetInstance(ctx).InstanceID()), + ), + database.NewChange(organizationRepo.StateColumn(), OrgStateActive), + ) + + if err != nil { + return err + } + + if updateCount == 0 { + err = zerrors.ThrowNotFound(nil, "DOM-CGumXG", "Errors.Org.NotFound") + return err + } + if updateCount > 1 { + err = zerrors.ThrowInternal(NewMultipleObjectsUpdatedError(1, updateCount), "DOM-SEWCLp", "unexpected number of rows updated") + return err + } + + return nil +} + +// String implements [Commander]. +func (d *ActivateOrgCommand) String() string { + return "ActivateOrgCommand" +} + +// Validate implements [Commander]. +func (d *ActivateOrgCommand) Validate(ctx context.Context, opts *InvokeOpts) (err error) { + if d.ID = strings.TrimSpace(d.ID); d.ID == "" { + return zerrors.ThrowInvalidArgument(nil, "DOM-hJuuAv", "invalid organization ID") + } + + organizationRepo := opts.organizationRepo + + // TODO: lock entry as soon as https://github.com/zitadel/zitadel/issues/10930 is done + org, err := organizationRepo.Get(ctx, pool, database.WithCondition( + database.And( + organizationRepo.IDCondition(d.ID), + organizationRepo.InstanceIDCondition(authz.GetInstance(ctx).InstanceID()), + ), + )) + if err != nil { + var notFoundError *database.NoRowFoundError + if errors.As(err, ¬FoundError) { + err = zerrors.ThrowNotFound(err, "DOM-86HVfs", "Errors.Org.NotFound") + } + return err + } + + if org.State == OrgStateActive { + err = zerrors.ThrowPreconditionFailed(nil, "DOM-Ixfbxh", "Errors.Org.AlreadyActive") + return err + } + + return nil +} diff --git a/backend/v3/domain/org_activate_test.go b/backend/v3/domain/org_activate_test.go new file mode 100644 index 000000000..1b518b8da --- /dev/null +++ b/backend/v3/domain/org_activate_test.go @@ -0,0 +1,291 @@ +package domain_test + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + + "github.com/zitadel/zitadel/backend/v3/domain" + domainmock "github.com/zitadel/zitadel/backend/v3/domain/mock" + "github.com/zitadel/zitadel/backend/v3/storage/database" + "github.com/zitadel/zitadel/backend/v3/storage/database/dbmock" + noopdb "github.com/zitadel/zitadel/backend/v3/storage/database/dialect/noop" + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/repository/org" + "github.com/zitadel/zitadel/internal/zerrors" +) + +func TestActivateOrgCommand_Events(t *testing.T) { + t.Parallel() + // Given + expected := []eventstore.Command{org.NewOrgReactivatedEvent(context.Background(), &org.NewAggregate("some-id").Aggregate)} + activateCmd := domain.NewActivateOrgCommand("some-id") + + // Test + actual, err := activateCmd.Events(context.Background(), &domain.InvokeOpts{}) + + // Verify + + require.NoError(t, err) + assert.Equal(t, expected, actual) +} + +func TestActivateOrgCommand_Validate(t *testing.T) { + t.Parallel() + getErr := errors.New("get error") + + tt := []struct { + testName string + inputOrgID string + + orgRepo func(ctrl *gomock.Controller) domain.OrganizationRepository + + expectedError error + }{ + { + testName: "empty org id", + expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-hJuuAv", "invalid organization ID"), + }, + { + testName: "whitespace org id", + inputOrgID: " ", + expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-hJuuAv", "invalid organization ID"), + }, + { + testName: "when retrieving org fails with generic error should return error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions( + database.WithCondition( + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + ))). + Times(1). + Return(nil, getErr) + return repo + }, + inputOrgID: "org-1", + expectedError: getErr, + }, + { + testName: "when retrieving org fails with not found error should return not found error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions( + database.WithCondition( + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + ))). + Times(1). + Return(nil, database.NewNoRowFoundError(getErr)) + return repo + }, + inputOrgID: "org-1", + expectedError: zerrors.ThrowNotFound(database.NewNoRowFoundError(getErr), "DOM-86HVfs", "Errors.Org.NotFound"), + }, + { + testName: "when org state is active should return already active error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition( + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + ))). + Times(1). + Return(&domain.Organization{ + ID: "org-1", + State: domain.OrgStateActive, + }, nil) + return repo + }, + inputOrgID: "org-1", + expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-Ixfbxh", "Errors.Org.AlreadyActive"), + }, + { + testName: "when org state inactive should successfully validate organization", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition( + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + ))). + Times(1). + Return(&domain.Organization{ + ID: "org-1", + State: domain.OrgStateInactive, + }, nil) + return repo + }, + inputOrgID: "org-1", + }, + } + + for _, tc := range tt { + t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + // Given + cmd := domain.NewActivateOrgCommand(tc.inputOrgID) + ctx := authz.NewMockContext("instance-1", "", "") + ctrl := gomock.NewController(t) + opts := &domain.InvokeOpts{ + DB: new(noopdb.Pool), + } + if tc.orgRepo != nil { + domain.WithOrganizationRepo(tc.orgRepo(ctrl))(opts) + } + + // Test + err := cmd.Validate(ctx, opts) + + // Verify + assert.Equal(t, tc.expectedError, err) + }) + } +} + +func TestActivateOrgCommand_Execute(t *testing.T) { + t.Parallel() + txInitErr := errors.New("tx init error") + updateErr := errors.New("update error") + + tt := []struct { + testName string + + queryExecutor func(ctrl *gomock.Controller) database.QueryExecutor + orgRepo func(ctrl *gomock.Controller) domain.OrganizationRepository + + inputID string + + expectedError error + }{ + { + testName: "when EnsureTx fails should return error", + queryExecutor: func(ctrl *gomock.Controller) database.QueryExecutor { + mockDB := dbmock.NewMockPool(ctrl) + mockDB.EXPECT(). + Begin(gomock.Any(), gomock.Any()). + Times(1). + Return(nil, txInitErr) + return mockDB + }, + expectedError: txInitErr, + }, + { + testName: "when org update fails should return error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + Update(gomock.Any(), gomock.Any(), + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + repo.SetState(domain.OrgStateActive), + ). + Times(1). + Return(int64(0), updateErr) + return repo + }, + inputID: "org-1", + expectedError: updateErr, + }, + { + testName: "when org update returns 0 rows updated should return not found error", + inputID: "org-1", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + Update(gomock.Any(), gomock.Any(), + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + repo.SetState(domain.OrgStateActive)). + Times(1). + Return(int64(0), nil) + return repo + }, + expectedError: zerrors.ThrowNotFound(nil, "DOM-CGumXG", "Errors.Org.NotFound"), + }, + { + testName: "when org update returns more than 1 row updated should return internal error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + Update(gomock.Any(), gomock.Any(), + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + repo.SetState(domain.OrgStateActive)). + Times(1). + Return(int64(2), nil) + return repo + }, + inputID: "org-1", + expectedError: zerrors.ThrowInternal(domain.NewMultipleObjectsUpdatedError(1, 2), "DOM-SEWCLp", "unexpected number of rows updated"), + }, + { + testName: "when org update returns 1 row updated should return no error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + Update(gomock.Any(), gomock.Any(), + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + repo.SetState(domain.OrgStateActive)). + Times(1). + Return(int64(1), nil) + return repo + }, + inputID: "org-1", + }, + } + + for _, tc := range tt { + t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + // Given + ctx := authz.NewMockContext("instance-1", "", "") + ctrl := gomock.NewController(t) + cmd := &domain.ActivateOrgCommand{ + ID: tc.inputID, + } + + opts := &domain.InvokeOpts{ + DB: new(noopdb.Pool), + } + if tc.orgRepo != nil { + domain.WithOrganizationRepo(tc.orgRepo(ctrl))(opts) + } + if tc.queryExecutor != nil { + opts.DB = tc.queryExecutor(ctrl) + } + + // Test + err := cmd.Execute(ctx, opts) + + // Verify + assert.Equal(t, tc.expectedError, err) + }) + } +} diff --git a/backend/v3/domain/org_deactivate.go b/backend/v3/domain/org_deactivate.go new file mode 100644 index 000000000..519ecca35 --- /dev/null +++ b/backend/v3/domain/org_deactivate.go @@ -0,0 +1,96 @@ +package domain + +import ( + "context" + "errors" + "strings" + + "github.com/zitadel/zitadel/backend/v3/storage/database" + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/repository/org" + "github.com/zitadel/zitadel/internal/zerrors" +) + +var _ Commander = (*DeactivateOrgCommand)(nil) + +type DeactivateOrgCommand struct { + ID string `json:"id"` +} + +func NewDeactivateOrgCommand(organizationID string) *DeactivateOrgCommand { + return &DeactivateOrgCommand{ID: organizationID} +} + +// Events implements [Commander]. +func (d *DeactivateOrgCommand) Events(ctx context.Context, opts *InvokeOpts) ([]eventstore.Command, error) { + return []eventstore.Command{org.NewOrgDeactivatedEvent(ctx, &org.NewAggregate(d.ID).Aggregate)}, nil +} + +// Execute implements [Commander]. +func (d *DeactivateOrgCommand) Execute(ctx context.Context, opts *InvokeOpts) (err error) { + close, err := opts.EnsureTx(ctx) + if err != nil { + return err + } + defer func() { err = close(ctx, err) }() + + organizationRepo := opts.organizationRepo + + updateCount, err := organizationRepo.Update(ctx, pool, + database.And( + organizationRepo.IDCondition(d.ID), + organizationRepo.InstanceIDCondition(authz.GetInstance(ctx).InstanceID()), + ), + database.NewChange(organizationRepo.StateColumn(), OrgStateInactive), + ) + + if err != nil { + return err + } + + if updateCount == 0 { + err = zerrors.ThrowNotFound(nil, "DOM-vWPy7D", "Errors.Org.NotFound") + return err + } + if updateCount > 1 { + err = zerrors.ThrowInternal(NewMultipleObjectsUpdatedError(1, updateCount), "DOM-dXl1kJ", "unexpected number of rows updated") + return err + } + + return nil +} + +// String implements [Commander]. +func (d *DeactivateOrgCommand) String() string { + return "DeactivateOrgCommand" +} + +// Validate implements [Commander]. +func (d *DeactivateOrgCommand) Validate(ctx context.Context, opts *InvokeOpts) (err error) { + if d.ID = strings.TrimSpace(d.ID); d.ID == "" { + return zerrors.ThrowInvalidArgument(nil, "DOM-Qc3T1r", "invalid organization ID") + } + + organizationRepo := opts.organizationRepo + + org, err := organizationRepo.Get(ctx, pool, database.WithCondition( + database.And( + organizationRepo.IDCondition(d.ID), + organizationRepo.InstanceIDCondition(authz.GetInstance(ctx).InstanceID()), + ), + )) + if err != nil { + var notFoundError *database.NoRowFoundError + if errors.As(err, ¬FoundError) { + err = zerrors.ThrowNotFound(err, "DOM-QEjfpz", "Errors.Org.NotFound") + } + return err + } + + if org.State == OrgStateInactive { + err = zerrors.ThrowPreconditionFailed(nil, "DOM-Z2dzsT", "Errors.Org.AlreadyDeactivated") + return err + } + return nil +} diff --git a/backend/v3/domain/org_deactivate_test.go b/backend/v3/domain/org_deactivate_test.go new file mode 100644 index 000000000..5998e07f1 --- /dev/null +++ b/backend/v3/domain/org_deactivate_test.go @@ -0,0 +1,270 @@ +package domain_test + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + + "github.com/zitadel/zitadel/backend/v3/domain" + domainmock "github.com/zitadel/zitadel/backend/v3/domain/mock" + "github.com/zitadel/zitadel/backend/v3/storage/database" + "github.com/zitadel/zitadel/backend/v3/storage/database/dbmock" + noopdb "github.com/zitadel/zitadel/backend/v3/storage/database/dialect/noop" + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/repository/org" + "github.com/zitadel/zitadel/internal/zerrors" +) + +func TestDeactivateOrgCommand_Events(t *testing.T) { + t.Parallel() + // Given + expected := []eventstore.Command{org.NewOrgDeactivatedEvent(context.Background(), &org.NewAggregate("some-id").Aggregate)} + deactivateCmd := domain.NewDeactivateOrgCommand("some-id") + + // Test + actual, err := deactivateCmd.Events(context.Background(), &domain.InvokeOpts{}) + + // Verify + + require.NoError(t, err) + assert.Equal(t, expected, actual) +} + +func TestDeactivateOrgCommand_Validate(t *testing.T) { + t.Parallel() + getErr := errors.New("get error") + + tt := []struct { + testName string + orgRepo func(ctrl *gomock.Controller) domain.OrganizationRepository + + inputOrgID string + expectedError error + }{ + { + testName: "empty org id", + expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-Qc3T1r", "invalid organization ID"), + }, + { + testName: "whitespace org id", + inputOrgID: " ", + expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-Qc3T1r", "invalid organization ID"), + }, + { + testName: "when retrieving org fails with generic error should return error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions( + database.WithCondition( + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + ))). + Times(1). + Return(nil, getErr) + return repo + }, + inputOrgID: "org-1", + expectedError: getErr, + }, + { + testName: "when retrieving org fails with not found error should return not found error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions( + database.WithCondition( + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + ))). + Times(1). + Return(nil, database.NewNoRowFoundError(getErr)) + return repo + }, + inputOrgID: "org-1", + expectedError: zerrors.ThrowNotFound(database.NewNoRowFoundError(getErr), "DOM-QEjfpz", "Errors.Org.NotFound"), + }, + { + testName: "when org state is inactive should return already inactive error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition( + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + ))). + Times(1). + Return(&domain.Organization{ + ID: "org-1", + State: domain.OrgStateInactive, + }, nil) + return repo + }, + inputOrgID: "org-1", + expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-Z2dzsT", "Errors.Org.AlreadyDeactivated"), + }, + } + + for _, tc := range tt { + t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + ctx := authz.NewMockContext("instance-1", "", "") + ctrl := gomock.NewController(t) + cmd := domain.NewDeactivateOrgCommand(tc.inputOrgID) + + opts := &domain.InvokeOpts{ + DB: new(noopdb.Pool), + } + if tc.orgRepo != nil { + domain.WithOrganizationRepo(tc.orgRepo(ctrl))(opts) + } + + // Test + err := cmd.Validate(ctx, opts) + + // Verify + assert.Equal(t, tc.expectedError, err) + }) + } +} + +func TestDeactivateOrgCommand_Execute(t *testing.T) { + t.Parallel() + txInitErr := errors.New("tx init error") + updateErr := errors.New("update error") + + tt := []struct { + testName string + + queryExecutor func(ctrl *gomock.Controller) database.QueryExecutor + orgRepo func(ctrl *gomock.Controller) domain.OrganizationRepository + + inputID string + + expectedError error + }{ + { + testName: "when EnsureTx fails should return error", + queryExecutor: func(ctrl *gomock.Controller) database.QueryExecutor { + mockDB := dbmock.NewMockPool(ctrl) + mockDB.EXPECT(). + Begin(gomock.Any(), gomock.Any()). + Times(1). + Return(nil, txInitErr) + return mockDB + }, + expectedError: txInitErr, + }, + { + testName: "when org update fails should return error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + Update(gomock.Any(), gomock.Any(), + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + repo.SetState(domain.OrgStateInactive), + ). + Times(1). + Return(int64(0), updateErr) + return repo + }, + inputID: "org-1", + expectedError: updateErr, + }, + { + testName: "when org update returns 0 rows updated should return not found error", + inputID: "org-1", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + Update(gomock.Any(), gomock.Any(), + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + repo.SetState(domain.OrgStateInactive)). + Times(1). + Return(int64(0), nil) + return repo + }, + expectedError: zerrors.ThrowNotFound(nil, "DOM-vWPy7D", "Errors.Org.NotFound"), + }, + { + testName: "when org update returns more than 1 row updated should return internal error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + Update(gomock.Any(), gomock.Any(), + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + repo.SetState(domain.OrgStateInactive)). + Times(1). + Return(int64(2), nil) + return repo + }, + inputID: "org-1", + expectedError: zerrors.ThrowInternal(domain.NewMultipleObjectsUpdatedError(1, 2), "DOM-dXl1kJ", "unexpected number of rows updated"), + }, + { + testName: "when org update returns 1 row updated should return no error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + Update(gomock.Any(), gomock.Any(), + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + repo.SetState(domain.OrgStateInactive)). + Times(1). + Return(int64(1), nil) + return repo + }, + inputID: "org-1", + }, + } + + for _, tc := range tt { + t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + // Given + ctx := authz.NewMockContext("instance-1", "", "") + ctrl := gomock.NewController(t) + cmd := &domain.DeactivateOrgCommand{ + ID: tc.inputID, + } + + opts := &domain.InvokeOpts{ + DB: new(noopdb.Pool), + } + if tc.orgRepo != nil { + domain.WithOrganizationRepo(tc.orgRepo(ctrl))(opts) + } + if tc.queryExecutor != nil { + opts.DB = tc.queryExecutor(ctrl) + } + + // Test + err := cmd.Execute(ctx, opts) + + // Verify + assert.Equal(t, tc.expectedError, err) + }) + } +} diff --git a/backend/v3/domain/org_delete.go b/backend/v3/domain/org_delete.go new file mode 100644 index 000000000..d347cc9bc --- /dev/null +++ b/backend/v3/domain/org_delete.go @@ -0,0 +1,192 @@ +package domain + +import ( + "context" + "errors" + "strings" + + "github.com/zitadel/zitadel/backend/v3/storage/database" + "github.com/zitadel/zitadel/internal/api/authz" + internal_domain "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/repository/org" + "github.com/zitadel/zitadel/internal/zerrors" +) + +type DeleteOrgCommand struct { + OrganizationName string `json:"organization_name"` + Domains []*OrganizationDomain + ID string `json:"id"` +} + +func NewDeleteOrgCommand(organizationID string) *DeleteOrgCommand { + return &DeleteOrgCommand{ID: organizationID} +} + +// Events implements [Commander]. +// +// TODO(IAM-Marco): Finish implementation when policies, org settings, idp links and entities repositories +// are implemented +func (d *DeleteOrgCommand) Events(ctx context.Context, opts *InvokeOpts) ([]eventstore.Command, error) { + closeFunc, err := opts.EnsureTx(ctx) + if err != nil { + return nil, err + } + defer func() { err = closeFunc(ctx, err) }() + + usernames := []string{} + // userRepo := opts.usersRepo(pool) + // users, err := userRepo.List(ctx, database.WithCondition(opts.organizationRepo(pool).IDCondition(d.ID))) + // if err != nil { + // return nil, err + // } + // for _, u := range users { + // usernames = append(usernames, u.UserID) + // } + + // domainPolicyRepo := opts.domainPolicyRepo(pool) + // policy, err := domainPolicyRepo.Get(ctx, instanceID, d.ID) + // if err != nil { + // return nil, err + // } + + // orgSettingsRepo := opts.organizationSettingsRepo(pool) + // orgSettings, err := orgSettingsRepo.Get(ctx, d.ID) + // if err != nil { + // return nil, err + // } + // areUsernamesOrganizationScoped := policy.UserLoginMustBeDomain || orgSettings.UsernamesUnique + areUsernamesOrganizationScoped := false + + domainNames := make([]string, len(d.Domains)) + for i, domain := range d.Domains { + domainNames[i] = domain.Domain + } + + externalIDPLinks := []*internal_domain.UserIDPLink{} + // idpLinksRepo := opts.idpLinksRepo(pool) + // idpLinks, err := idpLinksRepo.List(ctx, d.ID) + // if err != nil { + // return nil, err + // } + // for _, link := range idpLinks { + // // Convert repo to internal_domain + // externalIDPLinks = append(externalIDPLinks, link) + // } + + samlEntityIDs := []string{} + // entityIDsRepo := opts.entityIDsRepo(pool) + // entityIDs, err := entityIDsRepo.List(ctx, d.ID) + // if err != nil { + // return nil, err + // } + // for _, ei := range entityIDs { + // samlEntityIDs = append(samlEntityIDs, ei.ID) + // } + + return []eventstore.Command{ + org.NewOrgRemovedEvent( + ctx, + &org.NewAggregate(d.ID).Aggregate, + d.OrganizationName, + usernames, + areUsernamesOrganizationScoped, + domainNames, + externalIDPLinks, + samlEntityIDs, + ), + }, nil +} + +// Execute implements [Commander]. +func (d *DeleteOrgCommand) Execute(ctx context.Context, opts *InvokeOpts) (err error) { + closeFunc, err := opts.EnsureTx(ctx) + if err != nil { + return err + } + + defer func() { err = closeFunc(ctx, err) }() + instance := authz.GetInstance(ctx) + + orgRepo := opts.organizationRepo.LoadDomains() + + orgToDelete, err := orgRepo.Get(ctx, pool, database.WithCondition(database.And( + orgRepo.IDCondition(d.ID), + orgRepo.InstanceIDCondition(instance.InstanceID()), + ))) + if err != nil { + return err + } + d.OrganizationName = orgToDelete.Name + d.Domains = orgToDelete.Domains + + deletedRows, err := orgRepo.Delete(ctx, pool, + database.And( + orgRepo.IDCondition(d.ID), + orgRepo.InstanceIDCondition(authz.GetInstance(ctx).InstanceID()), + ), + ) + if err != nil { + return err + } + + if deletedRows > 1 { + err = zerrors.ThrowInternalf(nil, "DOM-5cE9u6", "expecting 1 row deleted, got %d", deletedRows) + return err + } + + if deletedRows < 1 { + err = zerrors.ThrowNotFound(nil, "DOM-ur6Qyv", "organization not found") + } + return err +} + +// String implements [Commander]. +func (d *DeleteOrgCommand) String() string { + return "DeleteOrgCommand" +} + +// Validate implements [Commander]. +func (d *DeleteOrgCommand) Validate(ctx context.Context, opts *InvokeOpts) (err error) { + instance := authz.GetInstance(ctx) + + d.ID = strings.TrimSpace(d.ID) + if d.ID == instance.DefaultOrganisationID() { + return zerrors.ThrowPreconditionFailed(nil, "DOM-LCkE69", "Errors.Org.DefaultOrgNotDeletable") + } + + // Check if the ZITADEL project exists on the input organization + projectRepo := opts.projectRepo + _, getErr := projectRepo.Get(ctx, pool, + database.WithCondition(database.And( + projectRepo.IDCondition(instance.ProjectID()), + projectRepo.OrganizationIDCondition(d.ID), + projectRepo.InstanceIDCondition(instance.InstanceID()), + )), + ) + if getErr == nil { + return zerrors.ThrowPreconditionFailed(nil, "DOM-X7YXxC", "Errors.Org.ZitadelOrgNotDeletable") + } + // [database.NoRowFoundError] error means the project does not exist, return other errors in case it's not that + if !errors.Is(getErr, &database.NoRowFoundError{}) { + err = getErr + return err + } + + orgRepo := opts.organizationRepo + _, errGetOrg := orgRepo.Get(ctx, pool, + database.WithCondition(database.And( + orgRepo.IDCondition(d.ID), + orgRepo.InstanceIDCondition(instance.InstanceID()), + ))) + if errGetOrg != nil { + if errors.Is(errGetOrg, &database.NoRowFoundError{}) { + err = zerrors.ThrowNotFound(errGetOrg, "DOM-8KYOH3", "Errors.Org.NotFound") + } + return err + } + + return err +} + +var _ Commander = (*DeleteOrgCommand)(nil) diff --git a/backend/v3/domain/org_delete_test.go b/backend/v3/domain/org_delete_test.go new file mode 100644 index 000000000..796c7a325 --- /dev/null +++ b/backend/v3/domain/org_delete_test.go @@ -0,0 +1,503 @@ +package domain_test + +import ( + "errors" + "testing" + + "github.com/brianvoe/gofakeit/v6" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + + "github.com/zitadel/zitadel/backend/v3/domain" + domainmock "github.com/zitadel/zitadel/backend/v3/domain/mock" + "github.com/zitadel/zitadel/backend/v3/storage/database" + "github.com/zitadel/zitadel/backend/v3/storage/database/dbmock" + noopdb "github.com/zitadel/zitadel/backend/v3/storage/database/dialect/noop" + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/zerrors" +) + +func TestDeleteOrgCommand_Validate(t *testing.T) { + t.Parallel() + ctx := authz.NewMockContext("inst-1", "org-default", gofakeit.UUID(), authz.WithMockProjectID("prj-1")) + getErr := errors.New("get error") + + tt := []struct { + testName string + orgRepo func(ctrl *gomock.Controller) domain.OrganizationRepository + projectRepo func(ctrl *gomock.Controller) domain.ProjectRepository + inputOrganizationID string + expectedError error + }{ + { + testName: "validate delete default org, precondition failed", + + inputOrganizationID: "org-default", + expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-LCkE69", "Errors.Org.DefaultOrgNotDeletable"), + }, + { + testName: "when fetching project fails with NON precondition error should return error", + inputOrganizationID: "org-1", + projectRepo: func(ctrl *gomock.Controller) domain.ProjectRepository { + repo := domainmock.NewProjectRepo(ctrl) + + repo.EXPECT().Get(gomock.Any(), gomock.Any(), + dbmock.QueryOptions(database.WithCondition( + database.And( + repo.IDCondition("prj-1"), + repo.OrganizationIDCondition("org-1"), + repo.InstanceIDCondition("inst-1"), + ), + ))). + Times(1). + Return(nil, getErr) + + return repo + }, + expectedError: getErr, + }, + { + testName: "when fetching project succeeds should return precondition failed error", + inputOrganizationID: "org-1", + projectRepo: func(ctrl *gomock.Controller) domain.ProjectRepository { + repo := domainmock.NewProjectRepo(ctrl) + + repo.EXPECT().Get(gomock.Any(), gomock.Any(), + dbmock.QueryOptions(database.WithCondition( + database.And( + repo.IDCondition("prj-1"), + repo.OrganizationIDCondition("org-1"), + repo.InstanceIDCondition("inst-1"), + ), + ))). + Times(1). + Return(&domain.Project{}, nil) + + return repo + }, + expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-X7YXxC", "Errors.Org.ZitadelOrgNotDeletable"), + }, + { + testName: "when fetching organization fails with not found error should return not found error", + projectRepo: func(ctrl *gomock.Controller) domain.ProjectRepository { + repo := domainmock.NewProjectRepo(ctrl) + + repo.EXPECT().Get(gomock.Any(), gomock.Any(), + dbmock.QueryOptions(database.WithCondition( + database.And( + repo.IDCondition("prj-1"), + repo.OrganizationIDCondition("org-1"), + repo.InstanceIDCondition("inst-1"), + ), + ))). + Times(1). + Return(nil, database.NewNoRowFoundError(getErr)) + + return repo + }, + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition( + database.And(repo.IDCondition("org-1"), repo.InstanceIDCondition("inst-1")), + ))). + Times(1). + Return(nil, database.NewNoRowFoundError(getErr)) + return repo + }, + inputOrganizationID: "org-1", + expectedError: zerrors.ThrowNotFound(database.NewNoRowFoundError(getErr), "DOM-8KYOH3", "Errors.Org.NotFound"), + }, + { + testName: "when organization is active should validate successfully", + projectRepo: func(ctrl *gomock.Controller) domain.ProjectRepository { + repo := domainmock.NewProjectRepo(ctrl) + + repo.EXPECT().Get(gomock.Any(), gomock.Any(), + dbmock.QueryOptions(database.WithCondition( + database.And( + repo.IDCondition("prj-1"), + repo.OrganizationIDCondition("org-1"), + repo.InstanceIDCondition("inst-1"), + ), + ))). + Times(1). + Return(nil, database.NewNoRowFoundError(getErr)) + + return repo + }, + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition( + database.And(repo.IDCondition("org-1"), repo.InstanceIDCondition("inst-1")), + ))). + Times(1). + Return(&domain.Organization{ + ID: "org-1", + State: domain.OrgStateActive, + }, nil) + return repo + }, + inputOrganizationID: "org-1", + }, + { + testName: "when organization is inactive should validate successfully", + projectRepo: func(ctrl *gomock.Controller) domain.ProjectRepository { + repo := domainmock.NewProjectRepo(ctrl) + + repo.EXPECT().Get(gomock.Any(), gomock.Any(), + dbmock.QueryOptions(database.WithCondition( + database.And( + repo.IDCondition("prj-1"), + repo.OrganizationIDCondition("org-1"), + repo.InstanceIDCondition("inst-1"), + ), + ))). + Times(1). + Return(nil, database.NewNoRowFoundError(getErr)) + + return repo + }, + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition( + database.And(repo.IDCondition("org-1"), repo.InstanceIDCondition("inst-1")), + ))). + Times(1). + Return(&domain.Organization{ + ID: "org-1", + State: domain.OrgStateInactive, + }, nil) + return repo + }, + inputOrganizationID: "org-1", + }, + } + for _, tc := range tt { + t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + // Given + d := domain.NewDeleteOrgCommand(tc.inputOrganizationID) + ctrl := gomock.NewController(t) + opts := &domain.InvokeOpts{DB: new(noopdb.Pool)} + + if tc.orgRepo != nil { + domain.WithOrganizationRepo(tc.orgRepo(ctrl))(opts) + } + if tc.projectRepo != nil { + domain.WithProjectRepo(tc.projectRepo(ctrl))(opts) + } + + // Test + err := d.Validate(ctx, opts) + + // Verify + assert.Equal(t, tc.expectedError, err) + }) + } +} + +func TestDeleteOrgCommand_Execute(t *testing.T) { + t.Parallel() + + ctx := authz.NewMockContext("inst-1", "org-1", gofakeit.UUID()) + txInitErr := errors.New("tx init error") + deleteErr := errors.New("delete error") + getErr := errors.New("get error") + + tt := []struct { + testName string + mockTx func(ctrl *gomock.Controller) database.QueryExecutor + orgRepo func(ctrl *gomock.Controller) domain.OrganizationRepository + + inputOrganizationID string + + expectedError error + expectedOrgName string + }{ + { + testName: "when EnsureTx fails should return error", + mockTx: func(ctrl *gomock.Controller) database.QueryExecutor { + mockDB := dbmock.NewMockPool(ctrl) + mockDB.EXPECT(). + Begin(gomock.Any(), gomock.Any()). + Times(1). + Return(nil, txInitErr) + return mockDB + }, + expectedError: txInitErr, + }, + { + testName: "when retrieving organization fails should return error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + + repo.EXPECT(). + LoadDomains(). + Times(1). + Return(repo) + + repo.EXPECT(). + Get( + gomock.Any(), + gomock.Any(), + dbmock.QueryOptions(database.WithCondition( + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("inst-1"), + ), + )), + ). + Times(1). + Return(nil, getErr) + return repo + }, + inputOrganizationID: "org-1", + expectedError: getErr, + }, + { + testName: "when delete organization fails should return error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + + repo.EXPECT(). + LoadDomains(). + Times(1). + Return(repo) + + repo.EXPECT(). + Get( + gomock.Any(), + gomock.Any(), + dbmock.QueryOptions(database.WithCondition( + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("inst-1"), + ), + )), + ). + Times(1). + Return(&domain.Organization{ + ID: "org-1", + Name: "organization 1", + }, nil) + repo.EXPECT(). + Delete(gomock.Any(), gomock.Any(), + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("inst-1")), + ). + Times(1). + Return(int64(0), deleteErr) + return repo + }, + inputOrganizationID: "org-1", + expectedError: deleteErr, + expectedOrgName: "organization 1", + }, + { + testName: "when more than one row deleted should return internal error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + + repo.EXPECT(). + LoadDomains(). + Times(1). + Return(repo) + + repo.EXPECT(). + Get( + gomock.Any(), + gomock.Any(), + dbmock.QueryOptions(database.WithCondition( + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("inst-1"), + ), + )), + ). + Times(1). + Return(&domain.Organization{ + ID: "org-1", + Name: "organization 1", + }, nil) + repo.EXPECT(). + Delete(gomock.Any(), gomock.Any(), database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("inst-1")), + ). + Times(1). + Return(int64(2), nil) + return repo + }, + inputOrganizationID: "org-1", + expectedError: zerrors.ThrowInternalf(nil, "DOM-5cE9u6", "expecting 1 row deleted, got %d", 2), + expectedOrgName: "organization 1", + }, + { + testName: "when no rows deleted should return not found error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + + repo.EXPECT(). + LoadDomains(). + Times(1). + Return(repo) + + repo.EXPECT(). + Get( + gomock.Any(), + gomock.Any(), + dbmock.QueryOptions(database.WithCondition( + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("inst-1"), + ), + )), + ). + Times(1). + Return(&domain.Organization{ + ID: "org-1", + Name: "organization 1", + }, nil) + repo.EXPECT(). + Delete(gomock.Any(), gomock.Any(), database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("inst-1")), + ). + Times(1). + Return(int64(0), nil) + return repo + }, + inputOrganizationID: "org-1", + expectedError: zerrors.ThrowNotFoundf(nil, "DOM-ur6Qyv", "organization not found"), + expectedOrgName: "organization 1", + }, + { + testName: "when one row deleted should execute successfully", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + + repo.EXPECT(). + LoadDomains(). + Times(1). + Return(repo) + + repo.EXPECT(). + Get( + gomock.Any(), + gomock.Any(), + dbmock.QueryOptions(database.WithCondition( + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("inst-1"), + ), + )), + ). + Times(1). + Return(&domain.Organization{ + ID: "org-1", + Name: "organization 1", + }, nil) + repo.EXPECT(). + Delete(gomock.Any(), gomock.Any(), database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("inst-1")), + ). + Times(1). + Return(int64(1), nil) + return repo + }, + inputOrganizationID: "org-1", + expectedOrgName: "organization 1", + }, + } + for _, tc := range tt { + t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + // Given + d := domain.NewDeleteOrgCommand(tc.inputOrganizationID) + ctrl := gomock.NewController(t) + opts := &domain.InvokeOpts{DB: new(noopdb.Pool)} + + if tc.mockTx != nil { + opts.DB = tc.mockTx(ctrl) + } + if tc.orgRepo != nil { + domain.WithOrganizationRepo(tc.orgRepo(ctrl))(opts) + } + + // Test + err := d.Execute(ctx, opts) + + // Verify + assert.Equal(t, tc.expectedError, err) + assert.Equal(t, tc.expectedOrgName, d.OrganizationName) + }) + } +} + +// TODO(IAM-Marco): Expand these tests once the needed repositories (policies, org settings, idp links and entities) are available +func TestDeleteOrgCommand_Events(t *testing.T) { + t.Parallel() + ctx := authz.NewMockContext("inst-1", "org-1", gofakeit.UUID()) + txInitErr := errors.New("tx init error") + + tt := []struct { + testName string + mockTx func(ctrl *gomock.Controller) database.QueryExecutor + command *domain.DeleteOrgCommand + expectedError error + expectedCount int + }{ + { + testName: "when EnsureTx fails should return error", + mockTx: func(ctrl *gomock.Controller) database.QueryExecutor { + mockDB := dbmock.NewMockPool(ctrl) + mockDB.EXPECT(). + Begin(gomock.Any(), gomock.Any()). + Times(1). + Return(nil, txInitErr) + return mockDB + }, + command: domain.NewDeleteOrgCommand("org-1"), + expectedError: txInitErr, + }, + { + testName: "should create org removed event", + command: &domain.DeleteOrgCommand{ + ID: "org-1", + OrganizationName: "org name", + Domains: []*domain.OrganizationDomain{ + {Domain: "domain1.com"}, + {Domain: "domain2.com"}, + }, + }, + expectedCount: 1, + }, + } + + for _, tc := range tt { + t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + + // Given + ctrl := gomock.NewController(t) + opts := &domain.InvokeOpts{DB: new(noopdb.Pool)} + + if tc.mockTx != nil { + opts.DB = tc.mockTx(ctrl) + } + + // Test + cmds, err := tc.command.Events(ctx, opts) + + // Verify + require.Equal(t, tc.expectedError, err) + assert.Len(t, cmds, tc.expectedCount) + }) + } +} diff --git a/backend/v3/domain/org_domain_add.go b/backend/v3/domain/org_domain_add.go new file mode 100644 index 000000000..508e75c3d --- /dev/null +++ b/backend/v3/domain/org_domain_add.go @@ -0,0 +1,47 @@ +package domain + +import ( + "context" + + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/repository/org" +) + +type AddOrgDomainCommand struct { + OrgID string `json:"org_id"` + Name string `json:"name"` + // ClaimedUserIDs []string `json:"claimed_user_ids"` +} + +// Events implements Commander. +func (a *AddOrgDomainCommand) Events(ctx context.Context, opts *InvokeOpts) ([]eventstore.Command, error) { + // TODO(IAM-Marco) Finish implementation in https://github.com/zitadel/zitadel/issues/10447 + return []eventstore.Command{ + org.NewDomainAddedEvent(ctx, &org.NewAggregate(a.OrgID).Aggregate, a.Name), + org.NewDomainVerifiedEvent(ctx, &org.NewAggregate(a.OrgID).Aggregate, a.Name), + }, nil +} + +func NewAddOrgDomainCommand(orgID, domainName string) *AddOrgDomainCommand { + return &AddOrgDomainCommand{ + OrgID: orgID, + Name: domainName, + } +} + +// Execute implements Commander. +func (a *AddOrgDomainCommand) Execute(ctx context.Context, opts *InvokeOpts) (err error) { + return nil +} + +// String implements Commander. +func (a *AddOrgDomainCommand) String() string { + return "AddOrgDomainCommand" +} + +// Validate implements Commander. +func (a *AddOrgDomainCommand) Validate(ctx context.Context, opts *InvokeOpts) (err error) { + return nil +} + +var _ Commander = (*AddOrgDomainCommand)(nil) diff --git a/backend/v3/domain/org_domain_remove.go b/backend/v3/domain/org_domain_remove.go new file mode 100644 index 000000000..e19bd35b3 --- /dev/null +++ b/backend/v3/domain/org_domain_remove.go @@ -0,0 +1,56 @@ +package domain + +import ( + "context" + + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/repository/org" +) + +type RemoveOrgDomainCommand struct { + OrgID string `json:"org_id"` + Name *string `json:"name"` + IsVerified *bool `json:"is_verified"` +} + +// Events implements Commander. +func (r *RemoveOrgDomainCommand) Events(ctx context.Context, opts *InvokeOpts) ([]eventstore.Command, error) { + // TODO(IAM-Marco) Finish implementation in https://github.com/zitadel/zitadel/issues/10447 + oldDomainName := "" + isVerified := false + if r.Name != nil { + oldDomainName = *r.Name + } + if r.IsVerified != nil { + isVerified = *r.IsVerified + } + + return []eventstore.Command{ + org.NewDomainRemovedEvent(ctx, &org.NewAggregate(r.OrgID).Aggregate, oldDomainName, isVerified), + }, nil +} + +func NewRemoveOrgDomainCommand(orgID string, domainName *string, isDomainVerified *bool) *RemoveOrgDomainCommand { + return &RemoveOrgDomainCommand{ + OrgID: orgID, + Name: domainName, + IsVerified: isDomainVerified, + } +} + +// Execute implements Commander. +func (r *RemoveOrgDomainCommand) Execute(ctx context.Context, opts *InvokeOpts) (err error) { + return nil +} + +// String implements Commander. +func (r *RemoveOrgDomainCommand) String() string { + return "RemoveOrgDomainCommand" +} + +// Validate implements Commander. +func (r *RemoveOrgDomainCommand) Validate(ctx context.Context, opts *InvokeOpts) (err error) { + return nil +} + +var _ Commander = (*RemoveOrgDomainCommand)(nil) diff --git a/backend/v3/domain/org_domain_set.go b/backend/v3/domain/org_domain_set.go new file mode 100644 index 000000000..3520ba44b --- /dev/null +++ b/backend/v3/domain/org_domain_set.go @@ -0,0 +1,45 @@ +package domain + +import ( + "context" + + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/repository/org" +) + +type SetPrimaryOrgDomainCommand struct { + OrgID string `json:"org_id"` + Name string `json:"name"` +} + +// Events implements Commander. +func (a *SetPrimaryOrgDomainCommand) Events(ctx context.Context, opts *InvokeOpts) ([]eventstore.Command, error) { + // TODO(IAM-Marco) Finish implementation in https://github.com/zitadel/zitadel/issues/10447 + return []eventstore.Command{ + org.NewDomainPrimarySetEvent(ctx, &org.NewAggregate(a.OrgID).Aggregate, a.Name), + }, nil +} + +func NewSetPrimaryOrgDomainCommand(orgID, domainName string) *SetPrimaryOrgDomainCommand { + return &SetPrimaryOrgDomainCommand{ + OrgID: orgID, + Name: domainName, + } +} + +// Execute implements Commander. +func (a *SetPrimaryOrgDomainCommand) Execute(ctx context.Context, opts *InvokeOpts) (err error) { + return nil +} + +// String implements Commander. +func (a *SetPrimaryOrgDomainCommand) String() string { + return "SetPrimaryOrgDomainCommand" +} + +// Validate implements Commander. +func (a *SetPrimaryOrgDomainCommand) Validate(ctx context.Context, opts *InvokeOpts) (err error) { + return nil +} + +var _ Commander = (*SetPrimaryOrgDomainCommand)(nil) diff --git a/backend/v3/domain/org_list.go b/backend/v3/domain/org_list.go new file mode 100644 index 000000000..080d90097 --- /dev/null +++ b/backend/v3/domain/org_list.go @@ -0,0 +1,120 @@ +package domain + +import ( + "context" + + "github.com/zitadel/zitadel/backend/v3/api/object" + "github.com/zitadel/zitadel/backend/v3/storage/database" + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/zerrors" + v2_org "github.com/zitadel/zitadel/pkg/grpc/org/v2" +) + +type ListOrgsQuery struct { + Request *v2_org.ListOrganizationsRequest + + result []*Organization +} + +// Result implements [Querier]. +func (l *ListOrgsQuery) Result() []*Organization { + return l.result +} + +func NewListOrgsQuery(inputRequest *v2_org.ListOrganizationsRequest) *ListOrgsQuery { + return &ListOrgsQuery{ + Request: inputRequest, + } +} + +// Execute implements [Querier]. +func (l *ListOrgsQuery) Execute(ctx context.Context, opts *InvokeOpts) (err error) { + organizationRepo := opts.organizationRepo.LoadDomains() + domainRepo := opts.organizationDomainRepo + + sorting := l.Sorting(organizationRepo) + limit, pagination := l.Pagination() + conditions, condErr := l.conditions(ctx, organizationRepo, domainRepo) + if condErr != nil { + err = condErr + return err + } + + l.result, err = organizationRepo.List(ctx, pool, conditions, sorting, limit, pagination) + return err +} + +func (l *ListOrgsQuery) Sorting(orgRepo OrganizationRepository) database.QueryOption { + var sortingCol database.Column + switch l.Request.GetSortingColumn() { + case v2_org.OrganizationFieldName_ORGANIZATION_FIELD_NAME_NAME: + sortingCol = orgRepo.NameColumn() + case v2_org.OrganizationFieldName_ORGANIZATION_FIELD_NAME_UNSPECIFIED: + fallthrough + default: + return func(opts *database.QueryOpts) {} + } + + orderDirection := database.OrderDirectionDesc + if l.Request.GetQuery().GetAsc() { + orderDirection = database.OrderDirectionAsc + } + + return database.WithOrderBy(orderDirection, sortingCol) +} + +func (l *ListOrgsQuery) Pagination() (database.QueryOption, database.QueryOption) { + return database.WithLimit(l.Request.GetQuery().GetLimit()), + database.WithOffset(uint32(l.Request.GetQuery().GetOffset())) +} + +func (l *ListOrgsQuery) conditions(ctx context.Context, orgRepo OrganizationRepository, domainRepo OrganizationDomainRepository) (database.QueryOption, error) { + conditions := make([]database.Condition, len(l.Request.GetQueries())) + instance := authz.GetInstance(ctx) + + for i, query := range l.Request.GetQueries() { + switch assertedType := query.GetQuery().(type) { + + case *v2_org.SearchQuery_DefaultQuery: + conditions[i] = orgRepo.IDCondition(instance.DefaultOrganisationID()) + case *v2_org.SearchQuery_DomainQuery: + method, err := object.TextQueryMethodToTextOperation(assertedType.DomainQuery.GetMethod()) + if err != nil { + return nil, err + } + + conditions[i] = database.And( + orgRepo.InstanceIDCondition(instance.InstanceID()), + orgRepo.ExistsDomain(domainRepo.DomainCondition(method, assertedType.DomainQuery.GetDomain())), + ) + case *v2_org.SearchQuery_IdQuery: + conditions[i] = orgRepo.IDCondition(assertedType.IdQuery.GetId()) + case *v2_org.SearchQuery_NameQuery: + method, err := object.TextQueryMethodToTextOperation(assertedType.NameQuery.GetMethod()) + if err != nil { + return nil, err + } + conditions[i] = orgRepo.NameCondition(method, assertedType.NameQuery.GetName()) + case *v2_org.SearchQuery_StateQuery: + conditions[i] = orgRepo.StateCondition(OrgState(assertedType.StateQuery.GetState())) + default: + return nil, zerrors.ThrowInvalidArgument(NewUnexpectedQueryTypeError(assertedType), "DOM-TCEzcr", "List.Query.Invalid") + } + } + + return database.WithCondition(database.And( + append(conditions, orgRepo.InstanceIDCondition(instance.InstanceID()))..., + )), nil +} + +// String implements [Querier]. +func (l *ListOrgsQuery) String() string { + return "ListOrgsQuery" +} + +// Validate implements [Querier]. +func (l *ListOrgsQuery) Validate(_ context.Context, _ *InvokeOpts) (err error) { + return nil +} + +var _ Querier[[]*Organization] = (*ListOrgsQuery)(nil) diff --git a/backend/v3/domain/org_list_test.go b/backend/v3/domain/org_list_test.go new file mode 100644 index 000000000..0634d6444 --- /dev/null +++ b/backend/v3/domain/org_list_test.go @@ -0,0 +1,337 @@ +package domain_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + + "github.com/zitadel/zitadel/backend/v3/domain" + domainmock "github.com/zitadel/zitadel/backend/v3/domain/mock" + "github.com/zitadel/zitadel/backend/v3/storage/database" + "github.com/zitadel/zitadel/backend/v3/storage/database/dbmock" + noopdb "github.com/zitadel/zitadel/backend/v3/storage/database/dialect/noop" + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/zerrors" + "github.com/zitadel/zitadel/pkg/grpc/object/v2" + "github.com/zitadel/zitadel/pkg/grpc/org/v2" +) + +func TestListOrgsCommand_sorting(t *testing.T) { + t.Parallel() + tt := []struct { + name string + request *org.ListOrganizationsRequest + + expectedSortingDirection database.OrderDirection + expectedOrderBy database.Columns + }{ + { + name: "sorting by name desc", + request: &org.ListOrganizationsRequest{ + SortingColumn: org.OrganizationFieldName_ORGANIZATION_FIELD_NAME_NAME, + Query: &object.ListQuery{ + Asc: false, + }, + }, + + expectedSortingDirection: database.OrderDirectionDesc, + expectedOrderBy: database.Columns{database.NewColumn("organizations", "name")}, + }, + { + name: "sorting by name asc", + request: &org.ListOrganizationsRequest{ + SortingColumn: org.OrganizationFieldName_ORGANIZATION_FIELD_NAME_NAME, + Query: &object.ListQuery{ + Asc: true, + }, + }, + + expectedSortingDirection: database.OrderDirectionAsc, + expectedOrderBy: database.Columns{database.NewColumn("organizations", "name")}, + }, + { + name: "unspecified sorting", + request: &org.ListOrganizationsRequest{ + SortingColumn: org.OrganizationFieldName_ORGANIZATION_FIELD_NAME_UNSPECIFIED, + }, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + // Given + ctrl := gomock.NewController(t) + orgRepo := domainmock.NewOrgRepo(ctrl) + l := &domain.ListOrgsQuery{ + Request: tc.request, + } + opts := &database.QueryOpts{} + + // Test + gotFunc := l.Sorting(orgRepo) + gotFunc(opts) + + // Verify + assert.Equal(t, tc.expectedOrderBy, opts.OrderBy) + assert.Equal(t, tc.expectedSortingDirection, opts.Ordering) + }) + } +} + +func TestListOrgsCommand_pagination(t *testing.T) { + t.Parallel() + tt := []struct { + name string + request *org.ListOrganizationsRequest + wantLimit uint64 + wantOffset uint32 + }{ + { + name: "pagination with limit and offset", + request: &org.ListOrganizationsRequest{ + Query: &object.ListQuery{ + Limit: 10, + Offset: 5, + }, + }, + wantLimit: 10, + wantOffset: 5, + }, + { + name: "pagination with zero values", + request: &org.ListOrganizationsRequest{ + Query: &object.ListQuery{ + Limit: 0, + Offset: 0, + }, + }, + wantLimit: 0, + wantOffset: 0, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + // Given + l := &domain.ListOrgsQuery{ + Request: tc.request, + } + opts := &database.QueryOpts{} + + // Test + limitFunc, offsetFunc := l.Pagination() + limitFunc(opts) + offsetFunc(opts) + + // Verify + assert.EqualValues(t, tc.wantLimit, opts.Limit) + assert.EqualValues(t, tc.wantOffset, opts.Offset) + }) + } +} + +func TestListOrgsCommand_Execute(t *testing.T) { + t.Parallel() + listErr := errors.New("list mock error") + + tt := []struct { + testName string + + repos func(ctrl *gomock.Controller, queryParams ...database.QueryOption) (domain.OrganizationRepository, domain.OrganizationDomainRepository) + + queryParams []database.QueryOption + inputRequest *org.ListOrganizationsRequest + + expectedOrganizations []*domain.Organization + expectedError error + }{ + { + testName: "when condition parsing fails should return error", + repos: func(ctrl *gomock.Controller, _ ...database.QueryOption) (domain.OrganizationRepository, domain.OrganizationDomainRepository) { + orgRepo := domainmock.NewOrgRepo(ctrl) + domainRepo := domainmock.NewOrgDomainRepo(ctrl) + orgRepo.EXPECT(). + LoadDomains(). + Times(1). + Return(orgRepo) + + return orgRepo, domainRepo + }, + inputRequest: &org.ListOrganizationsRequest{ + Queries: []*org.SearchQuery{{Query: &org.SearchQuery_DomainQuery{DomainQuery: &org.OrganizationDomainQuery{ + Domain: "some domain", + Method: object.TextQueryMethod(99)}}, + }, + }, + }, + expectedError: zerrors.ThrowInvalidArgument(nil, "OBJ-iBRBVe", "invalid text query method"), + }, + { + testName: "when listing orgs fails should return error", + repos: func(ctrl *gomock.Controller, queryParams ...database.QueryOption) (domain.OrganizationRepository, domain.OrganizationDomainRepository) { + orgRepo := domainmock.NewOrgRepo(ctrl) + domainRepo := domainmock.NewOrgDomainRepo(ctrl) + orgRepo.SetExistsDomain(database.Exists("domains", domainRepo.DomainCondition(database.TextOperationEqual, "some domain"))) + + orgRepo.EXPECT(). + LoadDomains(). + Times(1). + Return(orgRepo) + + orgRepo.EXPECT(). + List( + gomock.Any(), + gomock.Any(), + dbmock.QueryOptions( + database.WithCondition( + database.And( + database.And( + orgRepo.InstanceIDCondition("instance-1"), + orgRepo.ExistsDomain(domainRepo.DomainCondition(database.TextOperationEqual, "some domain")), + ), + orgRepo.InstanceIDCondition("instance-1"), + ), + ), + ), + + dbmock.QueryOptions(database.WithOrderBy(database.OrderDirectionAsc, orgRepo.NameColumn())), + dbmock.QueryOptions(database.WithLimit(2)), + dbmock.QueryOptions(database.WithOffset(1)), + ). + Times(1). + Return(nil, listErr) + + return orgRepo, domainRepo + }, + inputRequest: &org.ListOrganizationsRequest{ + Queries: []*org.SearchQuery{ + { + Query: &org.SearchQuery_DomainQuery{ + DomainQuery: &org.OrganizationDomainQuery{ + Domain: "some domain", + Method: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS, + }, + }, + }, + }, + SortingColumn: org.OrganizationFieldName_ORGANIZATION_FIELD_NAME_NAME, + Query: &object.ListQuery{ + Asc: true, + Offset: 1, + Limit: 2, + }, + }, + + expectedError: listErr, + }, + { + testName: "when listing orgs succeeds should return expected organizations", + repos: func(ctrl *gomock.Controller, queryParams ...database.QueryOption) (domain.OrganizationRepository, domain.OrganizationDomainRepository) { + orgRepo := domainmock.NewOrgRepo(ctrl) + domainRepo := domainmock.NewOrgDomainRepo(ctrl) + + orgRepo.EXPECT(). + LoadDomains(). + Times(1). + Return(orgRepo) + + orgRepo.EXPECT(). + List( + gomock.Any(), + gomock.Any(), + dbmock.QueryOptions(database.WithCondition(database.And( + orgRepo.IDCondition("org-1"), + orgRepo.IDCondition("org-2"), + orgRepo.NameCondition(database.TextOperationEqual, "Named Org"), + orgRepo.StateCondition(domain.OrgStateActive), + orgRepo.InstanceIDCondition("instance-1"), + ))), + + dbmock.QueryOptions(database.WithOrderBy(database.OrderDirectionDesc, orgRepo.NameColumn())), + dbmock.QueryOptions(database.WithLimit(2)), + dbmock.QueryOptions(database.WithOffset(1)), + ). + Times(1). + Return([]*domain.Organization{ + {ID: "org-1"}, + {ID: "org-2"}, + }, nil) + + return orgRepo, domainRepo + }, + inputRequest: &org.ListOrganizationsRequest{ + Queries: []*org.SearchQuery{ + { + Query: &org.SearchQuery_DefaultQuery{ + DefaultQuery: &org.DefaultOrganizationQuery{}, + }, + }, + { + Query: &org.SearchQuery_IdQuery{ + IdQuery: &org.OrganizationIDQuery{ + Id: "org-2", + }, + }, + }, + { + Query: &org.SearchQuery_NameQuery{ + NameQuery: &org.OrganizationNameQuery{ + Name: "Named Org", + Method: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS, + }, + }, + }, + { + Query: &org.SearchQuery_StateQuery{ + StateQuery: &org.OrganizationStateQuery{ + State: org.OrganizationState_ORGANIZATION_STATE_ACTIVE, + }, + }, + }, + }, + SortingColumn: org.OrganizationFieldName_ORGANIZATION_FIELD_NAME_NAME, + Query: &object.ListQuery{ + Asc: false, + Offset: 1, + Limit: 2, + }, + }, + + expectedOrganizations: []*domain.Organization{ + {ID: "org-1"}, + {ID: "org-2"}, + }, + }, + } + + for _, tc := range tt { + t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + // Given + ctx := authz.NewMockContext("instance-1", "org-1", "") + ctrl := gomock.NewController(t) + cmd := &domain.ListOrgsQuery{ + Request: tc.inputRequest, + } + opts := &domain.InvokeOpts{ + DB: new(noopdb.Pool), + } + if tc.repos != nil { + orgRepo, domainRepo := tc.repos(ctrl, tc.queryParams...) + domain.WithOrganizationRepo(orgRepo)(opts) + domain.WithOrganizationDomainRepo(domainRepo)(opts) + } + + // Test + err := cmd.Execute(ctx, opts) + + // Verify + assert.Equal(t, tc.expectedError, err) + assert.ElementsMatch(t, tc.expectedOrganizations, cmd.Result()) + }) + } +} diff --git a/backend/v3/domain/org_update.go b/backend/v3/domain/org_update.go new file mode 100644 index 000000000..8a074df8f --- /dev/null +++ b/backend/v3/domain/org_update.go @@ -0,0 +1,147 @@ +package domain + +import ( + "context" + "strings" + + "github.com/zitadel/zitadel/backend/v3/storage/database" + "github.com/zitadel/zitadel/internal/api/authz" + http_utils "github.com/zitadel/zitadel/internal/api/http" + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/repository/org" + "github.com/zitadel/zitadel/internal/zerrors" +) + +type UpdateOrgCommand struct { + ID string `json:"id"` + Name string `json:"name"` + + OldDomainName *string `json:"old_name"` + IsOldDomainVerified *bool `json:"is_old_domain_primary"` +} + +// Events implements Commander. +func (u *UpdateOrgCommand) Events(ctx context.Context, opts *InvokeOpts) ([]eventstore.Command, error) { + toReturn := []eventstore.Command{} + + if u.OldDomainName != nil && *u.OldDomainName != u.Name { + toReturn = append(toReturn, org.NewOrgChangedEvent(ctx, &org.NewAggregate(u.ID).Aggregate, *u.OldDomainName, u.Name)) + } + + return toReturn, nil +} + +var _ Commander = (*UpdateOrgCommand)(nil) + +func NewUpdateOrgCommand(id, name string) *UpdateOrgCommand { + return &UpdateOrgCommand{ + ID: id, + Name: name, + } +} + +func (u *UpdateOrgCommand) Execute(ctx context.Context, opts *InvokeOpts) (err error) { + close, err := opts.EnsureTx(ctx) + if err != nil { + return err + } + defer func() { err = close(ctx, err) }() + + organizationRepo := opts.organizationRepo.LoadDomains() + + org, err := organizationRepo.Get(ctx, pool, database.WithCondition( + database.And( + organizationRepo.IDCondition(u.ID), + organizationRepo.InstanceIDCondition(authz.GetInstance(ctx).InstanceID()), + ), + )) + if err != nil { + return err + } + + err = u.setDomainInfos(ctx, org) + if err != nil { + return err + } + + updateCount, err := organizationRepo.Update( + ctx, + pool, + database.And( + organizationRepo.IDCondition(org.ID), + organizationRepo.InstanceIDCondition(org.InstanceID), + ), + database.NewChange(organizationRepo.NameColumn(), u.Name), + ) + if err != nil { + return err + } + + if updateCount == 0 { + err = zerrors.ThrowNotFound(nil, "DOM-7PfSUn", "Errors.Org.NotFound") + return err + } + if updateCount > 1 { + err = zerrors.ThrowInternal(NewMultipleObjectsUpdatedError(1, updateCount), "DOM-QzITrx", "unexpected number of rows updated") + return err + } + + return err +} + +func (u *UpdateOrgCommand) String() string { + return "UpdateOrgCommand" +} + +func (u *UpdateOrgCommand) Validate(ctx context.Context, opts *InvokeOpts) error { + if u.ID = strings.TrimSpace(u.ID); u.ID == "" { + return zerrors.ThrowInvalidArgument(nil, "DOM-lEMhVC", "invalid organization ID") + } + if u.Name = strings.TrimSpace(u.Name); u.Name == "" { + return zerrors.ThrowInvalidArgument(nil, "DOM-wfUntW", "invalid organization name") + } + + close, err := opts.EnsureTx(ctx) + if err != nil { + return err + } + defer func() { err = close(ctx, err) }() + + organizationRepo := opts.organizationRepo + + org, err := organizationRepo.Get(ctx, pool, database.WithCondition( + database.And( + organizationRepo.IDCondition(u.ID), + organizationRepo.InstanceIDCondition(authz.GetInstance(ctx).InstanceID()), + ), + )) + if err != nil { + return err + } + + if org.Name == u.Name { + err = zerrors.ThrowPreconditionFailed(nil, "DOM-nDzwIu", "Errors.Org.NotChanged") + return err + } + + return nil +} + +func (u *UpdateOrgCommand) setDomainInfos(ctx context.Context, org *Organization) error { + iamDomain := http_utils.DomainContext(ctx).RequestedDomain() + oldDomainName, err := domain.NewIAMDomainName(org.Name, iamDomain) + if err != nil { + return err + } + + for _, d := range org.Domains { + if d.Domain == oldDomainName && !d.IsPrimary { + u.IsOldDomainVerified = &d.IsVerified + u.OldDomainName = &d.Domain + return nil + } + } + + return nil +} diff --git a/backend/v3/domain/org_update_test.go b/backend/v3/domain/org_update_test.go new file mode 100644 index 000000000..5c2bfd60b --- /dev/null +++ b/backend/v3/domain/org_update_test.go @@ -0,0 +1,492 @@ +package domain_test + +import ( + "context" + "errors" + "testing" + + "github.com/muhlemmer/gu" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + + "github.com/zitadel/zitadel/backend/v3/domain" + domainmock "github.com/zitadel/zitadel/backend/v3/domain/mock" + "github.com/zitadel/zitadel/backend/v3/storage/database" + "github.com/zitadel/zitadel/backend/v3/storage/database/dbmock" + noopdb "github.com/zitadel/zitadel/backend/v3/storage/database/dialect/noop" + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/zerrors" +) + +func TestUpdateOrgCommand_Execute(t *testing.T) { + t.Parallel() + + txInitErr := errors.New("tx init error") + getErr := errors.New("get error") + updateErr := errors.New("update error") + + tt := []struct { + testName string + + queryExecutor func(ctrl *gomock.Controller) database.QueryExecutor + orgRepo func(ctrl *gomock.Controller) domain.OrganizationRepository + + inputID string + inputName string + + expectedError error + expectedOldDomainName *string + expectedDomainVerified *bool + }{ + { + testName: "when EnsureTx fails should return error", + queryExecutor: func(ctrl *gomock.Controller) database.QueryExecutor { + mockDB := dbmock.NewMockPool(ctrl) + mockDB.EXPECT(). + Begin(gomock.Any(), gomock.Any()). + Times(1). + Return(nil, txInitErr) + return mockDB + }, + expectedError: txInitErr, + }, + { + testName: "when retrieving org fails should return error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + LoadDomains(). + Times(1). + Return(repo) + repo.EXPECT(). + Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions( + database.WithCondition( + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + ))). + Times(1). + Return(nil, getErr) + return repo + }, + inputID: "org-1", + inputName: "test org update", + expectedError: getErr, + }, + { + testName: "when setting domain info fails should return error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + LoadDomains(). + Times(1). + Return(repo) + repo.EXPECT(). + Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition( + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + ))). + Times(1). + Return(&domain.Organization{ + ID: "org-1", + Name: "", + State: domain.OrgStateActive, + Domains: []*domain.OrganizationDomain{ + {IsPrimary: true, IsVerified: true, Domain: "old org name"}, + {IsPrimary: false, IsVerified: true, Domain: "old org name"}, + {IsPrimary: false, IsVerified: true, Domain: "old primary org name"}, + }, + }, nil) + return repo + }, + inputID: "org-1", + inputName: "test org update", + expectedError: zerrors.ThrowInvalidArgument(nil, "ORG-RrfXY", "Errors.Org.Domain.EmptyString"), + }, + { + testName: "when org update fails should return error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + LoadDomains(). + Times(1). + Return(repo) + repo.EXPECT(). + Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition( + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + ))). + Times(1). + Return(&domain.Organization{ + ID: "org-1", + Name: "old org name", + InstanceID: "instance-1", + State: domain.OrgStateActive, + Domains: []*domain.OrganizationDomain{ + {IsPrimary: true, IsVerified: true, Domain: "old-org-name."}, + {IsPrimary: false, IsVerified: true, Domain: "old-org-name-2."}, + }, + }, nil) + + repo.EXPECT(). + Update(gomock.Any(), gomock.Any(), + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + repo.SetName("test org update"), + ). + Times(1). + Return(int64(0), updateErr) + return repo + }, + inputID: "org-1", + inputName: "test org update", + expectedError: updateErr, + }, + { + testName: "when org update returns 0 rows updated should return not found error", + inputID: "org-1", + inputName: "test org update", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + LoadDomains(). + Times(1). + Return(repo) + repo.EXPECT(). + Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition( + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + ))). + Times(1). + Return(&domain.Organization{ + ID: "org-1", + Name: "old org name", + InstanceID: "instance-1", + State: domain.OrgStateActive, + Domains: []*domain.OrganizationDomain{ + {IsPrimary: true, IsVerified: true, Domain: "old-org-name."}, + {IsPrimary: false, IsVerified: true, Domain: "old-org-name."}, + {IsPrimary: false, IsVerified: true, Domain: "old-org-name-2."}, + }, + }, nil) + + repo.EXPECT(). + Update(gomock.Any(), gomock.Any(), + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + repo.SetName("test org update")). + Times(1). + Return(int64(0), nil) + return repo + }, + expectedError: zerrors.ThrowNotFound(nil, "DOM-7PfSUn", "Errors.Org.NotFound"), + expectedOldDomainName: gu.Ptr("old-org-name."), + expectedDomainVerified: gu.Ptr(true), + }, + { + testName: "when org update returns more than 1 row updated should return internal error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + LoadDomains(). + Times(1). + Return(repo) + repo.EXPECT(). + Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition( + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + ))). + Times(1). + Return(&domain.Organization{ + ID: "org-1", + Name: "old org name", + InstanceID: "instance-1", + State: domain.OrgStateActive, + Domains: []*domain.OrganizationDomain{ + {IsPrimary: true, IsVerified: true, Domain: "old-org-name."}, + {IsPrimary: false, IsVerified: true, Domain: "old-org-name."}, + {IsPrimary: false, IsVerified: true, Domain: "old-org-name-2."}, + }, + }, nil) + + repo.EXPECT(). + Update(gomock.Any(), gomock.Any(), + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + repo.SetName("test org update")). + Times(1). + Return(int64(2), nil) + return repo + }, + inputID: "org-1", + inputName: "test org update", + expectedError: zerrors.ThrowInternal(domain.NewMultipleObjectsUpdatedError(1, 2), "DOM-QzITrx", "unexpected number of rows updated"), + expectedOldDomainName: gu.Ptr("old-org-name."), + expectedDomainVerified: gu.Ptr(true), + }, + { + testName: "when org update returns 1 row updated should return no error and set non-primary verified domain", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + LoadDomains(). + Times(1). + Return(repo) + repo.EXPECT(). + Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition( + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + ))). + Times(1). + Return(&domain.Organization{ + ID: "org-1", + Name: "old org name", + InstanceID: "instance-1", + State: domain.OrgStateActive, + Domains: []*domain.OrganizationDomain{ + {IsPrimary: true, IsVerified: true, Domain: "old-org-name."}, + {IsPrimary: false, IsVerified: true, Domain: "old-org-name."}, + {IsPrimary: false, IsVerified: true, Domain: "old-org-name-2."}, + }, + }, nil) + + repo.EXPECT(). + Update(gomock.Any(), gomock.Any(), + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + repo.SetName("test org update")). + Times(1). + Return(int64(1), nil) + return repo + }, + inputID: "org-1", + inputName: "test org update", + + expectedOldDomainName: gu.Ptr("old-org-name."), + expectedDomainVerified: gu.Ptr(true), + }, + } + + for _, tc := range tt { + t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + // Given + ctx := authz.NewMockContext("instance-1", "", "") + ctrl := gomock.NewController(t) + cmd := domain.NewUpdateOrgCommand(tc.inputID, tc.inputName) + + opts := &domain.InvokeOpts{ + DB: new(noopdb.Pool), + } + if tc.orgRepo != nil { + domain.WithOrganizationRepo(tc.orgRepo(ctrl))(opts) + } + if tc.queryExecutor != nil { + opts.DB = tc.queryExecutor(ctrl) + } + + // Test + err := cmd.Execute(ctx, opts) + + // Verify + assert.Equal(t, tc.expectedError, err) + assert.Equal(t, tc.expectedOldDomainName, cmd.OldDomainName) + assert.Equal(t, tc.expectedDomainVerified, cmd.IsOldDomainVerified) + }) + } +} + +func TestUpdateOrgCommand_Validate(t *testing.T) { + t.Parallel() + txInitErr := errors.New("tx init error") + getErr := errors.New("get error") + + tt := []struct { + testName string + queryExecutor func(ctrl *gomock.Controller) database.QueryExecutor + orgRepo func(ctrl *gomock.Controller) domain.OrganizationRepository + inputOrgID string + inputOrgName string + expectedError error + }{ + { + testName: "when no ID should return invalid argument error", + inputOrgID: "", + inputOrgName: "test-name", + expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-lEMhVC", "invalid organization ID"), + }, + { + testName: "when no name shuld return invalid argument error", + inputOrgID: "test-id", + inputOrgName: "", + expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-wfUntW", "invalid organization name"), + }, + { + testName: "when EnsureTx fails should return error", + queryExecutor: func(ctrl *gomock.Controller) database.QueryExecutor { + mockDB := dbmock.NewMockPool(ctrl) + mockDB.EXPECT(). + Begin(gomock.Any(), gomock.Any()). + Times(1). + Return(nil, txInitErr) + return mockDB + }, + inputOrgID: "test-id", + inputOrgName: "test-name", + expectedError: txInitErr, + }, + { + testName: "when retrieving org fails should return error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions( + database.WithCondition( + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + ))). + Times(1). + Return(nil, getErr) + return repo + }, + inputOrgID: "org-1", + inputOrgName: "test org update", + expectedError: getErr, + }, + { + testName: "when org name is not changed should return name not changed error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition( + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + ))). + Times(1). + Return(&domain.Organization{ + ID: "org-1", + Name: "test org update", + }, nil) + return repo + }, + inputOrgID: "org-1", + inputOrgName: "test org update", + expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-nDzwIu", "Errors.Org.NotChanged"), + }, + { + testName: "when org name is changed should validate successfully and return no error", + orgRepo: func(ctrl *gomock.Controller) domain.OrganizationRepository { + repo := domainmock.NewOrgRepo(ctrl) + repo.EXPECT(). + Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition( + database.And( + repo.IDCondition("org-1"), + repo.InstanceIDCondition("instance-1"), + ), + ))). + Times(1). + Return(&domain.Organization{ + ID: "org-1", + Name: "old org name", + }, nil) + return repo + }, + inputOrgID: "org-1", + inputOrgName: "test org update", + }, + } + + for _, tc := range tt { + t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + // Given + ctx := authz.NewMockContext("instance-1", "", "") + ctrl := gomock.NewController(t) + cmd := domain.NewUpdateOrgCommand(tc.inputOrgID, tc.inputOrgName) + + opts := &domain.InvokeOpts{ + DB: new(noopdb.Pool), + } + if tc.orgRepo != nil { + domain.WithOrganizationRepo(tc.orgRepo(ctrl))(opts) + } + if tc.queryExecutor != nil { + opts.DB = tc.queryExecutor(ctrl) + } + + err := cmd.Validate(ctx, opts) + assert.Equal(t, tc.expectedError, err) + }) + } +} + +func TestUpdateOrgCommand_Events(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + cmd *domain.UpdateOrgCommand + expectedCount int + }{ + { + name: "no old name, no events", + cmd: &domain.UpdateOrgCommand{ + ID: "org-1", + Name: "new-name", + OldDomainName: nil, + }, + expectedCount: 0, + }, + { + name: "old name equals new name, no events", + cmd: &domain.UpdateOrgCommand{ + ID: "org-1", + Name: "same-name", + OldDomainName: gu.Ptr("same-name"), + }, + expectedCount: 0, + }, + { + name: "old name different from new name, returns event", + cmd: &domain.UpdateOrgCommand{ + ID: "org-1", + Name: "new-name", + OldDomainName: gu.Ptr("old-name"), + }, + expectedCount: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + events, err := tt.cmd.Events(context.Background(), &domain.InvokeOpts{}) + require.Nil(t, err) + assert.Len(t, events, tt.expectedCount) + }) + } +} diff --git a/backend/v3/domain/organization.go b/backend/v3/domain/organization.go index 2bf92e448..cc99bebc9 100644 --- a/backend/v3/domain/organization.go +++ b/backend/v3/domain/organization.go @@ -10,9 +10,15 @@ import ( //go:generate enumer -type OrgState -transform lower -trimprefix OrgState -sql type OrgState uint8 +// Must be in the same order and quantity as zitadel/org/v2/org.proto const ( - OrgStateActive OrgState = iota + OrgStateUnspecified OrgState = iota + OrgStateActive OrgStateInactive + + // TODO(IAM-Marco): This should be removed in next versions of Zitadel I believe. We are hard deleting, + // so not sure when this state would be used. It is kept here just for compatibility with the gRPC model + OrgStateRemoved ) type Organization struct { @@ -67,6 +73,8 @@ type organizationChanges interface { SetState(state OrgState) database.Change } +//go:generate mockgen -typed -package domainmock -destination ./mock/org.mock.go . OrganizationRepository + // OrganizationRepository is the interface for the instance repository. type OrganizationRepository interface { organizationColumns @@ -98,3 +106,13 @@ type MemberRepository interface { SetMemberRoles(ctx context.Context, orgID, userID string, roles []string) error RemoveMember(ctx context.Context, orgID, userID string) error } + +func (o *Organization) PrimaryDomain() string { + for _, d := range o.Domains { + if d.IsPrimary { + return d.Domain + } + } + + return "" +} diff --git a/backend/v3/domain/organization_domain.go b/backend/v3/domain/organization_domain.go index 1e1b3877c..157e2c890 100644 --- a/backend/v3/domain/organization_domain.go +++ b/backend/v3/domain/organization_domain.go @@ -62,6 +62,8 @@ type organizationDomainChanges interface { SetValidationType(verificationType DomainValidationType) database.Change } +//go:generate mockgen -typed -package domainmock -destination ./mock/org_domain.mock.go . OrganizationDomainRepository + type OrganizationDomainRepository interface { organizationDomainColumns organizationDomainConditions diff --git a/backend/v3/domain/orgstate_enumer.go b/backend/v3/domain/orgstate_enumer.go index 69d4c053b..a20435874 100644 --- a/backend/v3/domain/orgstate_enumer.go +++ b/backend/v3/domain/orgstate_enumer.go @@ -8,11 +8,11 @@ import ( "strings" ) -const _OrgStateName = "activeinactive" +const _OrgStateName = "unspecifiedactiveinactiveremoved" -var _OrgStateIndex = [...]uint8{0, 6, 14} +var _OrgStateIndex = [...]uint8{0, 11, 17, 25, 32} -const _OrgStateLowerName = "activeinactive" +const _OrgStateLowerName = "unspecifiedactiveinactiveremoved" func (i OrgState) String() string { if i >= OrgState(len(_OrgStateIndex)-1) { @@ -25,22 +25,30 @@ func (i OrgState) String() string { // Re-run the stringer command to generate them again. func _OrgStateNoOp() { var x [1]struct{} - _ = x[OrgStateActive-(0)] - _ = x[OrgStateInactive-(1)] + _ = x[OrgStateUnspecified-(0)] + _ = x[OrgStateActive-(1)] + _ = x[OrgStateInactive-(2)] + _ = x[OrgStateRemoved-(3)] } -var _OrgStateValues = []OrgState{OrgStateActive, OrgStateInactive} +var _OrgStateValues = []OrgState{OrgStateUnspecified, OrgStateActive, OrgStateInactive, OrgStateRemoved} var _OrgStateNameToValueMap = map[string]OrgState{ - _OrgStateName[0:6]: OrgStateActive, - _OrgStateLowerName[0:6]: OrgStateActive, - _OrgStateName[6:14]: OrgStateInactive, - _OrgStateLowerName[6:14]: OrgStateInactive, + _OrgStateName[0:11]: OrgStateUnspecified, + _OrgStateLowerName[0:11]: OrgStateUnspecified, + _OrgStateName[11:17]: OrgStateActive, + _OrgStateLowerName[11:17]: OrgStateActive, + _OrgStateName[17:25]: OrgStateInactive, + _OrgStateLowerName[17:25]: OrgStateInactive, + _OrgStateName[25:32]: OrgStateRemoved, + _OrgStateLowerName[25:32]: OrgStateRemoved, } var _OrgStateNames = []string{ - _OrgStateName[0:6], - _OrgStateName[6:14], + _OrgStateName[0:11], + _OrgStateName[11:17], + _OrgStateName[17:25], + _OrgStateName[25:32], } // OrgStateString retrieves an enum value from the enum constants string name. diff --git a/backend/v3/domain/project.go b/backend/v3/domain/project.go index 885744b2b..f0d9de39d 100644 --- a/backend/v3/domain/project.go +++ b/backend/v3/domain/project.go @@ -91,6 +91,8 @@ type projectChanges interface { } // ProjectRepository manages projects and project roles. +// +//go:generate mockgen -typed -package domainmock -destination ./mock/project.mock.go . ProjectRepository type ProjectRepository interface { projectColumns projectConditions diff --git a/backend/v3/storage/database/change.go b/backend/v3/storage/database/change.go index 6581a2c4c..89d899364 100644 --- a/backend/v3/storage/database/change.go +++ b/backend/v3/storage/database/change.go @@ -1,10 +1,16 @@ package database -import "slices" +import ( + "reflect" + "slices" + + "go.uber.org/mock/gomock" +) // Change represents a change to a column in a database table. // Its written in the SET clause of an UPDATE statement. type Change interface { + gomock.Matcher // Write writes the change to the given statement builder. Write(builder *StatementBuilder) // IsOnColumn checks if the change is on the given column. @@ -16,7 +22,26 @@ type change[V Value] struct { value V } -var _ Change = (*change[string])(nil) +// Matches implements [gomock.Matcher]. +func (c *change[V]) Matches(x any) bool { + toMatch, ok := x.(*change[V]) + if !ok { + return false + } + colMatch := c.column.Equals(toMatch.column) + valueMatch := reflect.DeepEqual(c.value, toMatch.value) + return colMatch && valueMatch +} + +// String implements [gomock.Matcher]. +func (c *change[V]) String() string { + return "database.change" +} + +var ( + _ Change = (*change[string])(nil) + _ gomock.Matcher = (*change[string])(nil) +) // NewChange creates a new Change for the given column and value. // If you want to set a column to NULL, use [NewChangePtr]. @@ -71,4 +96,26 @@ func (m Changes) Write(builder *StatementBuilder) { } } +// Matches implements [gomock.Matcher]. +func (c Changes) Matches(x any) bool { + toMatch, ok := x.(*Changes) + if !ok { + return false + } + if len(c) != len(*toMatch) { + return false + } + for i := range c { + if !c[i].Matches((*toMatch)[i]) { + return false + } + } + return true +} + +// String implements [gomock.Matcher]. +func (c Changes) String() string { + return "database.Changes" +} + var _ Change = Changes(nil) diff --git a/backend/v3/storage/database/column.go b/backend/v3/storage/database/column.go index f1316048b..a2a510680 100644 --- a/backend/v3/storage/database/column.go +++ b/backend/v3/storage/database/column.go @@ -1,7 +1,25 @@ package database +import "go.uber.org/mock/gomock" + type Columns []Column +// Matches implements [Column]. +func (c Columns) Matches(x any) bool { + for _, col := range c { + if !col.Matches(x) { + return false + } + } + + return true +} + +// String implements [Column]. +func (c Columns) String() string { + return "database.Columns" +} + // WriteQualified implements [Column]. // Columns are separated by ", ". func (c Columns) WriteQualified(builder *StatementBuilder) { @@ -45,6 +63,7 @@ var _ Column = (Columns)(nil) // Column represents a column in a database table. type Column interface { + gomock.Matcher // WriteQualified writes the column with the table name as prefix. WriteQualified(builder *StatementBuilder) // WriteUnqualified writes the column without the table name as prefix. @@ -58,6 +77,20 @@ type column struct { name string } +// Matches implements [Column]. +func (c column) Matches(x any) bool { + toMatch, ok := x.(*column) + if !ok { + return false + } + return c.table == toMatch.table && c.name == toMatch.name +} + +// String implements [Column]. +func (c column) String() string { + return "database.column" +} + func NewColumn(table, name string) Column { return &column{table: table, name: name} } @@ -102,6 +135,26 @@ type functionColumn struct { col Column } +// Matches implements [Column]. +func (c *functionColumn) Matches(x any) bool { + toMatch, ok := x.(*functionColumn) + if !ok || toMatch.fn != c.fn { + return false + } + if toMatch.col == nil { + return c.col == nil + } + if c.col == nil { + return false + } + return toMatch.col.Matches(c.col) +} + +// String implements [Column]. +func (c *functionColumn) String() string { + return "database.functionColumn" +} + type function string const ( diff --git a/backend/v3/storage/database/condition.go b/backend/v3/storage/database/condition.go index 6bc1124f1..4658a258e 100644 --- a/backend/v3/storage/database/condition.go +++ b/backend/v3/storage/database/condition.go @@ -1,11 +1,14 @@ package database +import "go.uber.org/mock/gomock" + // Condition represents a SQL condition. // Its written after the WHERE keyword in a SQL statement. type Condition interface { + gomock.Matcher Write(builder *StatementBuilder) // IsRestrictingColumn is used to check if the condition filters for a specific column. - // It acts as a save guard database operations that should be specific on the given column. + // It acts as a safeguard database operations that should be specific on the given column. IsRestrictingColumn(col Column) bool } @@ -13,6 +16,28 @@ type and struct { conditions []Condition } +// Matches implements [Condition]. +func (a *and) Matches(x any) bool { + toMatch, ok := x.(*and) + if !ok { + return false + } + if len(a.conditions) != len(toMatch.conditions) { + return false + } + for i, condition := range a.conditions { + if !condition.Matches(toMatch.conditions[i]) { + return false + } + } + return true +} + +// String implements [Condition]. +func (a *and) String() string { + return "database.and" +} + // Write implements [Condition]. func (a and) Write(builder *StatementBuilder) { if len(a.conditions) > 1 { @@ -48,6 +73,28 @@ type or struct { conditions []Condition } +// Matches implements [Condition]. +func (o *or) Matches(x any) bool { + toMatch, ok := x.(*or) + if !ok { + return false + } + if len(o.conditions) != len(toMatch.conditions) { + return false + } + for i, condition := range o.conditions { + if !condition.Matches(toMatch.conditions[i]) { + return false + } + } + return true +} + +// String implements [Condition]. +func (o *or) String() string { + return "database.or" +} + // Write implements [Condition]. func (o or) Write(builder *StatementBuilder) { if len(o.conditions) > 1 { @@ -84,6 +131,20 @@ type isNull struct { column Column } +// Matches implements [Condition]. +func (i *isNull) Matches(x any) bool { + toMatch, ok := x.(*isNull) + if !ok { + return false + } + return i.column.Matches(toMatch.column) +} + +// String implements [Condition]. +func (i *isNull) String() string { + return "database.isNull" +} + // Write implements [Condition]. func (i isNull) Write(builder *StatementBuilder) { i.column.WriteQualified(builder) @@ -107,6 +168,20 @@ type isNotNull struct { column Column } +// Matches implements [Condition]. +func (i *isNotNull) Matches(x any) bool { + toMatch, ok := x.(*isNotNull) + if !ok { + return false + } + return i.column.Matches(toMatch.column) +} + +// String implements [Condition]. +func (i *isNotNull) String() string { + return "database.isNotNull" +} + // Write implements [Condition]. func (i isNotNull) Write(builder *StatementBuilder) { i.column.WriteQualified(builder) @@ -131,9 +206,32 @@ type valueCondition struct { col Column } +// Matches implements [Condition]. +func (c valueCondition) Matches(x any) bool { + toMatch, ok := x.(valueCondition) + if !ok { + return false + } + if !c.col.Matches(toMatch.col) { + return false + } + var expectedStatement, inputStatement StatementBuilder + c.write(&expectedStatement) + toMatch.write(&inputStatement) + return expectedStatement.String() == inputStatement.String() +} + +// String implements [Condition]. +func (c valueCondition) String() string { + return "database.valueCondition" +} + // NewTextCondition creates a condition that compares a text column with a value. -// If you want to use ignore case operations, consider using [NewTextIgnoreCaseCondition]. +// if `op.IsIgnoreCaseOperation()` is true the response is created using [NewTextIgnoreCaseCondition] func NewTextCondition[T Text](col Column, op TextOperation, value T) Condition { + if op.IsIgnoreCaseOperation() { + return NewTextIgnoreCaseCondition(col, op.Value(), value) + } return valueCondition{ col: col, write: func(builder *StatementBuilder) { @@ -207,12 +305,23 @@ func (i valueCondition) IsRestrictingColumn(col Column) bool { var _ Condition = (*valueCondition)(nil) // existsCondition is a helper to write an EXISTS (SELECT 1 FROM WHERE ) clause. -// It implements Condition so it can be composed with other conditions using And/Or. +// It implements [Condition] so it can be composed with other conditions using And/Or. type existsCondition struct { table string condition Condition } +// Matches implements [Condition]. +func (e *existsCondition) Matches(x any) bool { + // Unimplemented + return false +} + +// String implements [Condition]. +func (e *existsCondition) String() string { + return "unimplemented" +} + // Exists creates a condition that checks for the existence of rows in a subquery. func Exists(table string, condition Condition) Condition { return &existsCondition{ diff --git a/backend/v3/storage/database/condition_test.go b/backend/v3/storage/database/condition_test.go index a15dcd8fd..4ac1c78ae 100644 --- a/backend/v3/storage/database/condition_test.go +++ b/backend/v3/storage/database/condition_test.go @@ -99,6 +99,14 @@ func TestWrite(t *testing.T) { args: []any{"some text"}, }, }, + { + name: "text condition with ignore case", + cond: NewTextCondition(NewColumn("table", "column1"), TextOperationEqualIgnoreCase, "some text"), + want: want{ + stmt: "LOWER(table.column1) = LOWER($1)", + args: []any{"some text"}, + }, + }, { name: "text ignore case condition", cond: NewTextIgnoreCaseCondition(NewColumn("table", "column1"), TextOperationNotEqual, "some TEXT"), diff --git a/backend/v3/storage/database/dbmock/query_option.go b/backend/v3/storage/database/dbmock/query_option.go new file mode 100644 index 000000000..6a2e1a667 --- /dev/null +++ b/backend/v3/storage/database/dbmock/query_option.go @@ -0,0 +1,15 @@ +package dbmock + +import ( + "github.com/zitadel/zitadel/backend/v3/storage/database" +) + +// QueryOptions converts database.QueryOption to *database.QueryOpts which +// can then be used as a gomock Matcher. +func QueryOptions(optsFunc database.QueryOption) *database.QueryOpts { + opts := &database.QueryOpts{} + + optsFunc(opts) + + return opts +} diff --git a/backend/v3/storage/database/dialect/noop/client.go b/backend/v3/storage/database/dialect/noop/client.go new file mode 100644 index 000000000..66144b0bd --- /dev/null +++ b/backend/v3/storage/database/dialect/noop/client.go @@ -0,0 +1,46 @@ +package noopdb + +import ( + "context" + + "github.com/zitadel/zitadel/backend/v3/storage/database" +) + +type Client struct{} + +// Ping implements [database.Client]. +func (n *Client) Ping(ctx context.Context) error { + return nil +} + +// Begin implements [database.Client]. +func (n *Client) Begin(ctx context.Context, opts *database.TransactionOptions) (database.Transaction, error) { + return new(Transaction), nil +} + +// Exec implements [database.Client]. +func (n *Client) Exec(ctx context.Context, stmt string, args ...any) (int64, error) { + return 0, nil +} + +// Migrate implements [database.Client]. +func (n *Client) Migrate(ctx context.Context) error { + return nil +} + +// Query implements [database.Client]. +func (n *Client) Query(ctx context.Context, stmt string, args ...any) (database.Rows, error) { + return new(rows), nil +} + +// QueryRow implements [database.Client]. +func (n *Client) QueryRow(ctx context.Context, stmt string, args ...any) database.Row { + return new(row) +} + +// Release implements [database.Client]. +func (n *Client) Release(ctx context.Context) error { + return nil +} + +var _ database.Connection = (*Client)(nil) diff --git a/backend/v3/storage/database/dialect/noop/pool.go b/backend/v3/storage/database/dialect/noop/pool.go new file mode 100644 index 000000000..66a496a0e --- /dev/null +++ b/backend/v3/storage/database/dialect/noop/pool.go @@ -0,0 +1,51 @@ +package noopdb + +import ( + "context" + + "github.com/zitadel/zitadel/backend/v3/storage/database" +) + +type Pool struct{} + +// Ping implements [database.Pool]. +func (n *Pool) Ping(ctx context.Context) error { + return nil +} + +// Acquire implements [database.Pool]. +func (n *Pool) Acquire(ctx context.Context) (database.Connection, error) { + return new(Client), nil +} + +// Begin implements [database.Pool]. +func (n *Pool) Begin(ctx context.Context, opts *database.TransactionOptions) (database.Transaction, error) { + return new(Transaction), nil +} + +// Close implements [database.Pool]. +func (n *Pool) Close(ctx context.Context) error { + return nil +} + +// Exec implements [database.Pool]. +func (n *Pool) Exec(ctx context.Context, stmt string, args ...any) (int64, error) { + return 0, nil +} + +// Migrate implements [database.Pool]. +func (n *Pool) Migrate(ctx context.Context) error { + return nil +} + +// Query implements [database.Pool]. +func (n *Pool) Query(ctx context.Context, stmt string, args ...any) (database.Rows, error) { + return new(rows), nil +} + +// QueryRow implements [database.Pool]. +func (n *Pool) QueryRow(ctx context.Context, stmt string, args ...any) database.Row { + return new(row) +} + +var _ database.Pool = (*Pool)(nil) diff --git a/backend/v3/storage/database/dialect/noop/row.go b/backend/v3/storage/database/dialect/noop/row.go new file mode 100644 index 000000000..8b1d2f8e4 --- /dev/null +++ b/backend/v3/storage/database/dialect/noop/row.go @@ -0,0 +1,36 @@ +package noopdb + +import "github.com/zitadel/zitadel/backend/v3/storage/database" + +type rows struct{} + +// Close implements [database.Rows]. +func (r *rows) Close() error { + return nil +} + +// Err implements [database.Rows]. +func (r *rows) Err() error { + return nil +} + +// Next implements [database.Rows]. +func (r *rows) Next() bool { + return false +} + +// Scan implements [database.Rows]. +func (r *rows) Scan(dest ...any) error { + return nil +} + +var _ database.Rows = (*rows)(nil) + +type row struct{} + +// Scan implements database.Row. +func (r *row) Scan(dest ...any) error { + return nil +} + +var _ database.Row = (*row)(nil) diff --git a/backend/v3/storage/database/dialect/noop/transaction.go b/backend/v3/storage/database/dialect/noop/transaction.go new file mode 100644 index 000000000..38bc15560 --- /dev/null +++ b/backend/v3/storage/database/dialect/noop/transaction.go @@ -0,0 +1,46 @@ +package noopdb + +import ( + "context" + + "github.com/zitadel/zitadel/backend/v3/storage/database" +) + +type Transaction struct{} + +// Begin implements [database.Transaction]. +func (n *Transaction) Begin(ctx context.Context) (database.Transaction, error) { + return new(Transaction), nil +} + +// Commit implements [database.Transaction]. +func (n *Transaction) Commit(ctx context.Context) error { + return nil +} + +// End implements [database.Transaction]. +func (n *Transaction) End(ctx context.Context, err error) error { + return err +} + +// Exec implements [database.Transaction]. +func (n *Transaction) Exec(ctx context.Context, stmt string, args ...any) (int64, error) { + return 0, nil +} + +// Query implements [database.Transaction]. +func (n *Transaction) Query(ctx context.Context, stmt string, args ...any) (database.Rows, error) { + return new(rows), nil +} + +// QueryRow implements [database.Transaction]. +func (n *Transaction) QueryRow(ctx context.Context, stmt string, args ...any) database.Row { + return new(row) +} + +// Rollback implements [database.Transaction]. +func (n *Transaction) Rollback(ctx context.Context) error { + return nil +} + +var _ database.Transaction = (*Transaction)(nil) diff --git a/backend/v3/storage/database/operators.go b/backend/v3/storage/database/operators.go index 93cf1d32c..ca779cd07 100644 --- a/backend/v3/storage/database/operators.go +++ b/backend/v3/storage/database/operators.go @@ -1,6 +1,7 @@ package database import ( + "fmt" "time" "golang.org/x/exp/constraints" @@ -52,12 +53,68 @@ const ( TextOperationNotEqual // <> // TextOperationStartsWith checks if the first string starts with the second. TextOperationStartsWith // LIKE + // TextOperationContains checks if the first string contains the second + TextOperationContains // LIKE + // TextOperationEndsWith checks if the first string ends with the second. + TextOperationEndsWith // LIKE + + // TextOperationContainsIgnoreCase checks if the first string contains the second ignoring case + TextOperationContainsIgnoreCase // LIKE + // TextOperationEndsWith checks if the first string ends with the second ignoring case + TextOperationEndsWithIgnoreCase // LIKE + // TextOperationStartsWith checks if the first string starts with the second ignoring case + TextOperationStartsWithIgnoreCase // LIKE + // TextOperationEqualIgnoreCase compares two strings for equality ignoring case + TextOperationEqualIgnoreCase // = + // TextOperationNotEqualIgnoreCase compares two strings for inequality ignoring case + TextOperationNotEqualIgnoreCase // <> ) +func (op TextOperation) IsIgnoreCaseOperation() bool { + return op == TextOperationContainsIgnoreCase || + op == TextOperationEndsWithIgnoreCase || + op == TextOperationStartsWithIgnoreCase || + op == TextOperationEqualIgnoreCase || + op == TextOperationNotEqualIgnoreCase +} + +func (op TextOperation) Value() TextOperation { + switch op { + case TextOperationStartsWith, TextOperationContains, TextOperationEndsWith, TextOperationEqual, TextOperationNotEqual: + return op + case TextOperationContainsIgnoreCase: + return TextOperationContains + case TextOperationEndsWithIgnoreCase: + return TextOperationEndsWith + case TextOperationStartsWithIgnoreCase: + return TextOperationStartsWith + case TextOperationEqualIgnoreCase: + return TextOperationEqual + case TextOperationNotEqualIgnoreCase: + return TextOperationNotEqual + default: + panic(fmt.Sprintf("unexpected database.TextOperation: %#v", op)) + } +} + func writeTextOperation[T Text](builder *StatementBuilder, col Column, op TextOperation, value any) { - writeOperation[T](builder, col, op.String(), value) - if op == TextOperationStartsWith { + switch op { + case TextOperationContains: + writeOperation[T](builder, col, op.String()+" '%' ||", value) builder.WriteString(" || '%'") + + case TextOperationEndsWith: + writeOperation[T](builder, col, op.String()+" '%' ||", value) + + case TextOperationEqual, TextOperationNotEqual: + writeOperation[T](builder, col, op.String(), value) + case TextOperationStartsWith: + writeOperation[T](builder, col, op.String(), value) + builder.WriteString(" || '%'") + case TextOperationContainsIgnoreCase, TextOperationEndsWithIgnoreCase, TextOperationStartsWithIgnoreCase, TextOperationEqualIgnoreCase, TextOperationNotEqualIgnoreCase: + fallthrough + default: + panic(fmt.Sprintf("unexpected database.TextOperation: %#v", op)) } } diff --git a/backend/v3/storage/database/operators_enumer.go b/backend/v3/storage/database/operators_enumer.go index 8c73ad37a..2a72aee18 100644 --- a/backend/v3/storage/database/operators_enumer.go +++ b/backend/v3/storage/database/operators_enumer.go @@ -94,11 +94,11 @@ func (i NumberOperation) IsANumberOperation() bool { return false } -const _TextOperationName = "=<>LIKE" +const _TextOperationName = "=<>LIKELIKELIKELIKELIKELIKE" -var _TextOperationIndex = [...]uint8{0, 1, 3, 7} +var _TextOperationIndex = [...]uint8{0, 1, 3, 7, 11, 15, 19, 23, 27} -const _TextOperationLowerName = "=<>like" +const _TextOperationLowerName = "=<>likelikelikelikelikelike" func (i TextOperation) String() string { i -= 1 @@ -115,23 +115,43 @@ func _TextOperationNoOp() { _ = x[TextOperationEqual-(1)] _ = x[TextOperationNotEqual-(2)] _ = x[TextOperationStartsWith-(3)] + _ = x[TextOperationContains-(4)] + _ = x[TextOperationEndsWith-(5)] + _ = x[TextOperationContainsIgnoreCase-(6)] + _ = x[TextOperationEndsWithIgnoreCase-(7)] + _ = x[TextOperationStartsWithIgnoreCase-(8)] } -var _TextOperationValues = []TextOperation{TextOperationEqual, TextOperationNotEqual, TextOperationStartsWith} +var _TextOperationValues = []TextOperation{TextOperationEqual, TextOperationNotEqual, TextOperationStartsWith, TextOperationContains, TextOperationEndsWith, TextOperationContainsIgnoreCase, TextOperationEndsWithIgnoreCase, TextOperationStartsWithIgnoreCase} var _TextOperationNameToValueMap = map[string]TextOperation{ - _TextOperationName[0:1]: TextOperationEqual, - _TextOperationLowerName[0:1]: TextOperationEqual, - _TextOperationName[1:3]: TextOperationNotEqual, - _TextOperationLowerName[1:3]: TextOperationNotEqual, - _TextOperationName[3:7]: TextOperationStartsWith, - _TextOperationLowerName[3:7]: TextOperationStartsWith, + _TextOperationName[0:1]: TextOperationEqual, + _TextOperationLowerName[0:1]: TextOperationEqual, + _TextOperationName[1:3]: TextOperationNotEqual, + _TextOperationLowerName[1:3]: TextOperationNotEqual, + _TextOperationName[3:7]: TextOperationStartsWith, + _TextOperationLowerName[3:7]: TextOperationStartsWith, + _TextOperationName[7:11]: TextOperationContains, + _TextOperationLowerName[7:11]: TextOperationContains, + _TextOperationName[11:15]: TextOperationEndsWith, + _TextOperationLowerName[11:15]: TextOperationEndsWith, + _TextOperationName[15:19]: TextOperationContainsIgnoreCase, + _TextOperationLowerName[15:19]: TextOperationContainsIgnoreCase, + _TextOperationName[19:23]: TextOperationEndsWithIgnoreCase, + _TextOperationLowerName[19:23]: TextOperationEndsWithIgnoreCase, + _TextOperationName[23:27]: TextOperationStartsWithIgnoreCase, + _TextOperationLowerName[23:27]: TextOperationStartsWithIgnoreCase, } var _TextOperationNames = []string{ _TextOperationName[0:1], _TextOperationName[1:3], _TextOperationName[3:7], + _TextOperationName[7:11], + _TextOperationName[11:15], + _TextOperationName[15:19], + _TextOperationName[19:23], + _TextOperationName[23:27], } // TextOperationString retrieves an enum value from the enum constants string name. diff --git a/backend/v3/storage/database/operators_test.go b/backend/v3/storage/database/operators_test.go index d7de372b8..5c8a5120b 100644 --- a/backend/v3/storage/database/operators_test.go +++ b/backend/v3/storage/database/operators_test.go @@ -77,6 +77,26 @@ func Test_writeOperation(t *testing.T) { args: []any{"value"}, }, }, + { + name: "text ends with", + write: func(builder *StatementBuilder) { + writeTextOperation[string](builder, NewColumn("table", "column"), TextOperationEndsWith, "value") + }, + want: want{ + stmt: "table.column LIKE '%' || $1", + args: []any{"value"}, + }, + }, + { + name: "text contains", + write: func(builder *StatementBuilder) { + writeTextOperation[string](builder, NewColumn("table", "column"), TextOperationContains, "value") + }, + want: want{ + stmt: "table.column LIKE '%' || $1 || '%'", + args: []any{"value"}, + }, + }, { name: "text equal with wrapped value", write: func(builder *StatementBuilder) { @@ -107,6 +127,27 @@ func Test_writeOperation(t *testing.T) { args: []any{"value"}, }, }, + { + name: "text ends with with wrapped value", + write: func(builder *StatementBuilder) { + writeTextOperation[string](builder, LowerColumn(NewColumn("table", "column")), TextOperationEndsWith, LowerValue("value")) + }, + want: want{ + stmt: "LOWER(table.column) LIKE '%' || LOWER($1)", + args: []any{"value"}, + }, + }, + { + name: "text contains with wrapped value", + write: func(builder *StatementBuilder) { + writeTextOperation[string](builder, LowerColumn(NewColumn("table", "column")), TextOperationContains, LowerValue("value")) + }, + want: want{ + stmt: "LOWER(table.column) LIKE '%' || LOWER($1) || '%'", + args: []any{"value"}, + }, + }, + { name: "number equal", write: func(builder *StatementBuilder) { @@ -167,6 +208,7 @@ func Test_writeOperation(t *testing.T) { args: []any{123}, }, }, + { name: "boolean is true", write: func(builder *StatementBuilder) { @@ -187,6 +229,7 @@ func Test_writeOperation(t *testing.T) { args: []any{false}, }, }, + { name: "bytes equal", write: func(builder *StatementBuilder) { @@ -242,3 +285,137 @@ func Test_writeOperation(t *testing.T) { }) } } + +func TestTextOperation_Value(t *testing.T) { + t.Parallel() + tests := []struct { + name string + op TextOperation + want TextOperation + }{ + { + name: "TextOperationStartsWith returns self", + op: TextOperationStartsWith, + want: TextOperationStartsWith, + }, + { + name: "TextOperationContains returns self", + op: TextOperationContains, + want: TextOperationContains, + }, + { + name: "TextOperationEndsWith returns self", + op: TextOperationEndsWith, + want: TextOperationEndsWith, + }, + { + name: "TextOperationEqual returns self", + op: TextOperationEqual, + want: TextOperationEqual, + }, + { + name: "TextOperationNotEqual returns self", + op: TextOperationNotEqual, + want: TextOperationNotEqual, + }, + { + name: "TextOperationContainsIgnoreCase returns Contains", + op: TextOperationContainsIgnoreCase, + want: TextOperationContains, + }, + { + name: "TextOperationEndsWithIgnoreCase returns EndsWith", + op: TextOperationEndsWithIgnoreCase, + want: TextOperationEndsWith, + }, + { + name: "TextOperationStartsWithIgnoreCase returns StartsWith", + op: TextOperationStartsWithIgnoreCase, + want: TextOperationStartsWith, + }, + { + name: "TextOperationEqualIgnoreCase returns Equal", + op: TextOperationEqualIgnoreCase, + want: TextOperationEqual, + }, + { + name: "TextOperationNotEqualIgnoreCase returns NotEqual", + op: TextOperationNotEqualIgnoreCase, + want: TextOperationNotEqual, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := tt.op.Value() + assert.Equal(t, tt.want, got) + }) + } +} + +func TestTextOperation_IsIgnoreCaseOperation(t *testing.T) { + t.Parallel() + tests := []struct { + name string + op TextOperation + want bool + }{ + { + name: "TextOperationContainsIgnoreCase returns true", + op: TextOperationContainsIgnoreCase, + want: true, + }, + { + name: "TextOperationEndsWithIgnoreCase returns true", + op: TextOperationEndsWithIgnoreCase, + want: true, + }, + { + name: "TextOperationStartsWithIgnoreCase returns true", + op: TextOperationStartsWithIgnoreCase, + want: true, + }, + { + name: "TextOperationEqualIgnoreCase returns true", + op: TextOperationEqualIgnoreCase, + want: true, + }, + { + name: "TextOperationNotEqualIgnoreCase returns true", + op: TextOperationNotEqualIgnoreCase, + want: true, + }, + { + name: "TextOperationContains returns false", + op: TextOperationContains, + want: false, + }, + { + name: "TextOperationEndsWith returns false", + op: TextOperationEndsWith, + want: false, + }, + { + name: "TextOperationStartsWith returns false", + op: TextOperationStartsWith, + want: false, + }, + { + name: "TextOperationEqual returns false", + op: TextOperationEqual, + want: false, + }, + { + name: "TextOperationNotEqual returns false", + op: TextOperationNotEqual, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := tt.op.IsIgnoreCaseOperation() + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/backend/v3/storage/database/query.go b/backend/v3/storage/database/query.go index 11e528752..ba0b54c09 100644 --- a/backend/v3/storage/database/query.go +++ b/backend/v3/storage/database/query.go @@ -1,5 +1,12 @@ package database +import ( + "fmt" + "reflect" + + "go.uber.org/mock/gomock" +) + type QueryOption func(opts *QueryOpts) // WithCondition sets the condition for the query. @@ -113,6 +120,64 @@ type QueryOpts struct { Permission string } +// Matches implements [gomock.Matcher]. +func (q *QueryOpts) Matches(x any) bool { + // first check if the x is a [QueryOpt] + inputOpts, ok := x.(*QueryOpts) + if !ok { + // second possibility is a [QueryOption] + optFunc, ok := x.(QueryOption) + if !ok { + return false + } + + // QueryOption is a function that takes a *QueryOpts in input and fills it with its data. + // QueryOption data is not accessible because it's a function, so we exploit this "hack" + // to read the data. + inputOpts = new(QueryOpts) + optFunc(inputOpts) + } + + // inputOpts now contains the actual data but made of other interfaces and functions/decorator. + // Doing a reflect.DeepEqual() will fail because you will end up into comparing functions + // which is not possible (comparison is successful only if both functions are nil). + // So we exploit the Write() method of QueryOpts to fill up the StatementBuilders: + // these objects are basically string builders, so we can leverage their String() + // method (implementing Stringer interface) to make an easy and safe comparison. + inputBuilder, expectedBuilder := &StatementBuilder{}, &StatementBuilder{} + inputOpts.Write(inputBuilder) + q.Write(expectedBuilder) + + if len(inputBuilder.Args()) != len(expectedBuilder.Args()) { + return false + } + + for i := range inputBuilder.Args() { + argIn, argExp := inputBuilder.Args()[i], expectedBuilder.Args()[i] + + isEqual := reflect.DeepEqual(argIn, argExp) + if !isEqual { + return false + } + } + return inputBuilder.String() == expectedBuilder.String() +} + +// String implements [gomock.Matcher]. +func (q *QueryOpts) String() string { + return fmt.Sprintf("QueryOpts: {%v,%v,%v,%v,%v,%v,%v}\n", + q.Condition, + q.OrderBy, + q.Ordering, + q.Limit, + q.Offset, + q.GroupBy, + q.Joins, + ) +} + +var _ (gomock.Matcher) = (*QueryOpts)(nil) + func (opts *QueryOpts) Write(builder *StatementBuilder) { opts.WriteLeftJoins(builder) opts.WriteCondition(builder) diff --git a/cmd/start/start.go b/cmd/start/start.go index f6e9ee003..9d0357fba 100644 --- a/cmd/start/start.go +++ b/cmd/start/start.go @@ -26,6 +26,8 @@ import ( "golang.org/x/net/http2/h2c" "golang.org/x/text/language" + new_domain "github.com/zitadel/zitadel/backend/v3/domain" + v3_postgres "github.com/zitadel/zitadel/backend/v3/storage/database/dialect/postgres" "github.com/zitadel/zitadel/cmd/build" "github.com/zitadel/zitadel/cmd/encryption" "github.com/zitadel/zitadel/cmd/key" @@ -165,6 +167,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server if err != nil { return fmt.Errorf("cannot start DB client for queries: %w", err) } + new_domain.SetPool(v3_postgres.PGxPool(dbClient.Pool)) keyStorage, err := cryptoDB.NewKeyStorage(dbClient, masterKey) if err != nil { @@ -189,6 +192,8 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server MaxRetries: config.Eventstore.MaxRetries, })) + new_domain.SetLegacyEventstore(eventstoreClient) + sessionTokenVerifier := internal_authz.SessionTokenVerifier(keys.OIDC) cacheConnectors, err := connector.StartConnectors(config.Caches, dbClient) if err != nil { diff --git a/internal/api/authz/context_mock.go b/internal/api/authz/context_mock.go index 4ff6f47bc..edcc0eb7f 100644 --- a/internal/api/authz/context_mock.go +++ b/internal/api/authz/context_mock.go @@ -22,10 +22,19 @@ func WithMockFeatures(features feature.Features) MockContextInstanceOpts { } } +func WithMockProjectID(projectID string) MockContextInstanceOpts { + return func(i *instance) { + i.projectID = projectID + } +} + func NewMockContext(instanceID, orgID, userID string, opts ...MockContextInstanceOpts) context.Context { ctx := context.WithValue(context.Background(), dataKey, CtxData{UserID: userID, OrgID: orgID}) - i := &instance{id: instanceID} + i := &instance{ + id: instanceID, + orgID: orgID, + } for _, o := range opts { o(i) } diff --git a/internal/api/grpc/org/v2/integration_test/query_test.go b/internal/api/grpc/org/v2/integration_test/query_test.go index d7acf4512..e390aa3b8 100644 --- a/internal/api/grpc/org/v2/integration_test/query_test.go +++ b/internal/api/grpc/org/v2/integration_test/query_test.go @@ -46,6 +46,13 @@ func createOrganizationWithCustomOrgID(ctx context.Context, name string, orgID s } } +// TODO(IAM-Marco): When permission checks will be implemented, this test needs to be updated to +// add the feature flag switch: +// +// relTableState := integration.RelationalTablesEnableMatrix() +// +// See TestServer_ListOrganizations in org/v2beta/integration_test +// See https://github.com/zitadel/zitadel/issues/10219 func TestServer_ListOrganizations(t *testing.T) { type args struct { ctx context.Context diff --git a/internal/api/grpc/org/v2/query.go b/internal/api/grpc/org/v2/query.go index 09e2534e8..1fff47f49 100644 --- a/internal/api/grpc/org/v2/query.go +++ b/internal/api/grpc/org/v2/query.go @@ -5,6 +5,7 @@ import ( "connectrpc.com/connect" + orgv2 "github.com/zitadel/zitadel/backend/v3/api/org/v2" "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/grpc/object/v2" "github.com/zitadel/zitadel/internal/domain" @@ -14,6 +15,10 @@ import ( ) func (s *Server) ListOrganizations(ctx context.Context, req *connect.Request[org.ListOrganizationsRequest]) (*connect.Response[org.ListOrganizationsResponse], error) { + if authz.GetFeatures(ctx).EnableRelationalTables { + return orgv2.ListOrganizations(ctx, req) + } + queries, err := listOrgRequestToModel(ctx, req) if err != nil { return nil, err diff --git a/internal/api/grpc/org/v2beta/integration_test/org_test.go b/internal/api/grpc/org/v2beta/integration_test/org_test.go index 5952e7bfc..165f32a76 100644 --- a/internal/api/grpc/org/v2beta/integration_test/org_test.go +++ b/internal/api/grpc/org/v2beta/integration_test/org_test.go @@ -5,6 +5,7 @@ package org_test import ( "context" "errors" + "fmt" "os" "slices" "strings" @@ -17,6 +18,7 @@ import ( "github.com/zitadel/zitadel/internal/integration" "github.com/zitadel/zitadel/pkg/grpc/admin" + "github.com/zitadel/zitadel/pkg/grpc/feature/v2" v2beta_object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta" v2beta_org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta" "github.com/zitadel/zitadel/pkg/grpc/user/v2" @@ -294,84 +296,100 @@ func TestServer_CreateOrganization(t *testing.T) { } func TestServer_UpdateOrganization(t *testing.T) { - orgs, orgsName, _ := createOrgs(CTX, t, Client, 1) - orgId := orgs[0].Id - orgName := orgsName[0] + ctx := Instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) - tests := []struct { - name string - ctx context.Context - req *v2beta_org.UpdateOrganizationRequest - want *v2beta_org.UpdateOrganizationResponse - wantErr bool - }{ - { - name: "update org with new name", - ctx: Instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner), - req: &v2beta_org.UpdateOrganizationRequest{ - Id: orgId, - Name: "new org name", - }, - }, - { - name: "update org with same name", - ctx: Instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner), - req: &v2beta_org.UpdateOrganizationRequest{ - Id: orgId, - Name: orgName, - }, - }, - { - name: "update org with non existent org id", - ctx: Instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner), - req: &v2beta_org.UpdateOrganizationRequest{ - Id: "non existant org id", - // Name: "", - }, - wantErr: true, - }, - { - name: "update org with no id", - ctx: Instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner), - req: &v2beta_org.UpdateOrganizationRequest{ - Id: "", - Name: orgName, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := Client.UpdateOrganization(tt.ctx, tt.req) - if tt.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) + t.Cleanup(func() { + _, err := Instance.Client.FeatureV2.ResetInstanceFeatures(ctx, &feature.ResetInstanceFeaturesRequest{}) + require.NoError(t, err) + }) - // check details - gotCD := got.GetChangeDate().AsTime() - now := time.Now() - assert.WithinRange(t, gotCD, now.Add(-time.Minute), now.Add(time.Minute)) + relTableState := integration.RelationalTablesEnableMatrix() + + cases := len(relTableState) + orgs, orgNames, _ := createOrgs(CTX, t, Client, 2*cases) + + for i, stateCase := range relTableState { + integration.EnsureInstanceFeature(t, ctx, Instance, stateCase.FeatureSet, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) { + assert.Equal(tCollect, stateCase.FeatureSet.GetEnableRelationalTables(), got.EnableRelationalTables.GetEnabled()) }) + tests := []struct { + name string + req *v2beta_org.UpdateOrganizationRequest + want *v2beta_org.UpdateOrganizationResponse + wantErr bool + }{ + { + name: "update org with new name", + req: &v2beta_org.UpdateOrganizationRequest{ + Id: orgs[0+(i*cases)].GetId(), + Name: fmt.Sprintf("new org name %d", i), + }, + }, + { + name: "update org with same name", + req: &v2beta_org.UpdateOrganizationRequest{ + Id: orgs[1+(i*cases)].GetId(), + Name: orgNames[1+(i*cases)], + }, + wantErr: true, + }, + { + name: "update org with non existent org id", + req: &v2beta_org.UpdateOrganizationRequest{ + Id: "non existent org id", + Name: "new name", + }, + wantErr: true, + }, + { + name: "update org with no id", + req: &v2beta_org.UpdateOrganizationRequest{ + Id: " ", + Name: "new name", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(fmt.Sprintf("%s - %s", stateCase.State, tt.name), func(t1 *testing.T) { + got, err := Client.UpdateOrganization(ctx, tt.req) + if tt.wantErr { + require.Error(t1, err) + return + } + require.NoError(t1, err) + + // check details + gotCD := got.GetChangeDate().AsTime() + now := time.Now() + assert.WithinRange(t1, gotCD, now.Add(-time.Minute), now.Add(time.Minute)) + }) + } } } func TestServer_ListOrganizations(t *testing.T) { + t.Cleanup(func() { + _, err := Instance.Client.FeatureV2.ResetInstanceFeatures(CTX, &feature.ResetInstanceFeaturesRequest{}) + require.NoError(t, err) + }) + testStartTimestamp := time.Now() - ListOrgIinstance := integration.NewInstance(CTX) - listOrgIAmOwnerCtx := ListOrgIinstance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) - listOrgClient := ListOrgIinstance.Client.OrgV2beta + listOrgInstance := integration.NewInstance(CTX) + listOrgIAmOwnerCtx := listOrgInstance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) + listOrgClient := listOrgInstance.Client.OrgV2beta noOfOrgs := 3 orgs, orgsName, orgsDomain := createOrgs(listOrgIAmOwnerCtx, t, listOrgClient, noOfOrgs) - // deactivat org[1] + // deactivate org[1] _, err := listOrgClient.DeactivateOrganization(listOrgIAmOwnerCtx, &v2beta_org.DeactivateOrganizationRequest{ Id: orgs[1].Id, }) require.NoError(t, err) + relTableState := integration.RelationalTablesEnableMatrix() + tests := []struct { name string ctx context.Context @@ -381,7 +399,7 @@ func TestServer_ListOrganizations(t *testing.T) { }{ { name: "list organizations, without required permissions", - ctx: ListOrgIinstance.WithAuthorizationToken(CTX, integration.UserTypeNoPermission), + ctx: listOrgInstance.WithAuthorizationToken(CTX, integration.UserTypeNoPermission), err: errors.New("membership not found"), }, { @@ -609,122 +627,137 @@ func TestServer_ListOrganizations(t *testing.T) { }, }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, 10*time.Minute) - require.EventuallyWithT(t, func(ttt *assert.CollectT) { - got, err := listOrgClient.ListOrganizations(tt.ctx, &v2beta_org.ListOrganizationsRequest{ - Filter: tt.query, - }) - if tt.err != nil { - require.ErrorContains(ttt, err, tt.err.Error()) - return - } - require.NoError(ttt, err) - require.Equal(ttt, uint64(len(tt.want)), got.Pagination.GetTotalResult()) + for _, stateCase := range relTableState { + integration.EnsureInstanceFeature(t, listOrgIAmOwnerCtx, listOrgInstance, stateCase.FeatureSet, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) { + assert.Equal(tCollect, stateCase.FeatureSet.GetEnableRelationalTables(), got.EnableRelationalTables.GetEnabled()) + }) - foundOrgs := 0 - for _, got := range got.Organizations { - for _, org := range tt.want { + for _, tt := range tests { + t.Run(fmt.Sprintf("%s - %s", stateCase.State, tt.name), func(t *testing.T) { + retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, 20*time.Second) + require.EventuallyWithT(t, func(ttt *assert.CollectT) { + got, err := listOrgClient.ListOrganizations(tt.ctx, &v2beta_org.ListOrganizationsRequest{ + Filter: tt.query, + }) + if tt.err != nil { + require.ErrorContains(ttt, err, tt.err.Error()) + return + } + require.NoError(ttt, err) - // created/chagned date - gotCD := got.GetCreationDate().AsTime() - now := time.Now() - assert.WithinRange(ttt, gotCD, testStartTimestamp, now.Add(time.Minute)) - gotCD = got.GetChangedDate().AsTime() - assert.WithinRange(ttt, gotCD, testStartTimestamp, now.Add(time.Minute)) + require.Equal(ttt, uint64(len(tt.want)), got.Pagination.GetTotalResult()) - // default org - if org.Name == got.Name && got.Name == "testinstance" { - foundOrgs += 1 - continue - } + foundOrgs := 0 + for _, got := range got.Organizations { + for _, org := range tt.want { - if org.Name == got.Name && - org.Id == got.Id { - foundOrgs += 1 + // created/chagned date + gotCD := got.GetCreationDate().AsTime() + now := time.Now() + assert.WithinRange(ttt, gotCD, testStartTimestamp, now.Add(time.Minute)) + gotCD = got.GetChangedDate().AsTime() + assert.WithinRange(ttt, gotCD, testStartTimestamp, now.Add(time.Minute)) + + // default org + if org.Name == got.Name && got.Name == "testinstance" { + foundOrgs += 1 + continue + } + + if org.Name == got.Name && + org.Id == got.Id { + foundOrgs += 1 + } } } - } - require.Equal(ttt, len(tt.want), foundOrgs) - }, retryDuration, tick, "timeout waiting for expected organizations being created") - }) + require.Equal(ttt, len(tt.want), foundOrgs) + }, retryDuration, tick, "timeout waiting for expected organizations being created") + }) + } } } func TestServer_DeleteOrganization(t *testing.T) { - tests := []struct { - name string - ctx context.Context - createOrgFunc func() string - req *v2beta_org.DeleteOrganizationRequest - want *v2beta_org.DeleteOrganizationResponse - dontCheckTime bool - err error - }{ - { - name: "delete org no permission", - ctx: Instance.WithAuthorizationToken(CTX, integration.UserTypeNoPermission), - createOrgFunc: func() string { - orgs, _, _ := createOrgs(CTX, t, Client, 1) - return orgs[0].Id + t.Cleanup(func() { + _, err := Instance.Client.FeatureV2.ResetInstanceFeatures(CTX, &feature.ResetInstanceFeaturesRequest{}) + require.NoError(t, err) + }) + + relTableState := integration.RelationalTablesEnableMatrix() + var orgs []*v2beta_org.CreateOrganizationResponse + orgsNumPerCase := 3 + orgs, _, _ = createOrgs(CTX, t, Client, orgsNumPerCase*len(relTableState)) + require.NotNil(t, orgs) + require.NotEmpty(t, orgs) + + for i, stateCase := range relTableState { + retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute) + require.EventuallyWithT(t, func(ttt *assert.CollectT) { + deleteRes, err := Client.DeleteOrganization(CTX, &v2beta_org.DeleteOrganizationRequest{Id: orgs[2+(orgsNumPerCase*i)].GetId()}) + assert.Nil(ttt, err) + assert.NotNil(ttt, deleteRes) + assert.NotZero(ttt, deleteRes.GetDeletionDate()) + }, retryDuration, tick) + + tests := []struct { + name string + ctx context.Context + req *v2beta_org.DeleteOrganizationRequest + want *v2beta_org.DeleteOrganizationResponse + dontCheckTime bool + err error + }{ + { + name: "delete org no permission", + ctx: Instance.WithAuthorizationToken(CTX, integration.UserTypeNoPermission), + req: &v2beta_org.DeleteOrganizationRequest{ + Id: orgs[0+(orgsNumPerCase*i)].GetId(), + }, + err: errors.New("membership not found"), }, - req: &v2beta_org.DeleteOrganizationRequest{}, - err: errors.New("membership not found"), - }, - { - name: "delete org happy path", - ctx: Instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner), - createOrgFunc: func() string { - orgs, _, _ := createOrgs(CTX, t, Client, 1) - return orgs[0].Id + { + name: "delete org happy path", + ctx: Instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner), + req: &v2beta_org.DeleteOrganizationRequest{ + Id: orgs[1+(orgsNumPerCase*i)].GetId(), + }, }, - req: &v2beta_org.DeleteOrganizationRequest{}, - }, - { - name: "delete already deleted org", - ctx: Instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner), - createOrgFunc: func() string { - orgs, _, _ := createOrgs(CTX, t, Client, 1) - // delete org - _, err := Client.DeleteOrganization(CTX, &v2beta_org.DeleteOrganizationRequest{Id: orgs[0].Id}) + { + name: "delete already deleted org", + ctx: Instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner), + req: &v2beta_org.DeleteOrganizationRequest{Id: orgs[2+(orgsNumPerCase*i)].GetId()}, + dontCheckTime: true, + }, + { + name: "delete non existent org", + ctx: Instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner), + req: &v2beta_org.DeleteOrganizationRequest{ + Id: "non existent org id", + }, + dontCheckTime: true, + }, + } + integration.EnsureInstanceFeature(t, CTX, Instance, stateCase.FeatureSet, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) { + assert.Equal(tCollect, stateCase.FeatureSet.GetEnableRelationalTables(), got.EnableRelationalTables.GetEnabled()) + }) + for _, tt := range tests { + t.Run(fmt.Sprintf("%s - %s", stateCase.State, tt.name), func(t *testing.T) { + got, err := Client.DeleteOrganization(tt.ctx, tt.req) + if tt.err != nil { + require.Contains(t, err.Error(), tt.err.Error()) + return + } require.NoError(t, err) - return orgs[0].Id - }, - req: &v2beta_org.DeleteOrganizationRequest{}, - dontCheckTime: true, - }, - { - name: "delete non existent org", - ctx: Instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner), - req: &v2beta_org.DeleteOrganizationRequest{ - Id: "non existent org id", - }, - dontCheckTime: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.createOrgFunc != nil { - tt.req.Id = tt.createOrgFunc() - } - - got, err := Client.DeleteOrganization(tt.ctx, tt.req) - if tt.err != nil { - require.Contains(t, err.Error(), tt.err.Error()) - return - } - require.NoError(t, err) - - // check details - gotCD := got.GetDeletionDate().AsTime() - if !tt.dontCheckTime { - now := time.Now() - assert.WithinRange(t, gotCD, now.Add(-time.Minute), now.Add(time.Minute)) - } - }) + // check details + gotCD := got.GetDeletionDate().AsTime() + if !tt.dontCheckTime { + now := time.Now() + assert.WithinRange(t, gotCD, now.Add(-time.Minute), now.Add(time.Minute)) + } + }) + } } } @@ -745,192 +778,167 @@ func TestServer_DeactivateReactivateNonExistentOrganization(t *testing.T) { } func TestServer_ActivateOrganization(t *testing.T) { - tests := []struct { - name string - ctx context.Context - testFunc func() string - err error - }{ - { - name: "Activate, happy path", - ctx: CTX, - testFunc: func() string { - // 1. create organization - orgs, _, _ := createOrgs(CTX, t, Client, 1) - orgId := orgs[0].Id + t.Cleanup(func() { + _, err := Instance.Client.FeatureV2.ResetInstanceFeatures(CTX, &feature.ResetInstanceFeaturesRequest{}) + require.NoError(t, err) + }) + relTableState := integration.RelationalTablesEnableMatrix() - // 2. deactivate organization once - deactivate_res, err := Client.DeactivateOrganization(CTX, &v2beta_org.DeactivateOrganizationRequest{ - Id: orgId, - }) - require.NoError(t, err) - gotCD := deactivate_res.GetChangeDate().AsTime() - now := time.Now() - assert.WithinRange(t, gotCD, now.Add(-time.Minute), now.Add(time.Minute)) + orgsNumPerCase := 2 + orgs, _, _ := createOrgs(CTX, t, Client, orgsNumPerCase*len(relTableState)) + require.NotNil(t, orgs) + require.NotEmpty(t, orgs) - // 3. check organization state is deactivated - retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, 10*time.Minute) - require.EventuallyWithT(t, func(ttt *assert.CollectT) { - listOrgRes, err := Client.ListOrganizations(CTX, &v2beta_org.ListOrganizationsRequest{ - Filter: []*v2beta_org.OrganizationSearchFilter{ - { - Filter: &v2beta_org.OrganizationSearchFilter_IdFilter{ - IdFilter: &v2beta_org.OrgIDFilter{ - Id: orgId, - }, - }, - }, - }, - }) - require.NoError(ttt, err) - if assert.GreaterOrEqual(ttt, len(listOrgRes.Organizations), 1) { - require.Equal(ttt, v2beta_org.OrgState_ORG_STATE_INACTIVE, listOrgRes.Organizations[0].State) - } - }, retryDuration, tick, "timeout waiting for expected organizations being created") - - return orgId - }, - }, - { - name: "Activate, no permission", - ctx: Instance.WithAuthorizationToken(CTX, integration.UserTypeNoPermission), - testFunc: func() string { - orgs, _, _ := createOrgs(CTX, t, Client, 1) - orgId := orgs[0].Id - return orgId - }, - // BUG: this needs changing - err: errors.New("membership not found"), - }, - { - name: "Activate, not existing", - ctx: CTX, - testFunc: func() string { - return "non-existing-org-id" - }, - err: errors.New("Organisation not found"), - }, - { - name: "Activate, already activated", - ctx: CTX, - testFunc: func() string { - orgs, _, _ := createOrgs(CTX, t, Client, 1) - orgId := orgs[0].Id - return orgId - }, - err: errors.New("Organisation is already active"), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var orgId string - if tt.testFunc != nil { - orgId = tt.testFunc() - } - _, err := Client.ActivateOrganization(tt.ctx, &v2beta_org.ActivateOrganizationRequest{ - Id: orgId, - }) - if tt.err != nil { - require.Contains(t, err.Error(), tt.err.Error()) - } else { - require.NoError(t, err) - } + for i, stateCase := range relTableState { + deactivatedOrgID := orgs[1+(orgsNumPerCase*i)].GetId() + deactivateRes, err := Client.DeactivateOrganization(CTX, &v2beta_org.DeactivateOrganizationRequest{ + Id: deactivatedOrgID, }) + require.NoError(t, err) + now := time.Now() + assert.WithinRange(t, deactivateRes.GetChangeDate().AsTime(), now.Add(-time.Minute), now.Add(time.Minute)) + + retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, 20*time.Second) + require.EventuallyWithT(t, func(ttt *assert.CollectT) { + listOrgRes, err := Client.ListOrganizations(CTX, &v2beta_org.ListOrganizationsRequest{ + Filter: []*v2beta_org.OrganizationSearchFilter{ + {Filter: &v2beta_org.OrganizationSearchFilter_IdFilter{IdFilter: &v2beta_org.OrgIDFilter{Id: deactivatedOrgID}}}, + }, + }) + assert.NoError(ttt, err) + assert.Equal(ttt, v2beta_org.OrgState_ORG_STATE_INACTIVE, listOrgRes.Organizations[0].State) + }, retryDuration, tick, "timeout waiting for expected organizations being created") + + tt := []struct { + name string + ctx context.Context + inputOrgID string + err error + }{ + { + name: "Activate, happy path", + ctx: CTX, + inputOrgID: deactivatedOrgID, + }, + { + name: "Activate, no permission", + ctx: Instance.WithAuthorizationToken(CTX, integration.UserTypeNoPermission), + inputOrgID: orgs[0+(orgsNumPerCase*i)].GetId(), + // BUG: this needs changing + err: errors.New("membership not found"), + }, + { + name: "Activate, not existing", + ctx: CTX, + inputOrgID: "non-existing-org-id", + err: errors.New("Organisation not found"), + }, + { + name: "Activate, already activated", + ctx: CTX, + inputOrgID: orgs[0+(orgsNumPerCase*i)].GetId(), + err: errors.New("Organisation is already active"), + }, + } + + integration.EnsureInstanceFeature(t, CTX, Instance, stateCase.FeatureSet, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) { + assert.Equal(tCollect, stateCase.FeatureSet.GetEnableRelationalTables(), got.EnableRelationalTables.GetEnabled()) + }) + for _, tc := range tt { + t.Run(fmt.Sprintf("%s - %s", stateCase.State, tc.name), func(t *testing.T) { + _, err := Client.ActivateOrganization(tc.ctx, &v2beta_org.ActivateOrganizationRequest{ + Id: tc.inputOrgID, + }) + if tc.err != nil { + require.Contains(t, err.Error(), tc.err.Error()) + } else { + require.NoError(t, err) + } + }) + } } } func TestServer_DeactivateOrganization(t *testing.T) { - tests := []struct { - name string - ctx context.Context - testFunc func() string - err error - }{ - { - name: "Deactivate, happy path", - ctx: CTX, - testFunc: func() string { - // 1. create organization - orgs, _, _ := createOrgs(CTX, t, Client, 1) - orgId := orgs[0].Id + t.Cleanup(func() { + _, err := Instance.Client.FeatureV2.ResetInstanceFeatures(CTX, &feature.ResetInstanceFeaturesRequest{}) + require.NoError(t, err) + }) + relTableState := integration.RelationalTablesEnableMatrix() - return orgId - }, - }, - { - name: "Deactivate, no permission", - ctx: Instance.WithAuthorizationToken(CTX, integration.UserTypeNoPermission), - testFunc: func() string { - orgs, _, _ := createOrgs(CTX, t, Client, 1) - orgId := orgs[0].Id - return orgId - }, - // BUG: this needs changing - err: errors.New("membership not found"), - }, - { - name: "Deactivate, not existing", - ctx: CTX, - testFunc: func() string { - return "non-existing-org-id" - }, - err: errors.New("Organisation not found"), - }, - { - name: "Deactivate, already deactivated", - ctx: CTX, - testFunc: func() string { - // 1. create organization - orgs, _, _ := createOrgs(CTX, t, Client, 1) - orgId := orgs[0].Id + orgsNumPerCase := 2 + orgs, _, _ := createOrgs(CTX, t, Client, orgsNumPerCase*len(relTableState)) + require.NotNil(t, orgs) + require.NotEmpty(t, orgs) - // 2. deactivate organization once - deactivate_res, err := Client.DeactivateOrganization(CTX, &v2beta_org.DeactivateOrganizationRequest{ - Id: orgId, - }) - require.NoError(t, err) - gotCD := deactivate_res.GetChangeDate().AsTime() - now := time.Now() - assert.WithinRange(t, gotCD, now.Add(-time.Minute), now.Add(time.Minute)) - - // 3. check organization state is deactivated - retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, 10*time.Minute) - require.EventuallyWithT(t, func(ttt *assert.CollectT) { - listOrgRes, err := Client.ListOrganizations(CTX, &v2beta_org.ListOrganizationsRequest{ - Filter: []*v2beta_org.OrganizationSearchFilter{ - { - Filter: &v2beta_org.OrganizationSearchFilter_IdFilter{ - IdFilter: &v2beta_org.OrgIDFilter{ - Id: orgId, - }, - }, - }, - }, - }) - require.NoError(ttt, err) - require.Equal(ttt, v2beta_org.OrgState_ORG_STATE_INACTIVE, listOrgRes.Organizations[0].State) - }, retryDuration, tick, "timeout waiting for expected organizations being created") - - return orgId - }, - err: errors.New("Organisation is already deactivated"), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var orgId string - orgId = tt.testFunc() - _, err := Client.DeactivateOrganization(tt.ctx, &v2beta_org.DeactivateOrganizationRequest{ - Id: orgId, - }) - if tt.err != nil { - require.Contains(t, err.Error(), tt.err.Error()) - } else { - require.NoError(t, err) - } + for i, stateCase := range relTableState { + deactivatedOrgID := orgs[1+(orgsNumPerCase*i)].GetId() + deactivateRes, err := Client.DeactivateOrganization(CTX, &v2beta_org.DeactivateOrganizationRequest{ + Id: deactivatedOrgID, }) + require.NoError(t, err) + now := time.Now() + assert.WithinRange(t, deactivateRes.GetChangeDate().AsTime(), now.Add(-time.Minute), now.Add(time.Minute)) + + retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, 20*time.Second) + require.EventuallyWithT(t, func(ttt *assert.CollectT) { + listOrgRes, err := Client.ListOrganizations(CTX, &v2beta_org.ListOrganizationsRequest{ + Filter: []*v2beta_org.OrganizationSearchFilter{ + {Filter: &v2beta_org.OrganizationSearchFilter_IdFilter{IdFilter: &v2beta_org.OrgIDFilter{Id: deactivatedOrgID}}}, + }, + }) + assert.NoError(ttt, err) + assert.Equal(ttt, v2beta_org.OrgState_ORG_STATE_INACTIVE, listOrgRes.Organizations[0].State) + }, retryDuration, tick, "timeout waiting for expected organizations being created") + + tt := []struct { + name string + ctx context.Context + inputOrgID string + err error + }{ + { + name: "Deactivate, happy path", + ctx: CTX, + inputOrgID: orgs[0+(orgsNumPerCase*i)].GetId(), + }, + { + name: "Deactivate, no permission", + ctx: Instance.WithAuthorizationToken(CTX, integration.UserTypeNoPermission), + inputOrgID: orgs[0+(orgsNumPerCase*i)].GetId(), + + // BUG: this needs changing + err: errors.New("membership not found"), + }, + { + name: "Deactivate, not existing", + ctx: CTX, + inputOrgID: "non-existing-org-id", + err: errors.New("Organisation not found"), + }, + { + name: "Deactivate, already deactivated", + ctx: CTX, + inputOrgID: deactivatedOrgID, + err: errors.New("Organisation is already deactivated"), + }, + } + + integration.EnsureInstanceFeature(t, CTX, Instance, stateCase.FeatureSet, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) { + assert.Equal(tCollect, stateCase.FeatureSet.GetEnableRelationalTables(), got.EnableRelationalTables.GetEnabled()) + }) + for _, tc := range tt { + t.Run(fmt.Sprintf("%s - %s", stateCase.State, tc.name), func(t *testing.T) { + _, err := Client.DeactivateOrganization(tc.ctx, &v2beta_org.DeactivateOrganizationRequest{ + Id: tc.inputOrgID, + }) + if tc.err != nil { + require.Contains(t, err.Error(), tc.err.Error()) + } else { + require.NoError(t, err) + } + }) + } } } diff --git a/internal/api/grpc/org/v2beta/org.go b/internal/api/grpc/org/v2beta/org.go index edc667409..29a522309 100644 --- a/internal/api/grpc/org/v2beta/org.go +++ b/internal/api/grpc/org/v2beta/org.go @@ -7,6 +7,8 @@ import ( "connectrpc.com/connect" "google.golang.org/protobuf/types/known/timestamppb" + orgv2 "github.com/zitadel/zitadel/backend/v3/api/org/v2" + "github.com/zitadel/zitadel/internal/api/authz" metadata "github.com/zitadel/zitadel/internal/api/grpc/metadata/v2beta" object "github.com/zitadel/zitadel/internal/api/grpc/object/v2beta" user "github.com/zitadel/zitadel/internal/api/grpc/user/v2beta" @@ -31,6 +33,10 @@ func (s *Server) CreateOrganization(ctx context.Context, request *connect.Reques } func (s *Server) UpdateOrganization(ctx context.Context, request *connect.Request[v2beta_org.UpdateOrganizationRequest]) (*connect.Response[v2beta_org.UpdateOrganizationResponse], error) { + if authz.GetFeatures(ctx).EnableRelationalTables { + return orgv2.UpdateOrganization(ctx, request) + } + org, err := s.command.ChangeOrg(ctx, request.Msg.GetId(), request.Msg.GetName()) if err != nil { return nil, err @@ -42,6 +48,10 @@ func (s *Server) UpdateOrganization(ctx context.Context, request *connect.Reques } func (s *Server) ListOrganizations(ctx context.Context, request *connect.Request[v2beta_org.ListOrganizationsRequest]) (*connect.Response[v2beta_org.ListOrganizationsResponse], error) { + if authz.GetFeatures(ctx).EnableRelationalTables { + return orgv2.ListOrganizationsBeta(ctx, request) + } + queries, err := listOrgRequestToModel(s.systemDefaults, request.Msg) if err != nil { return nil, err @@ -60,6 +70,10 @@ func (s *Server) ListOrganizations(ctx context.Context, request *connect.Request } func (s *Server) DeleteOrganization(ctx context.Context, request *connect.Request[v2beta_org.DeleteOrganizationRequest]) (*connect.Response[v2beta_org.DeleteOrganizationResponse], error) { + if authz.GetFeatures(ctx).EnableRelationalTables { + return orgv2.DeleteOrganization(ctx, request) + } + details, err := s.command.RemoveOrg(ctx, request.Msg.GetId()) if err != nil { var notFoundError *zerrors.NotFoundError @@ -112,6 +126,10 @@ func (s *Server) DeleteOrganizationMetadata(ctx context.Context, request *connec } func (s *Server) DeactivateOrganization(ctx context.Context, request *connect.Request[org.DeactivateOrganizationRequest]) (*connect.Response[org.DeactivateOrganizationResponse], error) { + if authz.GetFeatures(ctx).EnableRelationalTables { + return orgv2.DeactivateOrganization(ctx, request) + } + objectDetails, err := s.command.DeactivateOrg(ctx, request.Msg.GetId()) if err != nil { return nil, err @@ -122,6 +140,10 @@ func (s *Server) DeactivateOrganization(ctx context.Context, request *connect.Re } func (s *Server) ActivateOrganization(ctx context.Context, request *connect.Request[org.ActivateOrganizationRequest]) (*connect.Response[org.ActivateOrganizationResponse], error) { + if authz.GetFeatures(ctx).EnableRelationalTables { + return orgv2.ActivateOrganization(ctx, request) + } + objectDetails, err := s.command.ReactivateOrg(ctx, request.Msg.GetId()) if err != nil { return nil, err diff --git a/internal/command/org.go b/internal/command/org.go index 6505c8eef..081de7e7e 100644 --- a/internal/command/org.go +++ b/internal/command/org.go @@ -396,6 +396,7 @@ func (c *Commands) addOrgWithIDAndMember(ctx context.Context, name, userID, reso func (c *Commands) ChangeOrg(ctx context.Context, orgID, name string) (*domain.ObjectDetails, error) { name = strings.TrimSpace(name) + orgID = strings.TrimSpace(orgID) if orgID == "" || name == "" { return nil, zerrors.ThrowInvalidArgument(nil, "EVENT-Mf9sd", "Errors.Org.Invalid") } diff --git a/internal/command/org_domain.go b/internal/command/org_domain.go index 2e132f6c4..e13ba50f1 100644 --- a/internal/command/org_domain.go +++ b/internal/command/org_domain.go @@ -328,27 +328,27 @@ func (c *Commands) changeDefaultDomain(ctx context.Context, orgID, newName strin isPrimary := defaultDomain == orgDomains.PrimaryDomain orgAgg := OrgAggregateFromWriteModel(&orgDomains.WriteModel) for _, orgDomain := range orgDomains.Domains { - if orgDomain.State == domain.OrgDomainStateActive { - if orgDomain.Domain == defaultDomain { - newDefaultDomain, err := domain.NewIAMDomainName(newName, iamDomain) - if err != nil { - return nil, err - } - // rename of organization resulting in no change in the domain - if newDefaultDomain == defaultDomain { - return nil, nil - } - events := []eventstore.Command{ - org.NewDomainAddedEvent(ctx, orgAgg, newDefaultDomain), - org.NewDomainVerifiedEvent(ctx, orgAgg, newDefaultDomain), - } - if isPrimary { - events = append(events, org.NewDomainPrimarySetEvent(ctx, orgAgg, newDefaultDomain)) - } - events = append(events, org.NewDomainRemovedEvent(ctx, orgAgg, orgDomain.Domain, orgDomain.Verified)) - return events, nil - } + if orgDomain.State != domain.OrgDomainStateActive || orgDomain.Domain != defaultDomain { + continue } + + newDefaultDomain, err := domain.NewIAMDomainName(newName, iamDomain) + if err != nil { + return nil, err + } + // rename of organization resulting in no change in the domain + if newDefaultDomain == defaultDomain { + return nil, nil + } + events := []eventstore.Command{ + org.NewDomainAddedEvent(ctx, orgAgg, newDefaultDomain), + org.NewDomainVerifiedEvent(ctx, orgAgg, newDefaultDomain), + } + if isPrimary { + events = append(events, org.NewDomainPrimarySetEvent(ctx, orgAgg, newDefaultDomain)) + } + events = append(events, org.NewDomainRemovedEvent(ctx, orgAgg, orgDomain.Domain, orgDomain.Verified)) + return events, nil } return nil, nil } diff --git a/internal/integration/feature.go b/internal/integration/feature.go index 07942fcdc..41b9eaba8 100644 --- a/internal/integration/feature.go +++ b/internal/integration/feature.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/muhlemmer/gu" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -28,3 +29,22 @@ func EnsureInstanceFeature(t *testing.T, ctx context.Context, instance *Instance tick, "timed out waiting for ensuring instance feature") } + +type RelationalTableFeatureMatrix struct { + State string + FeatureSet *feature.SetInstanceFeaturesRequest +} + +// TODO(IAM-Marco): Once we have gotten rid of eventstore, this can be removed +func RelationalTablesEnableMatrix() []RelationalTableFeatureMatrix { + return []RelationalTableFeatureMatrix{ + { + State: "when relational tables are enabled", + FeatureSet: &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, + }, + { + State: "when relational tables are disabled", + FeatureSet: &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(false)}, + }, + } +}