From f04d8732708546cc41012fd67089d8387c427f5c Mon Sep 17 00:00:00 2001 From: "Marco A." Date: Tue, 21 Oct 2025 15:27:38 +0200 Subject: [PATCH] feat(domain): Organization APIs with relational tables (#10704) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Which Problems Are Solved This PR implements the endpoints listed in #10445 using relational tables. - [x] UpdateOrganization - [x] ListOrganizations - [x] DeleteOrganization - [x] DeactivateOrganization - [x] ActivateOrganization # How the Problems Are Solved - **UpdateOrganization:** Implemented logic for `Validate()`, `Execute()` and `Events()`. On CQRS side, updating an organization emits events related to the organization and its domains. Separate commanders have been made for the domain events so that their logic is run accordingly and we keep separation of concerns. Implementation of the domains is left to its own ticket. Because some domain-related data is changed during the organization update, its original values are saved as pointers inside [UpdateOrgCommand](https://github.com/zitadel/zitadel/blob/4f87cd8d0cfd75f290aa13c342e4939cecebef73/backend/v3/domain/org_update.go#L20). These variables will be then used as input for the domain commanders (that will run after the update organization one). - **ListOrganizations:** Implemented logic for `Validate()`, `Execute()`. Utility methods have been made to parse the input queries. This commander is used both by the v2 and v2beta endpoint, so a translation layer has been put in place to translate all v2beta requests into v2. The commander strictly handles v2 request only. The translation layer between v2beta and v2 APIs was made so to make it simple to remove it once the v2beta APIs will be removed (a simple deletion should suffice). A `converter` package [is shipped](https://github.com/zitadel/zitadel/blob/4f87cd8d0cfd75f290aa13c342e4939cecebef73/backend/v3/api/org/v2/convert/README.md#L5) as well with instruction for removal. TODOs have been put in place all over the code to indicate that the related piece of code needs to be removed once v2beta -> v2 transition is complete. - **DeleteOrganization:** Implemented logic for `Validate()`, `Execute()` and `Events()`. The `Events()` method is mostly incomplete as it requires a lot of data that is currently not retrievable due to the lack of their relational tables. - **DeactivateOrganization** and **ActivateOrganization**: Implemented logic for `Validate()`, `Execute()` and `Events()`. No notable remarks. 👉 All endpoints return matching errors to the CQRS counterpart: this way allows re-using the integration tests and 👉 All endpoints have been unit-tested through DB mocking 👉 A bunch of TODOs have been put in place for future reworks that are needed (e.g. `Update()` method on repository should also return a timestamp) # Additional Changes - A test utility method to allow mocking query options have been made. This was necessary because `database.QueryOption` is a function and the only comparison possible with functions is `nil` check. So, to mock this, `database.QueryOption` is converted first to `database.QueryOpts`. See 2276742adaef1998ea4844e1f0dca2079e45ccfb - A `BaseCommand` has been created with the idea to collect methods that are going to be needed by other commands. For the moment, it only offers a method to convert query text operations from gRPC to `domain` model. See bb85456dd101282fa76d2e2ad165eed4c8ef61b9 - SQL operations have been reworked to allow for queries using `ContainsIgnoreCase`, `EndsWithIgnoreCase` and other `IgnoreCase` operations. This change is pending approval and might be reworked. See https://github.com/zitadel/zitadel/pull/10704#discussion_r2392886177 # Additional Context `UpdateOrganization` is partially working because the logic for handling domain updates is not done (there is only a draft of the commanders to showcase the call chain). See https://github.com/zitadel/zitadel/pull/10704#discussion_r2356143178 Permissions are not implemented as they are missing. See https://github.com/zitadel/zitadel/pull/10771 - Closes: #10445 --------- Co-authored-by: Silvan <27845747+adlerhurst@users.noreply.github.com> --- backend/v3/api/object/object.go | 55 + backend/v3/api/object/object_test.go | 77 + backend/v3/api/org/v2/convert/org.go | 155 ++ backend/v3/api/org/v2/convert/org_test.go | 89 ++ backend/v3/api/org/v2/org.go | 151 ++ backend/v3/domain/command.go | 148 -- backend/v3/domain/defaults.go | 9 - backend/v3/domain/domain_test.go | 73 +- backend/v3/domain/errors.go | 45 + backend/v3/domain/errors_test.go | 38 + backend/v3/domain/executor_batch.go | 41 + backend/v3/domain/executor_batch_test.go | 45 + backend/v3/domain/invoke.go | 190 +-- backend/v3/domain/invoke_option.go | 113 ++ backend/v3/domain/invoke_test.go | 52 +- backend/v3/domain/invoker_event.go | 106 ++ backend/v3/domain/invoker_logging.go | 34 + backend/v3/domain/invoker_noop.go | 14 + backend/v3/domain/invoker_tracing.go | 30 + backend/v3/domain/invoker_validation.go | 31 + backend/v3/domain/mock/commander.mock.go | 196 +++ backend/v3/domain/mock/org.mock.go | 860 ++++++++++++ backend/v3/domain/mock/org_domain.mock.go | 899 ++++++++++++ backend/v3/domain/mock/org_domain_repo.go | 48 + backend/v3/domain/mock/org_repo.go | 69 + backend/v3/domain/mock/project.mock.go | 1241 +++++++++++++++++ backend/v3/domain/mock/project_repo.go | 47 + backend/v3/domain/mock/querier.mock.go | 194 +++ backend/v3/domain/org_activate.go | 98 ++ backend/v3/domain/org_activate_test.go | 291 ++++ backend/v3/domain/org_deactivate.go | 96 ++ backend/v3/domain/org_deactivate_test.go | 270 ++++ backend/v3/domain/org_delete.go | 192 +++ backend/v3/domain/org_delete_test.go | 503 +++++++ backend/v3/domain/org_domain_add.go | 47 + backend/v3/domain/org_domain_remove.go | 56 + backend/v3/domain/org_domain_set.go | 45 + backend/v3/domain/org_list.go | 120 ++ backend/v3/domain/org_list_test.go | 337 +++++ backend/v3/domain/org_update.go | 147 ++ backend/v3/domain/org_update_test.go | 492 +++++++ backend/v3/domain/organization.go | 20 +- backend/v3/domain/organization_domain.go | 2 + backend/v3/domain/orgstate_enumer.go | 32 +- backend/v3/domain/project.go | 2 + backend/v3/storage/database/change.go | 51 +- backend/v3/storage/database/column.go | 53 + backend/v3/storage/database/condition.go | 115 +- backend/v3/storage/database/condition_test.go | 8 + .../storage/database/dbmock/query_option.go | 15 + .../storage/database/dialect/noop/client.go | 46 + .../v3/storage/database/dialect/noop/pool.go | 51 + .../v3/storage/database/dialect/noop/row.go | 36 + .../database/dialect/noop/transaction.go | 46 + backend/v3/storage/database/operators.go | 61 +- .../v3/storage/database/operators_enumer.go | 40 +- backend/v3/storage/database/operators_test.go | 177 +++ backend/v3/storage/database/query.go | 65 + cmd/start/start.go | 5 + internal/api/authz/context_mock.go | 11 +- .../org/v2/integration_test/query_test.go | 7 + internal/api/grpc/org/v2/query.go | 5 + .../org/v2beta/integration_test/org_test.go | 686 ++++----- internal/api/grpc/org/v2beta/org.go | 22 + internal/command/org.go | 1 + internal/command/org_domain.go | 40 +- internal/integration/feature.go | 20 + 67 files changed, 8592 insertions(+), 769 deletions(-) create mode 100644 backend/v3/api/object/object.go create mode 100644 backend/v3/api/object/object_test.go create mode 100644 backend/v3/api/org/v2/convert/org.go create mode 100644 backend/v3/api/org/v2/convert/org_test.go delete mode 100644 backend/v3/domain/command.go create mode 100644 backend/v3/domain/errors_test.go create mode 100644 backend/v3/domain/executor_batch.go create mode 100644 backend/v3/domain/executor_batch_test.go create mode 100644 backend/v3/domain/invoke_option.go create mode 100644 backend/v3/domain/invoker_event.go create mode 100644 backend/v3/domain/invoker_logging.go create mode 100644 backend/v3/domain/invoker_noop.go create mode 100644 backend/v3/domain/invoker_tracing.go create mode 100644 backend/v3/domain/invoker_validation.go create mode 100644 backend/v3/domain/mock/commander.mock.go create mode 100644 backend/v3/domain/mock/org.mock.go create mode 100644 backend/v3/domain/mock/org_domain.mock.go create mode 100644 backend/v3/domain/mock/org_domain_repo.go create mode 100644 backend/v3/domain/mock/org_repo.go create mode 100644 backend/v3/domain/mock/project.mock.go create mode 100644 backend/v3/domain/mock/project_repo.go create mode 100644 backend/v3/domain/mock/querier.mock.go create mode 100644 backend/v3/domain/org_activate.go create mode 100644 backend/v3/domain/org_activate_test.go create mode 100644 backend/v3/domain/org_deactivate.go create mode 100644 backend/v3/domain/org_deactivate_test.go create mode 100644 backend/v3/domain/org_delete.go create mode 100644 backend/v3/domain/org_delete_test.go create mode 100644 backend/v3/domain/org_domain_add.go create mode 100644 backend/v3/domain/org_domain_remove.go create mode 100644 backend/v3/domain/org_domain_set.go create mode 100644 backend/v3/domain/org_list.go create mode 100644 backend/v3/domain/org_list_test.go create mode 100644 backend/v3/domain/org_update.go create mode 100644 backend/v3/domain/org_update_test.go create mode 100644 backend/v3/storage/database/dbmock/query_option.go create mode 100644 backend/v3/storage/database/dialect/noop/client.go create mode 100644 backend/v3/storage/database/dialect/noop/pool.go create mode 100644 backend/v3/storage/database/dialect/noop/row.go create mode 100644 backend/v3/storage/database/dialect/noop/transaction.go 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)}, + }, + } +}