feat(domain): Organization APIs with relational tables (#10704)

# 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 <[email protected]>
This commit is contained in:
Marco A.
2025-10-21 15:27:38 +02:00
committed by GitHub
co-authored by Silvan
parent 88213d785a
commit f04d873270
67 changed files with 8592 additions and 769 deletions
+55
View File
@@ -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")
}
}
+77
View File
@@ -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)
})
}
}
+155
View File
@@ -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(),
}
}
+89
View File
@@ -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)
})
}
}
+151
View File
@@ -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, &notFoundError) {
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
}
-148
View File
@@ -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)
-9
View File
@@ -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
// }
+9 -64
View File
@@ -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", "[email protected]", NewEmailVerifiedCommand("u1", true)))
// assert.NoError(t, err)
// })
// t.Run("unverified email", func(t *testing.T) {
// err := Invoke(ctx, NewSetEmailCommand("u2", "[email protected]", NewEmailVerifiedCommand("u2", false)))
// assert.NoError(t, err)
// })
// }
func TestMain(m *testing.M) {
os.Exit(func() int {
return m.Run()
}())
}
+45
View File
@@ -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)
}
+38
View File
@@ -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())
})
}
}
+41
View File
@@ -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)
+45
View File
@@ -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())
}
+46 -144
View File
@@ -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
// }
+113
View File
@@ -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
}
}
+38 -14
View File
@@ -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,
}
+106
View File
@@ -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
}
+34
View File
@@ -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
}
+14
View File
@@ -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)
}
+30
View File
@@ -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)
}
+31
View File
@@ -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)
}
+196
View File
@@ -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
}
+860
View File
@@ -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
}
+899
View File
@@ -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
}
+48
View File
@@ -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)
}
+69
View File
@@ -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()
}
File diff suppressed because it is too large Load Diff
+47
View File
@@ -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)
}
+194
View File
@@ -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
}
+98
View File
@@ -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, &notFoundError) {
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
}
+291
View File
@@ -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)
})
}
}
+96
View File
@@ -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, &notFoundError) {
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
}
+270
View File
@@ -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)
})
}
}
+192
View File
@@ -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)
+503
View File
@@ -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)
})
}
}
+47
View File
@@ -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)
+56
View File
@@ -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)
+45
View File
@@ -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)
+120
View File
@@ -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)
+337
View File
@@ -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())
})
}
}
+147
View File
@@ -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
}
+492
View File
@@ -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)
})
}
}
+19 -1
View File
@@ -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 ""
}
+2
View File
@@ -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
+20 -12
View File
@@ -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.
+2
View File
@@ -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
+49 -2
View File
@@ -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)
+53
View File
@@ -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 (
+112 -3
View File
@@ -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 <table> WHERE <condition>) 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{
@@ -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"),
@@ -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
}
@@ -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)
@@ -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)
@@ -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)
@@ -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)
+59 -2
View File
@@ -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))
}
}
+30 -10
View File
@@ -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.
@@ -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)
})
}
}
+65
View File
@@ -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)
+5
View File
@@ -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 {
+10 -1
View File
@@ -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)
}
@@ -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
+5
View File
@@ -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
@@ -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)
}
})
}
}
}
+22
View File
@@ -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
+1
View File
@@ -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")
}
+20 -20
View File
@@ -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
}
+20
View File
@@ -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)},
},
}
}