feat(domain): Instance APIs with relational tables (#10953)

# Which Problems Are Solved

This PR implements the endpoints listed in
https://github.com/zitadel/zitadel/issues/10443 .

# How the Problems Are Solved

The implementation follows the same pattern as the Organization one.
There are no peculiarities to this PR. The `Update`, `Delete` are
implementing the `Commander` interface, while `Get` and `List`
instance(s) endpoints are implementing the `Querier` interace.

# Additional Context

- Closes #10443
- Depends on #10445
This commit is contained in:
Marco A.
2025-10-28 14:17:19 +00:00
committed by GitHub
parent ad8e8bf61f
commit 39a29f534a
28 changed files with 4448 additions and 196 deletions
@@ -0,0 +1,52 @@
package convert
import (
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/backend/v3/domain"
"github.com/zitadel/zitadel/cmd/build"
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
)
/*
* Domain Model to GRPC v2
*/
func DomainInstanceListModelToGRPCResponse(instances []*domain.Instance) []*instance.Instance {
toReturn := make([]*instance.Instance, len(instances))
for i, inst := range instances {
toReturn[i] = DomainInstanceModelToGRPCResponse(inst)
}
return toReturn
}
func DomainInstanceModelToGRPCResponse(inst *domain.Instance) *instance.Instance {
return &instance.Instance{
Id: inst.ID,
ChangeDate: timestamppb.New(inst.UpdatedAt),
CreationDate: timestamppb.New(inst.CreatedAt),
State: instance.State_STATE_RUNNING, // TODO(IAM-Marco): Not sure what to put here
Name: inst.Name,
Version: build.Version(),
Domains: domainInstanceDomainListModelToGRPCResponse(inst.Domains),
}
}
func domainInstanceDomainListModelToGRPCResponse(dms []*domain.InstanceDomain) []*instance.Domain {
toReturn := make([]*instance.Domain, len(dms))
for i, domain := range dms {
isGenerated := domain.IsGenerated != nil && *domain.IsGenerated
isPrimary := domain.IsPrimary != nil && *domain.IsPrimary
toReturn[i] = &instance.Domain{
InstanceId: domain.InstanceID,
CreationDate: timestamppb.New(domain.CreatedAt),
Domain: domain.Domain,
Primary: isPrimary,
Generated: isGenerated,
}
}
return toReturn
}
@@ -0,0 +1,204 @@
package convert
import (
"testing"
"time"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/backend/v3/domain"
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
)
func TestDomainInstanceModelToGRPCResponse(t *testing.T) {
// Given
t.Parallel()
now := time.Now().UTC()
yesterday := now.AddDate(0, 0, -1)
inputInstance := &domain.Instance{
ID: "instance-1",
Name: "Instance One",
CreatedAt: yesterday,
UpdatedAt: now,
Domains: []*domain.InstanceDomain{
{
InstanceID: "instance-1",
Domain: "d1.example.com",
IsPrimary: gu.Ptr(true),
CreatedAt: yesterday,
},
{
InstanceID: "instance-1",
Domain: "d2.example.com",
IsGenerated: gu.Ptr(true),
CreatedAt: yesterday,
},
{
InstanceID: "instance-1",
Domain: "d3.example.com",
IsPrimary: gu.Ptr(true),
IsGenerated: gu.Ptr(false),
CreatedAt: yesterday,
},
{
InstanceID: "instance-1",
Domain: "d4.example.com",
IsPrimary: gu.Ptr(false),
IsGenerated: gu.Ptr(true),
CreatedAt: yesterday,
},
},
}
expectedInstance := &instance.Instance{
Id: "instance-1",
ChangeDate: timestamppb.New(now),
CreationDate: timestamppb.New(yesterday),
State: instance.State_STATE_RUNNING,
Name: "Instance One",
Version: "",
Domains: []*instance.Domain{
{
InstanceId: "instance-1",
CreationDate: timestamppb.New(yesterday),
Domain: "d1.example.com",
Primary: true,
Generated: false,
},
{
InstanceId: "instance-1",
CreationDate: timestamppb.New(yesterday),
Domain: "d2.example.com",
Primary: false,
Generated: true,
},
{
InstanceId: "instance-1",
CreationDate: timestamppb.New(yesterday),
Domain: "d3.example.com",
Primary: true,
Generated: false,
},
{
InstanceId: "instance-1",
CreationDate: timestamppb.New(yesterday),
Domain: "d4.example.com",
Primary: false,
Generated: true,
},
},
}
// Test
res := DomainInstanceModelToGRPCResponse(inputInstance)
// Verify
assert.Equal(t, expectedInstance, res)
}
func TestDomainInstanceListModelToGRPCResponse(t *testing.T) {
t.Parallel()
now := time.Now()
tt := []struct {
testName string
inputResult []*domain.Instance
expectedResult []*instance.Instance
}{
{
testName: "empty result",
inputResult: []*domain.Instance{},
expectedResult: []*instance.Instance{},
},
{
testName: "single instance without domains",
inputResult: []*domain.Instance{
{
ID: "instance1",
Name: "test-instance",
CreatedAt: now,
UpdatedAt: now,
Domains: nil,
},
},
expectedResult: []*instance.Instance{
{
Id: "instance1",
Name: "test-instance",
CreationDate: timestamppb.New(now),
ChangeDate: timestamppb.New(now),
State: instance.State_STATE_RUNNING,
Domains: []*instance.Domain{},
},
},
},
{
testName: "multiple instances with domains",
inputResult: []*domain.Instance{
{
ID: "instance1",
Name: "test-instance-1",
CreatedAt: now,
UpdatedAt: now,
Domains: []*domain.InstanceDomain{
{
InstanceID: "instance1",
Domain: "domain1.com",
CreatedAt: now,
IsPrimary: gu.Ptr(true),
IsGenerated: gu.Ptr(false),
},
},
},
{
ID: "instance2",
Name: "test-instance-2",
CreatedAt: now,
UpdatedAt: now,
Domains: nil,
},
},
expectedResult: []*instance.Instance{
{
Id: "instance1",
Name: "test-instance-1",
CreationDate: timestamppb.New(now),
ChangeDate: timestamppb.New(now),
State: instance.State_STATE_RUNNING,
Domains: []*instance.Domain{
{
InstanceId: "instance1",
Domain: "domain1.com",
CreationDate: timestamppb.New(now),
Primary: true,
Generated: false,
},
},
},
{
Id: "instance2",
Name: "test-instance-2",
CreationDate: timestamppb.New(now),
ChangeDate: timestamppb.New(now),
State: instance.State_STATE_RUNNING,
Domains: []*instance.Domain{},
},
},
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
// Test
result := DomainInstanceListModelToGRPCResponse(tc.inputResult)
// Verify
assert.Equal(t, tc.expectedResult, result)
})
}
}
+101
View File
@@ -0,0 +1,101 @@
package instancev2
import (
"context"
"errors"
"connectrpc.com/connect"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/backend/v3/api/instance/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"
"github.com/zitadel/zitadel/internal/zerrors"
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
)
func DeleteInstance(ctx context.Context, request *connect.Request[instance.DeleteInstanceRequest]) (*connect.Response[instance.DeleteInstanceResponse], error) {
instanceDeleteCmd := domain.NewDeleteInstanceCommand(request.Msg.GetInstanceId())
err := domain.Invoke(ctx, instanceDeleteCmd, domain.WithInstanceRepo(repository.InstanceRepository()))
if err != nil {
if errors.Is(err, &database.NoRowFoundError{}) {
return nil, zerrors.ThrowNotFound(err, "INST-QVrUwc", "instance not found")
}
return nil, err
}
return &connect.Response[instance.DeleteInstanceResponse]{
Msg: &instance.DeleteInstanceResponse{
// TODO(IAM-Marco): Change this with the real update date when OrganizationRepo.Update()
// returns the timestamp
DeletionDate: timestamppb.Now(),
},
}, nil
}
func GetInstance(ctx context.Context, request *connect.Request[instance.GetInstanceRequest]) (*connect.Response[instance.GetInstanceResponse], error) {
instanceGetCmd := domain.NewGetInstanceCommand(request.Msg.GetInstanceId())
err := domain.Invoke(ctx, instanceGetCmd, domain.WithInstanceRepo(repository.InstanceRepository()))
if err != nil {
if errors.Is(err, &database.NoRowFoundError{}) {
return nil, zerrors.ThrowNotFound(err, "INST-QVrUwc", "instance not found")
}
return nil, err
}
return &connect.Response[instance.GetInstanceResponse]{
Msg: &instance.GetInstanceResponse{
Instance: convert.DomainInstanceModelToGRPCResponse(instanceGetCmd.Result()),
},
}, nil
}
func UpdateInstance(ctx context.Context, request *connect.Request[instance.UpdateInstanceRequest]) (*connect.Response[instance.UpdateInstanceResponse], error) {
instanceUpdateCmd := domain.NewUpdateInstanceCommand(request.Msg.GetInstanceId(), request.Msg.GetInstanceName())
err := domain.Invoke(ctx, instanceUpdateCmd, domain.WithInstanceRepo(repository.InstanceRepository()))
if err != nil {
return nil, err
}
return &connect.Response[instance.UpdateInstanceResponse]{
Msg: &instance.UpdateInstanceResponse{
// TODO(IAM-Marco): Change this with the real update date when InstanceRepo.Update()
// returns the timestamp
ChangeDate: timestamppb.Now(),
},
}, nil
}
func ListInstances(ctx context.Context, request *connect.Request[instance.ListInstancesRequest]) (*connect.Response[instance.ListInstancesResponse], error) {
instancesListCmd := domain.NewListInstancesCommand(request.Msg)
err := domain.Invoke(
ctx,
instancesListCmd,
domain.WithInstanceRepo(repository.InstanceRepository()),
domain.WithInstanceDomainRepo(repository.InstanceDomainRepository()),
)
if err != nil {
return nil, err
}
instances := instancesListCmd.Result()
return &connect.Response[instance.ListInstancesResponse]{
Msg: &instance.ListInstancesResponse{
Instances: convert.DomainInstanceListModelToGRPCResponse(instances),
Pagination: &filter.PaginationResponse{
// TODO(IAM-Marco): return correct value. Tracked in https://github.com/zitadel/zitadel/issues/10955
TotalResult: uint64(len(instances)),
AppliedLimit: uint64(request.Msg.GetPagination().GetLimit()),
},
},
}, nil
}
-21
View File
@@ -1,21 +0,0 @@
package v2
// this file has been commented out to pass the linter
// import (
// "github.com/zitadel/zitadel/backend/v3/telemetry/logging"
// "github.com/zitadel/zitadel/backend/v3/telemetry/tracing"
// )
// var (
// logger logging.Logger
// tracer tracing.Tracer
// )
// func SetLogger(l logging.Logger) {
// logger = l
// }
// func SetTracer(t tracing.Tracer) {
// tracer = t
// }
-21
View File
@@ -1,21 +0,0 @@
package orgv2
// this file has been commented out to pass the linter
// import (
// "github.com/zitadel/zitadel/backend/v3/telemetry/logging"
// "github.com/zitadel/zitadel/backend/v3/telemetry/tracing"
// )
// var (
// logger logging.Logger
// tracer tracing.Tracer
// )
// func SetLogger(l logging.Logger) {
// logger = l
// }
// func SetTracer(t tracing.Tracer) {
// tracer = t
// }
+2
View File
@@ -94,6 +94,8 @@ type instanceChanges interface {
}
// InstanceRepository is the interface for the instance repository.
//
//go:generate mockgen -typed -package domainmock -destination ./mock/instance.mock.go . InstanceRepository
type InstanceRepository interface {
Repository
+97
View File
@@ -0,0 +1,97 @@
package domain
import (
"context"
"strings"
"github.com/zitadel/zitadel/backend/v3/storage/database"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/milestone"
"github.com/zitadel/zitadel/internal/zerrors"
)
type DeleteInstanceCommand struct {
ID string `json:"id"`
// InstanceDomains is public for testing purposes
// do not use this field
InstanceDomains []string
// InstanceName is public for testing purposes
// do not use this field
InstanceName string
}
// RequiresTransaction implements [Transactional].
func (d *DeleteInstanceCommand) RequiresTransaction() {}
func NewDeleteInstanceCommand(instanceID string) *DeleteInstanceCommand {
return &DeleteInstanceCommand{ID: instanceID}
}
// Events implements [Commander].
func (d *DeleteInstanceCommand) Events(ctx context.Context, opts *InvokeOpts) ([]eventstore.Command, error) {
milestoneAggregate := milestone.NewInstanceAggregate(d.ID)
instanceAggregate := instance.NewAggregate(d.ID)
return []eventstore.Command{
instance.NewInstanceRemovedEvent(ctx, &instanceAggregate.Aggregate, d.InstanceName, d.InstanceDomains),
milestone.NewReachedEvent(ctx, milestoneAggregate, milestone.InstanceDeleted),
}, nil
}
// Execute implements [Commander].
func (d *DeleteInstanceCommand) Execute(ctx context.Context, opts *InvokeOpts) (err error) {
instanceRepo := opts.instanceRepo.LoadDomains()
instanceToDelete, err := instanceRepo.Get(ctx, opts.DB(), database.WithCondition(instanceRepo.IDCondition(d.ID)))
if err != nil {
return err
}
d.InstanceDomains = make([]string, len(instanceToDelete.Domains))
for i, domain := range instanceToDelete.Domains {
d.InstanceDomains[i] = domain.Domain
}
d.InstanceName = instanceToDelete.Name
deletedRows, err := instanceRepo.Delete(ctx, opts.DB(), instanceToDelete.ID)
if err != nil {
return err
}
if deletedRows > 1 {
err = zerrors.ThrowInternalf(nil, "DOM-Od04Jx", "expecting 1 row deleted, got %d", deletedRows)
return err
}
if deletedRows < 1 {
err = zerrors.ThrowNotFound(nil, "DOM-daglwD", "instance not found")
}
return err
}
// String implements [Commander].
func (d *DeleteInstanceCommand) String() string {
return "DeleteInstanceCommand"
}
// Validate implements [Commander].
func (d *DeleteInstanceCommand) Validate(ctx context.Context, opts *InvokeOpts) (err error) {
if d.ID = strings.TrimSpace(d.ID); d.ID == "" {
return zerrors.ThrowInvalidArgument(nil, "DOM-VpQ9lF", "Errors.Invalid.Argument")
}
if authZErr := opts.Permissions.CheckInstancePermission(ctx, InstanceWritePermission); authZErr != nil {
return zerrors.ThrowPermissionDenied(authZErr, "DOM-Yz8f1X", "permission denied")
}
return nil
}
var (
_ Commander = (*DeleteInstanceCommand)(nil)
_ Transactional = (*DeleteInstanceCommand)(nil)
)
+340
View File
@@ -0,0 +1,340 @@
package domain_test
import (
"context"
"errors"
"testing"
"github.com/brianvoe/gofakeit/v6"
"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/eventstore"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/milestone"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestDeleteInstanceCommand_Validate(t *testing.T) {
t.Parallel()
permissionErr := errors.New("permission error")
tt := []struct {
name string
permissionChecker func(ctrl *gomock.Controller) domain.PermissionChecker
inputID string
expectedError error
}{
{
name: "empty id",
inputID: "",
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-VpQ9lF", "Errors.Invalid.Argument"),
},
{
name: "whitespace id",
inputID: " ",
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-VpQ9lF", "Errors.Invalid.Argument"),
},
{
name: "when user is missing permission should return permission denied",
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
permChecker := domainmock.NewMockPermissionChecker(ctrl)
permChecker.EXPECT().
CheckInstancePermission(gomock.Any(), domain.InstanceWritePermission).
Times(1).
Return(permissionErr)
return permChecker
},
expectedError: zerrors.ThrowPermissionDenied(permissionErr, "DOM-Yz8f1X", "permission denied"),
inputID: "instance-1",
},
{
name: "valid id",
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
permChecker := domainmock.NewMockPermissionChecker(ctrl)
permChecker.EXPECT().
CheckInstancePermission(gomock.Any(), domain.InstanceWritePermission).
Times(1).
Return(nil)
return permChecker
},
inputID: "instance-1",
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// Given
d := &domain.DeleteInstanceCommand{ID: tc.inputID}
cmdOpts := &domain.InvokeOpts{}
if tc.permissionChecker != nil {
ctrl := gomock.NewController(t)
cmdOpts.Permissions = tc.permissionChecker(ctrl)
}
// Test
err := d.Validate(context.Background(), cmdOpts)
// Verify
assert.Equal(t, tc.expectedError, err)
})
}
}
func TestDeleteInstanceCommand_Events(t *testing.T) {
t.Parallel()
// Given
cmd := domain.NewDeleteInstanceCommand("instance-1")
expectedEvents := []eventstore.Command{
instance.NewInstanceRemovedEvent(context.Background(), &instance.NewAggregate("instance-1").Aggregate, "instance-1", []string{}),
milestone.NewReachedEvent(context.Background(), milestone.NewInstanceAggregate("instance-1"), milestone.InstanceDeleted),
}
// Test
events, err := cmd.Events(context.Background(), nil)
// Verify
assert.NoError(t, err)
assert.Len(t, events, len(expectedEvents))
assert.IsType(t, &instance.InstanceRemovedEvent{}, events[0])
assert.IsType(t, &milestone.ReachedEvent{}, events[1])
}
func TestDeleteInstanceCommand_Execute(t *testing.T) {
t.Parallel()
ctx := authz.NewMockContext("inst-1", "org-1", gofakeit.UUID())
deleteErr := errors.New("delete error")
getErr := errors.New("get error")
tests := []struct {
testName string
instanceRepo func(ctrl *gomock.Controller) domain.InstanceRepository
inputInstanceID string
expectedError error
expectedInstanceName string
expectedInstanceDomains []string
}{
{
testName: "when retrieving instance fails should return error",
instanceRepo: func(ctrl *gomock.Controller) domain.InstanceRepository {
instanceRepo := domainmock.NewInstanceRepo(ctrl)
instanceRepo.EXPECT().
LoadDomains().
Times(1).
Return(instanceRepo)
instanceRepo.EXPECT().
Get(
gomock.Any(),
gomock.Any(),
dbmock.QueryOptions(database.WithCondition(
instanceRepo.IDCondition("instance-1"),
)),
).
Times(1).
Return(nil, getErr)
return instanceRepo
},
inputInstanceID: "instance-1",
expectedError: getErr,
},
{
testName: "when delete instance fails should return error",
instanceRepo: func(ctrl *gomock.Controller) domain.InstanceRepository {
instanceRepo := domainmock.NewInstanceRepo(ctrl)
instanceRepo.EXPECT().
LoadDomains().
Times(1).
Return(instanceRepo)
instanceRepo.EXPECT().
Get(
gomock.Any(),
gomock.Any(),
dbmock.QueryOptions(database.WithCondition(
instanceRepo.IDCondition("instance-1"),
)),
).
Times(1).
Return(&domain.Instance{
ID: "instance-1",
Name: "My instance 1",
Domains: []*domain.InstanceDomain{
{Domain: "d1.example.com"},
{Domain: "d2.example.com"},
{Domain: "d3.example.com"},
},
}, nil)
instanceRepo.EXPECT().
Delete(gomock.Any(), gomock.Any(), "instance-1").
Times(1).
Return(int64(0), deleteErr)
return instanceRepo
},
inputInstanceID: "instance-1",
expectedError: deleteErr,
expectedInstanceName: "My instance 1",
expectedInstanceDomains: []string{"d1.example.com", "d2.example.com", "d3.example.com"},
},
{
testName: "when more than one row deleted should return internal error",
instanceRepo: func(ctrl *gomock.Controller) domain.InstanceRepository {
instanceRepo := domainmock.NewInstanceRepo(ctrl)
instanceRepo.EXPECT().
LoadDomains().
Times(1).
Return(instanceRepo)
instanceRepo.EXPECT().
Get(
gomock.Any(),
gomock.Any(),
dbmock.QueryOptions(database.WithCondition(
instanceRepo.IDCondition("instance-1"),
)),
).
Times(1).
Return(&domain.Instance{
ID: "instance-1",
Name: "My instance 1",
Domains: []*domain.InstanceDomain{
{Domain: "d1.example.com"},
{Domain: "d2.example.com"},
{Domain: "d3.example.com"},
},
}, nil)
instanceRepo.EXPECT().
Delete(gomock.Any(), gomock.Any(), "instance-1").
Times(1).
Return(int64(2), nil)
return instanceRepo
},
inputInstanceID: "instance-1",
expectedError: zerrors.ThrowInternalf(nil, "DOM-Od04Jx", "expecting 1 row deleted, got %d", 2),
expectedInstanceName: "My instance 1",
expectedInstanceDomains: []string{"d1.example.com", "d2.example.com", "d3.example.com"},
},
{
testName: "when no rows deleted should return not found error",
instanceRepo: func(ctrl *gomock.Controller) domain.InstanceRepository {
instanceRepo := domainmock.NewInstanceRepo(ctrl)
instanceRepo.EXPECT().
LoadDomains().
Times(1).
Return(instanceRepo)
instanceRepo.EXPECT().
Get(
gomock.Any(),
gomock.Any(),
dbmock.QueryOptions(database.WithCondition(
instanceRepo.IDCondition("instance-1"),
)),
).
Times(1).
Return(&domain.Instance{
ID: "instance-1",
Name: "My instance 1",
Domains: []*domain.InstanceDomain{
{Domain: "d1.example.com"},
{Domain: "d2.example.com"},
{Domain: "d3.example.com"},
},
}, nil)
instanceRepo.EXPECT().
Delete(gomock.Any(), gomock.Any(), "instance-1").
Times(1).
Return(int64(0), nil)
return instanceRepo
},
inputInstanceID: "instance-1",
expectedError: zerrors.ThrowNotFound(nil, "DOM-daglwD", "instance not found"),
expectedInstanceName: "My instance 1",
expectedInstanceDomains: []string{"d1.example.com", "d2.example.com", "d3.example.com"},
},
{
testName: "when one row deleted should execute successfully",
instanceRepo: func(ctrl *gomock.Controller) domain.InstanceRepository {
instanceRepo := domainmock.NewInstanceRepo(ctrl)
instanceRepo.EXPECT().
LoadDomains().
Times(1).
Return(instanceRepo)
instanceRepo.EXPECT().
Get(
gomock.Any(),
gomock.Any(),
dbmock.QueryOptions(database.WithCondition(
instanceRepo.IDCondition("instance-1"),
)),
).
Times(1).
Return(&domain.Instance{
ID: "instance-1",
Name: "My instance 1",
Domains: []*domain.InstanceDomain{
{Domain: "d1.example.com"},
{Domain: "d2.example.com"},
{Domain: "d3.example.com"},
},
}, nil)
instanceRepo.EXPECT().
Delete(gomock.Any(), gomock.Any(), "instance-1").
Times(1).
Return(int64(1), nil)
return instanceRepo
},
inputInstanceID: "instance-1",
expectedInstanceName: "My instance 1",
expectedInstanceDomains: []string{"d1.example.com", "d2.example.com", "d3.example.com"},
},
}
for _, tc := range tests {
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
// Given
d := domain.NewDeleteInstanceCommand(tc.inputInstanceID)
ctrl := gomock.NewController(t)
opts := &domain.InvokeOpts{
Invoker: domain.NewTransactionInvoker(nil),
}
domain.WithQueryExecutor(new(noopdb.Pool))(opts)
if tc.instanceRepo != nil {
domain.WithInstanceRepo(tc.instanceRepo(ctrl))(opts)
}
// Test
err := d.Execute(ctx, opts)
// Verify
assert.Equal(t, tc.expectedError, err)
assert.Equal(t, tc.expectedInstanceName, d.InstanceName)
assert.ElementsMatch(t, tc.expectedInstanceDomains, d.InstanceDomains)
})
}
}
+1
View File
@@ -59,6 +59,7 @@ type instanceDomainChanges interface {
SetType(typ DomainType) database.Change
}
//go:generate mockgen -typed -package domainmock -destination ./mock/instance_domain.mock.go . InstanceDomainRepository
type InstanceDomainRepository interface {
Repository
+61
View File
@@ -0,0 +1,61 @@
package domain
import (
"context"
"strings"
"github.com/zitadel/zitadel/backend/v3/storage/database"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/zerrors"
)
type GetInstanceQuery struct {
ID string `json:"id"`
returnedInstance *Instance
}
// Result implements [Querier].
func (g *GetInstanceQuery) Result() *Instance {
return g.returnedInstance
}
func NewGetInstanceCommand(instanceID string) *GetInstanceQuery {
return &GetInstanceQuery{ID: instanceID}
}
// Execute implements [Executor].
func (g *GetInstanceQuery) Execute(ctx context.Context, opts *InvokeOpts) (err error) {
instanceRepo := opts.instanceRepo.LoadDomains()
instance, err := instanceRepo.Get(ctx, opts.DB(), database.WithCondition(instanceRepo.IDCondition(g.ID)))
if err != nil {
return err
}
g.returnedInstance = instance
return nil
}
// String implements [Executor].
func (g *GetInstanceQuery) String() string {
return "GetInstanceCommand"
}
// Validate implements [Validator].
func (g *GetInstanceQuery) Validate(ctx context.Context, opts *InvokeOpts) (err error) {
g.ID = strings.TrimSpace(g.ID)
if g.ID == "" {
return zerrors.ThrowInvalidArgument(nil, "DOM-32a0o2", "invalid instance ID")
}
if g.ID != authz.GetInstance(ctx).InstanceID() {
return zerrors.ThrowPermissionDenied(nil, "DOM-n0SvVB", "input instance ID doesn't match context instance")
}
if authZErr := opts.Permissions.CheckInstancePermission(ctx, InstanceReadPermission); authZErr != nil {
return zerrors.ThrowPermissionDenied(authZErr, "DOM-Uq6b00", "permission denied")
}
return nil
}
var _ Querier[*Instance] = (*GetInstanceQuery)(nil)
+213
View File
@@ -0,0 +1,213 @@
package domain_test
import (
"context"
"errors"
"testing"
"github.com/brianvoe/gofakeit/v6"
"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"
)
func TestGetInstanceCommand_Validate(t *testing.T) {
t.Parallel()
permissionErr := errors.New("permission error")
tests := []struct {
name string
permissionChecker func(ctrl *gomock.Controller) domain.PermissionChecker
inputInstanceID string
ctx context.Context
expectedError error
}{
{
name: "empty instance id, error",
inputInstanceID: "",
ctx: context.Background(),
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-32a0o2", "invalid instance ID"),
},
{
name: "whitespace instance id, error",
inputInstanceID: " ",
ctx: context.Background(),
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-32a0o2", "invalid instance ID"),
},
{
name: "instance id mismatch, error",
inputInstanceID: "instance1",
ctx: authz.NewMockContext("instance2", "", ""),
expectedError: zerrors.ThrowPermissionDenied(nil, "DOM-n0SvVB", "input instance ID doesn't match context instance"),
},
{
name: "when user is missing permission should return permission denied",
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
permChecker := domainmock.NewMockPermissionChecker(ctrl)
permChecker.EXPECT().
CheckInstancePermission(gomock.Any(), domain.InstanceReadPermission).
Times(1).
Return(permissionErr)
return permChecker
},
inputInstanceID: "instance1",
ctx: authz.NewMockContext("instance1", "", ""),
expectedError: zerrors.ThrowPermissionDenied(permissionErr, "DOM-Uq6b00", "permission denied"),
},
{
name: "valid instance id, success",
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
permChecker := domainmock.NewMockPermissionChecker(ctrl)
permChecker.EXPECT().
CheckInstancePermission(gomock.Any(), domain.InstanceReadPermission).
Times(1).
Return(nil)
return permChecker
},
inputInstanceID: "instance1",
ctx: authz.NewMockContext("instance1", "", ""),
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// Given
cmd := domain.NewGetInstanceCommand(tt.inputInstanceID)
cmdOpts := &domain.InvokeOpts{}
if tt.permissionChecker != nil {
ctrl := gomock.NewController(t)
cmdOpts.Permissions = tt.permissionChecker(ctrl)
}
// Test
err := cmd.Validate(tt.ctx, cmdOpts)
// Verify
assert.Equal(t, tt.expectedError, err)
})
}
}
func TestGetInstanceCommand_Execute(t *testing.T) {
t.Parallel()
ctx := authz.NewMockContext("inst-1", "org-1", gofakeit.UUID())
getErr := errors.New("get error")
tt := []struct {
testName string
instanceRepo func(ctrl *gomock.Controller) domain.InstanceRepository
inputInstanceID string
expectedError error
expectedInstance *domain.Instance
}{
{
testName: "when retrieving instance fails should return error",
instanceRepo: func(ctrl *gomock.Controller) domain.InstanceRepository {
instanceRepo := domainmock.NewInstanceRepo(ctrl)
instanceRepo.EXPECT().
LoadDomains().
Times(1).
Return(instanceRepo)
instanceRepo.EXPECT().
Get(
gomock.Any(),
gomock.Any(),
dbmock.QueryOptions(database.WithCondition(
instanceRepo.IDCondition("instance-1"),
)),
).
Times(1).
Return(nil, getErr)
return instanceRepo
},
inputInstanceID: "instance-1",
expectedError: getErr,
},
{
testName: "when retrieving instance succeeds should set instance on struct and return no error",
instanceRepo: func(ctrl *gomock.Controller) domain.InstanceRepository {
instanceRepo := domainmock.NewInstanceRepo(ctrl)
instanceRepo.EXPECT().
LoadDomains().
Times(1).
Return(instanceRepo)
instanceRepo.EXPECT().
Get(
gomock.Any(),
gomock.Any(),
dbmock.QueryOptions(database.WithCondition(
instanceRepo.IDCondition("instance-1"),
)),
).
Times(1).
Return(&domain.Instance{
ID: "instance-1",
Name: "My instance 1",
Domains: []*domain.InstanceDomain{
{Domain: "d1.example.com"},
{Domain: "d2.example.com"},
{Domain: "d3.example.com"},
},
}, nil)
return instanceRepo
},
inputInstanceID: "instance-1",
expectedInstance: &domain.Instance{
ID: "instance-1",
Name: "My instance 1",
Domains: []*domain.InstanceDomain{
{Domain: "d1.example.com"},
{Domain: "d2.example.com"},
{Domain: "d3.example.com"},
},
},
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
// Given
g := domain.NewGetInstanceCommand(tc.inputInstanceID)
ctrl := gomock.NewController(t)
opts := &domain.InvokeOpts{
Invoker: domain.NewTransactionInvoker(nil),
}
domain.WithQueryExecutor(new(noopdb.Pool))(opts)
if tc.instanceRepo != nil {
domain.WithInstanceRepo(tc.instanceRepo(ctrl))(opts)
}
// Test
err := g.Execute(ctx, opts)
// Verify
assert.Equal(t, tc.expectedError, err)
assert.Equal(t, tc.expectedInstance, g.Result())
})
}
}
+122
View File
@@ -0,0 +1,122 @@
package domain
import (
"context"
"github.com/zitadel/zitadel/backend/v3/storage/database"
"github.com/zitadel/zitadel/internal/zerrors"
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
)
type ListInstancesQuery struct {
Request *instance.ListInstancesRequest
toReturn []*Instance
}
// Result implements Querier.
func (l *ListInstancesQuery) Result() []*Instance {
return l.toReturn
}
var _ Querier[[]*Instance] = (*ListInstancesQuery)(nil)
func NewListInstancesCommand(inputRequest *instance.ListInstancesRequest) *ListInstancesQuery {
return &ListInstancesQuery{
Request: inputRequest,
}
}
// Execute implements [Querier].
func (l *ListInstancesQuery) Execute(ctx context.Context, opts *InvokeOpts) (err error) {
instanceRepo := opts.instanceRepo.LoadDomains()
domainRepo := opts.instanceDomainRepo
sorting := l.Sorting(instanceRepo)
limit, offset := l.Pagination()
conds, err := l.conditions(instanceRepo, domainRepo)
if err != nil {
return err
}
instances, err := instanceRepo.List(ctx, opts.DB(), conds, sorting, limit, offset)
if err != nil {
return err
}
l.toReturn = instances
return nil
}
func (l *ListInstancesQuery) Pagination() (database.QueryOption, database.QueryOption) {
return database.WithLimit(l.Request.GetPagination().GetLimit()),
database.WithOffset(uint32(l.Request.GetPagination().GetOffset()))
}
func (l *ListInstancesQuery) conditions(instanceRepo InstanceRepository, domainRepo InstanceDomainRepository) (database.QueryOption, error) {
conditions := make([]database.Condition, len(l.Request.GetQueries()))
for i, query := range l.Request.GetQueries() {
switch assertedType := query.GetQuery().(type) {
case *instance.Query_DomainQuery:
domainConditions := make([]database.Condition, len(assertedType.DomainQuery.GetDomains()))
for j, domain := range assertedType.DomainQuery.GetDomains() {
domainConditions[j] = domainRepo.DomainCondition(database.TextOperationEqual, domain)
}
conditions[i] = instanceRepo.ExistsDomain(
database.Or(domainConditions...),
)
case *instance.Query_IdQuery:
idConditions := make([]database.Condition, len(assertedType.IdQuery.GetIds()))
for j, id := range assertedType.IdQuery.GetIds() {
idConditions[j] = instanceRepo.IDCondition(id)
}
conditions[i] = database.Or(idConditions...)
default:
return nil, zerrors.ThrowInvalidArgument(NewUnexpectedQueryTypeError(assertedType), "DOM-AU99kR", "List.Query.Invalid")
}
}
return database.WithCondition(database.And(conditions...)), nil
}
func (l *ListInstancesQuery) Sorting(instanceRepo InstanceRepository) database.QueryOption {
var sortingCol database.Column
switch l.Request.GetSortingColumn() {
case instance.FieldName_FIELD_NAME_CREATION_DATE:
sortingCol = instanceRepo.CreatedAtColumn()
case instance.FieldName_FIELD_NAME_ID:
sortingCol = instanceRepo.IDColumn()
case instance.FieldName_FIELD_NAME_NAME:
sortingCol = instanceRepo.NameColumn()
case instance.FieldName_FIELD_NAME_UNSPECIFIED:
fallthrough
default:
return func(opts *database.QueryOpts) {}
}
orderDirection := database.OrderDirectionDesc
if l.Request.GetPagination().GetAsc() {
orderDirection = database.OrderDirectionAsc
}
return database.WithOrderBy(orderDirection, sortingCol)
}
// String implements [Querier].
func (l *ListInstancesQuery) String() string {
return "ListInstancesCommand"
}
// Validate implements [Querier].
func (l *ListInstancesQuery) Validate(ctx context.Context, opts *InvokeOpts) (err error) {
// TODO(IAM-Marco): This is likely wrong, because it's checking the read permission of
// the instance in context. In here, instead we should probably loop through all retrieved
// instances and check their permissions.
if authZErr := opts.Permissions.CheckInstancePermission(ctx, InstanceReadPermission); authZErr != nil {
return zerrors.ThrowPermissionDenied(authZErr, "DOM-cuT6Ws", "permission denied")
}
return nil
}
+328
View File
@@ -0,0 +1,328 @@
package domain_test
import (
"context"
"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"
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
)
func TestListInstancesCommand_Sorting(t *testing.T) {
t.Parallel()
tt := []struct {
name string
request *instance.ListInstancesRequest
expectedSortingDirection database.OrderDirection
expectedOrderBy database.Columns
}{
{
name: "sort by creation date DESC",
request: &instance.ListInstancesRequest{
SortingColumn: instance.FieldName_FIELD_NAME_CREATION_DATE.Enum(),
},
expectedSortingDirection: database.OrderDirectionDesc,
expectedOrderBy: database.Columns{database.NewColumn("instances", "created_at")},
},
{
name: "sort by creation date ASC",
request: &instance.ListInstancesRequest{
SortingColumn: instance.FieldName_FIELD_NAME_CREATION_DATE.Enum(),
Pagination: &filter.PaginationRequest{Asc: true},
},
expectedSortingDirection: database.OrderDirectionAsc,
expectedOrderBy: database.Columns{database.NewColumn("instances", "created_at")},
},
{
name: "sort by id DESC",
request: &instance.ListInstancesRequest{
SortingColumn: instance.FieldName_FIELD_NAME_ID.Enum(),
},
expectedSortingDirection: database.OrderDirectionDesc,
expectedOrderBy: database.Columns{database.NewColumn("instances", "id")},
},
{
name: "sort by id ASC",
request: &instance.ListInstancesRequest{
SortingColumn: instance.FieldName_FIELD_NAME_ID.Enum(),
Pagination: &filter.PaginationRequest{Asc: true},
},
expectedSortingDirection: database.OrderDirectionAsc,
expectedOrderBy: database.Columns{database.NewColumn("instances", "id")},
},
{
name: "unspecified field",
request: &instance.ListInstancesRequest{
SortingColumn: instance.FieldName_FIELD_NAME_UNSPECIFIED.Enum(),
},
expectedSortingDirection: database.OrderDirectionAsc,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// Given
mockCtrl := gomock.NewController(t)
mockRepo := domainmock.NewInstanceRepo(mockCtrl)
cmd := domain.NewListInstancesCommand(tc.request)
opts := &database.QueryOpts{}
// Test
queryOpt := cmd.Sorting(mockRepo)
queryOpt(opts)
// Verify
assert.Equal(t, tc.expectedOrderBy, opts.OrderBy)
assert.Equal(t, tc.expectedSortingDirection, opts.Ordering)
})
}
}
func TestListInstancesCommand_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.InstanceRepository, domain.InstanceDomainRepository)
queryParams []database.QueryOption
inputRequest *instance.ListInstancesRequest
expectedInstances []*domain.Instance
expectedError error
}{
{
testName: "when listing instances fails should return error",
repos: func(ctrl *gomock.Controller, _ ...database.QueryOption) (domain.InstanceRepository, domain.InstanceDomainRepository) {
instanceRepo := domainmock.NewInstanceRepo(ctrl)
domainRepo := domainmock.NewInstancesDomainRepo(ctrl)
instanceRepo.EXPECT().
LoadDomains().
Times(1).
Return(instanceRepo)
domainExistsORedConditions := instanceRepo.ExistsDomain(
database.Or([]database.Condition{
domainRepo.DomainCondition(database.TextOperationEqual, "domain1.example.com"),
domainRepo.DomainCondition(database.TextOperationEqual, "domain2.example.com"),
}...),
)
idConditions := database.Or(
[]database.Condition{
instanceRepo.IDCondition("instance-1"),
instanceRepo.IDCondition("instance-2"),
}...,
)
instanceRepo.EXPECT().
List(gomock.Any(), gomock.Any(),
dbmock.QueryOptions(
database.WithCondition(database.And(
idConditions, domainExistsORedConditions,
)),
),
dbmock.QueryOptions(database.WithOrderBy(database.OrderDirectionDesc, instanceRepo.NameColumn())),
dbmock.QueryOptions(database.WithLimit(2)),
dbmock.QueryOptions(database.WithOffset(1)),
).
Times(1).
Return(nil, listErr)
return instanceRepo, domainRepo
},
inputRequest: &instance.ListInstancesRequest{
Pagination: &filter.PaginationRequest{Limit: 2, Offset: 1, Asc: false},
SortingColumn: instance.FieldName_FIELD_NAME_NAME.Enum(),
Queries: []*instance.Query{
{Query: &instance.Query_IdQuery{IdQuery: &instance.IdsQuery{
Ids: []string{"instance-1", "instance-2"},
}}},
{Query: &instance.Query_DomainQuery{DomainQuery: &instance.DomainsQuery{
Domains: []string{"domain1.example.com", "domain2.example.com"},
}}},
},
},
expectedError: listErr,
},
{
testName: "when listing instances succeeds should save into result and return nil",
repos: func(ctrl *gomock.Controller, _ ...database.QueryOption) (domain.InstanceRepository, domain.InstanceDomainRepository) {
instanceRepo := domainmock.NewInstanceRepo(ctrl)
domainRepo := domainmock.NewInstancesDomainRepo(ctrl)
instanceRepo.EXPECT().
LoadDomains().
Times(1).
Return(instanceRepo)
domainExistsORedConditions := instanceRepo.ExistsDomain(
database.Or([]database.Condition{
domainRepo.DomainCondition(database.TextOperationEqual, "domain1.example.com"),
domainRepo.DomainCondition(database.TextOperationEqual, "domain2.example.com"),
}...),
)
idConditions := database.Or(
[]database.Condition{
instanceRepo.IDCondition("instance-1"),
instanceRepo.IDCondition("instance-2"),
}...,
)
instanceRepo.EXPECT().
List(gomock.Any(), gomock.Any(),
dbmock.QueryOptions(
database.WithCondition(database.And(
idConditions, domainExistsORedConditions,
)),
),
dbmock.QueryOptions(database.WithOrderBy(database.OrderDirectionDesc, instanceRepo.NameColumn())),
dbmock.QueryOptions(database.WithLimit(2)),
dbmock.QueryOptions(database.WithOffset(1)),
).
Times(1).
Return([]*domain.Instance{
{
ID: "instance-2",
Name: "Instance Two",
Domains: []*domain.InstanceDomain{{Domain: "domain1.example.com"}},
},
{
ID: "instance-1",
Name: "Instance One",
Domains: []*domain.InstanceDomain{{Domain: "domain2.example.com"}},
},
}, nil)
return instanceRepo, domainRepo
},
inputRequest: &instance.ListInstancesRequest{
Pagination: &filter.PaginationRequest{Limit: 2, Offset: 1, Asc: false},
SortingColumn: instance.FieldName_FIELD_NAME_NAME.Enum(),
Queries: []*instance.Query{
{Query: &instance.Query_IdQuery{IdQuery: &instance.IdsQuery{
Ids: []string{"instance-1", "instance-2"},
}}},
{Query: &instance.Query_DomainQuery{DomainQuery: &instance.DomainsQuery{
Domains: []string{"domain1.example.com", "domain2.example.com"},
}}},
},
},
expectedInstances: []*domain.Instance{
{
ID: "instance-2",
Name: "Instance Two",
Domains: []*domain.InstanceDomain{{Domain: "domain1.example.com"}},
},
{
ID: "instance-1",
Name: "Instance One",
Domains: []*domain.InstanceDomain{{Domain: "domain2.example.com"}},
},
},
},
}
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.NewListInstancesCommand(tc.inputRequest)
opts := &domain.InvokeOpts{
Invoker: domain.NewTransactionInvoker(nil),
}
domain.WithQueryExecutor(new(noopdb.Pool))(opts)
if tc.repos != nil {
inst, instDomain := tc.repos(ctrl, tc.queryParams...)
domain.WithInstanceRepo(inst)(opts)
domain.WithInstanceDomainRepo(instDomain)(opts)
}
// Test
err := cmd.Execute(ctx, opts)
// Verify
assert.Equal(t, tc.expectedError, err)
assert.ElementsMatch(t, tc.expectedInstances, cmd.Result())
})
}
}
func TestListInstancesCommand_Validate(t *testing.T) {
t.Parallel()
permissionErr := errors.New("permission error")
tt := []struct {
name string
permissionChecker func(ctrl *gomock.Controller) domain.PermissionChecker
expectedError error
}{
{
name: "when user is missing permission should return permission denied",
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
permChecker := domainmock.NewMockPermissionChecker(ctrl)
permChecker.EXPECT().
CheckInstancePermission(gomock.Any(), domain.InstanceReadPermission).
Times(1).
Return(permissionErr)
return permChecker
},
expectedError: zerrors.ThrowPermissionDenied(permissionErr, "DOM-cuT6Ws", "permission denied"),
},
{
name: "when valid permission should return no error",
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
permChecker := domainmock.NewMockPermissionChecker(ctrl)
permChecker.EXPECT().
CheckInstancePermission(gomock.Any(), domain.InstanceReadPermission).
Times(1).
Return(nil)
return permChecker
},
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// Given
l := &domain.ListInstancesQuery{}
cmdOpts := &domain.InvokeOpts{}
if tc.permissionChecker != nil {
ctrl := gomock.NewController(t)
cmdOpts.Permissions = tc.permissionChecker(ctrl)
}
// Test
err := l.Validate(context.Background(), cmdOpts)
// Verify
assert.Equal(t, tc.expectedError, err)
})
}
}
+96
View File
@@ -0,0 +1,96 @@
package domain
import (
"context"
"strings"
"github.com/zitadel/zitadel/backend/v3/storage/database"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/zerrors"
)
type UpdateInstanceCommand struct {
ID string `json:"id"`
Name string `json:"name"`
}
// RequiresTransaction implements [Transactional].
func (u *UpdateInstanceCommand) RequiresTransaction() {}
// Events implements Commander.
func (u *UpdateInstanceCommand) Events(ctx context.Context, _ *InvokeOpts) ([]eventstore.Command, error) {
return []eventstore.Command{
instance.NewInstanceChangedEvent(ctx, &instance.NewAggregate(u.ID).Aggregate, u.Name),
}, nil
}
var (
_ Commander = (*UpdateInstanceCommand)(nil)
_ Transactional = (*UpdateInstanceCommand)(nil)
)
func NewUpdateInstanceCommand(id, name string) *UpdateInstanceCommand {
return &UpdateInstanceCommand{
ID: id,
Name: name,
}
}
// Execute implements [Commander]
func (u *UpdateInstanceCommand) Execute(ctx context.Context, opts *InvokeOpts) (err error) {
instanceRepo := opts.instanceRepo
updateCount, err := instanceRepo.Update(
ctx,
opts.DB(),
u.ID,
database.NewChange(instanceRepo.NameColumn(), u.Name),
)
if err != nil {
return err
}
if updateCount == 0 {
err = zerrors.ThrowNotFound(nil, "DOM-ghfov1", "Errors.Instance.NotFound")
return err
}
if updateCount > 1 {
err = zerrors.ThrowInternal(NewMultipleObjectsUpdatedError(1, updateCount), "DOM-HlrNmD", "unexpected number of rows updated")
return err
}
return err
}
// String implements [Commander]
func (u *UpdateInstanceCommand) String() string {
return "UpdateInstanceCommand"
}
// Validate implements [Commander]
func (u *UpdateInstanceCommand) Validate(ctx context.Context, opts *InvokeOpts) error {
if u.ID = strings.TrimSpace(u.ID); u.ID == "" {
return zerrors.ThrowInvalidArgument(nil, "DOM-wSs6kG", "invalid instance ID")
}
if u.Name = strings.TrimSpace(u.Name); u.Name == "" {
return zerrors.ThrowInvalidArgument(nil, "DOM-FPJcLC", "invalid instance name")
}
if authZErr := opts.Permissions.CheckInstancePermission(ctx, InstanceWritePermission); authZErr != nil {
return zerrors.ThrowPermissionDenied(authZErr, "DOM-M5ObLP", "permission denied")
}
instanceRepo := opts.instanceRepo
instance, err := instanceRepo.Get(ctx, opts.DB(), database.WithCondition(instanceRepo.IDCondition(u.ID)))
if err != nil {
return err
}
if instance.Name == u.Name {
err = zerrors.ThrowPreconditionFailed(nil, "DOM-5MrT21", "Errors.Instance.NotChanged")
return err
}
return nil
}
+300
View File
@@ -0,0 +1,300 @@
package domain_test
import (
"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/repository/instance"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestUpdateInstanceCommand_Validate(t *testing.T) {
t.Parallel()
getErr := errors.New("get error")
permissionErr := errors.New("permission error")
tt := []struct {
testName string
instanceRepo func(ctrl *gomock.Controller) domain.InstanceRepository
permissionChecker func(ctrl *gomock.Controller) domain.PermissionChecker
inputInstanceID string
inputInstanceName string
expectedError error
}{
{
testName: "when no ID should return invalid argument error",
inputInstanceID: "",
inputInstanceName: "test-name",
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-wSs6kG", "invalid instance ID"),
},
{
testName: "when no name shuld return invalid argument error",
inputInstanceID: "test-id",
inputInstanceName: "",
expectedError: zerrors.ThrowInvalidArgument(nil, "DOM-FPJcLC", "invalid instance name"),
},
{
testName: "when user is missing permission should return permission denied",
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
permChecker := domainmock.NewMockPermissionChecker(ctrl)
permChecker.EXPECT().
CheckInstancePermission(gomock.Any(), domain.InstanceWritePermission).
Times(1).
Return(permissionErr)
return permChecker
},
inputInstanceID: "instance-1",
inputInstanceName: "test instance update",
expectedError: zerrors.ThrowPermissionDenied(permissionErr, "DOM-M5ObLP", "permission denied"),
},
{
testName: "when retrieving instance fails should return error",
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
permChecker := domainmock.NewMockPermissionChecker(ctrl)
permChecker.EXPECT().
CheckInstancePermission(gomock.Any(), domain.InstanceWritePermission).
Times(1).
Return(nil)
return permChecker
},
instanceRepo: func(ctrl *gomock.Controller) domain.InstanceRepository {
repo := domainmock.NewInstanceRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(
database.WithCondition(
repo.IDCondition("instance-1"),
),
)).
Times(1).
Return(nil, getErr)
return repo
},
inputInstanceID: "instance-1",
inputInstanceName: "test instance update",
expectedError: getErr,
},
{
testName: "when instance name is not changed should return name not changed error",
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
permChecker := domainmock.NewMockPermissionChecker(ctrl)
permChecker.EXPECT().
CheckInstancePermission(gomock.Any(), domain.InstanceWritePermission).
Times(1).
Return(nil)
return permChecker
},
instanceRepo: func(ctrl *gomock.Controller) domain.InstanceRepository {
repo := domainmock.NewInstanceRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(
repo.IDCondition("instance-1"),
))).
Times(1).
Return(&domain.Instance{
ID: "instance-1",
Name: "test instance update",
}, nil)
return repo
},
inputInstanceID: "instance-1",
inputInstanceName: "test instance update",
expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-5MrT21", "Errors.Instance.NotChanged"),
},
{
testName: "when instance name is changed should validate successfully and return no error",
permissionChecker: func(ctrl *gomock.Controller) domain.PermissionChecker {
permChecker := domainmock.NewMockPermissionChecker(ctrl)
permChecker.EXPECT().
CheckInstancePermission(gomock.Any(), domain.InstanceWritePermission).
Times(1).
Return(nil)
return permChecker
},
instanceRepo: func(ctrl *gomock.Controller) domain.InstanceRepository {
repo := domainmock.NewInstanceRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(
repo.IDCondition("instance-1"),
))).
Times(1).
Return(&domain.Instance{
ID: "instance-1",
Name: "old instance name",
}, nil)
return repo
},
inputInstanceID: "instance-1",
inputInstanceName: "test instance 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.NewUpdateInstanceCommand(tc.inputInstanceID, tc.inputInstanceName)
opts := &domain.InvokeOpts{
Invoker: domain.NewTransactionInvoker(nil),
}
domain.WithQueryExecutor(new(noopdb.Pool))(opts)
if tc.instanceRepo != nil {
domain.WithInstanceRepo(tc.instanceRepo(ctrl))(opts)
}
if tc.permissionChecker != nil {
opts.Permissions = tc.permissionChecker(ctrl)
}
err := cmd.Validate(ctx, opts)
assert.Equal(t, tc.expectedError, err)
})
}
}
func TestUpdateInstanceCommand_Execute(t *testing.T) {
t.Parallel()
updateErr := errors.New("update error")
tt := []struct {
testName string
instanceRepo func(ctrl *gomock.Controller) domain.InstanceRepository
inputID string
inputName string
expectedError error
}{
{
testName: "when instance update fails should return error",
instanceRepo: func(ctrl *gomock.Controller) domain.InstanceRepository {
repo := domainmock.NewInstanceRepo(ctrl)
repo.EXPECT().
Update(gomock.Any(), gomock.Any(),
"instance-1",
repo.SetName("test instance update"),
).
Times(1).
Return(int64(0), updateErr)
return repo
},
inputID: "instance-1",
inputName: "test instance update",
expectedError: updateErr,
},
{
testName: "when instance update returns 0 rows updated should return not found error",
inputID: "instance-1",
inputName: "test instance update",
instanceRepo: func(ctrl *gomock.Controller) domain.InstanceRepository {
repo := domainmock.NewInstanceRepo(ctrl)
repo.EXPECT().
Update(gomock.Any(), gomock.Any(),
"instance-1",
repo.SetName("test instance update"),
).
Times(1).
Return(int64(0), nil)
return repo
},
expectedError: zerrors.ThrowNotFound(nil, "DOM-ghfov1", "Errors.Instance.NotFound"),
},
{
testName: "when instance update returns more than 1 row updated should return internal error",
instanceRepo: func(ctrl *gomock.Controller) domain.InstanceRepository {
repo := domainmock.NewInstanceRepo(ctrl)
repo.EXPECT().
Update(gomock.Any(), gomock.Any(),
"instance-1",
repo.SetName("test instance update"),
).
Times(1).
Return(int64(2), nil)
return repo
},
inputID: "instance-1",
inputName: "test instance update",
expectedError: zerrors.ThrowInternal(domain.NewMultipleObjectsUpdatedError(1, 2), "DOM-HlrNmD", "unexpected number of rows updated"),
},
{
testName: "when instance update returns 1 row updated should return no error and set non-primary verified domain",
instanceRepo: func(ctrl *gomock.Controller) domain.InstanceRepository {
repo := domainmock.NewInstanceRepo(ctrl)
repo.EXPECT().
Update(gomock.Any(), gomock.Any(),
"instance-1",
repo.SetName("test instance update"),
).
Times(1).
Return(int64(1), nil)
return repo
},
inputID: "instance-1",
inputName: "test instance 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.NewUpdateInstanceCommand(tc.inputID, tc.inputName)
opts := &domain.InvokeOpts{
Invoker: domain.NewTransactionInvoker(nil),
}
domain.WithQueryExecutor(new(noopdb.Pool))(opts)
if tc.instanceRepo != nil {
domain.WithInstanceRepo(tc.instanceRepo(ctrl))(opts)
}
// Test
err := cmd.Execute(ctx, opts)
// Verify
assert.Equal(t, tc.expectedError, err)
})
}
}
func TestUpdateInstanceCommand_Events(t *testing.T) {
t.Parallel()
// Given
ctx := authz.NewMockContext("instance-1", "", "")
cmd := domain.NewUpdateInstanceCommand("instance-1", "test-name")
// Test
events, err := cmd.Events(ctx, &domain.InvokeOpts{})
// Verify
assert.NoError(t, err)
require.Len(t, events, 1)
event := events[0].(*instance.InstanceChangedEvent)
assert.Equal(t, "instance-1", event.Aggregate().ID)
assert.Equal(t, "test-name", event.Name)
}
+14
View File
@@ -27,6 +27,18 @@ func WithProjectRepo(repo ProjectRepository) InvokeOpt {
}
}
func WithInstanceRepo(repo InstanceRepository) InvokeOpt {
return func(opts *InvokeOpts) {
opts.instanceRepo = repo
}
}
func WithInstanceDomainRepo(repo InstanceDomainRepository) InvokeOpt {
return func(opts *InvokeOpts) {
opts.instanceDomainRepo = repo
}
}
// WithQueryExecutor sets the database client to be used by the command.
// If not set, the default pool will be used.
// This is mainly used for testing.
@@ -59,6 +71,8 @@ type InvokeOpts struct {
organizationRepo OrganizationRepository
organizationDomainRepo OrganizationDomainRepository
projectRepo ProjectRepository
instanceRepo InstanceRepository
instanceDomainRepo InstanceDomainRepository
}
func (o *InvokeOpts) DB() database.QueryExecutor {
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,860 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/zitadel/zitadel/backend/v3/domain (interfaces: InstanceDomainRepository)
//
// Generated by this command:
//
// mockgen -typed -package domainmock -destination ./mock/instance_domain.mock.go . InstanceDomainRepository
//
// 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"
)
// MockInstanceDomainRepository is a mock of InstanceDomainRepository interface.
type MockInstanceDomainRepository struct {
ctrl *gomock.Controller
recorder *MockInstanceDomainRepositoryMockRecorder
}
// MockInstanceDomainRepositoryMockRecorder is the mock recorder for MockInstanceDomainRepository.
type MockInstanceDomainRepositoryMockRecorder struct {
mock *MockInstanceDomainRepository
}
// NewMockInstanceDomainRepository creates a new mock instance.
func NewMockInstanceDomainRepository(ctrl *gomock.Controller) *MockInstanceDomainRepository {
mock := &MockInstanceDomainRepository{ctrl: ctrl}
mock.recorder = &MockInstanceDomainRepositoryMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockInstanceDomainRepository) EXPECT() *MockInstanceDomainRepositoryMockRecorder {
return m.recorder
}
// Add mocks base method.
func (m *MockInstanceDomainRepository) Add(arg0 context.Context, arg1 database.QueryExecutor, arg2 *domain.AddInstanceDomain) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Add", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// Add indicates an expected call of Add.
func (mr *MockInstanceDomainRepositoryMockRecorder) Add(arg0, arg1, arg2 any) *MockInstanceDomainRepositoryAddCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockInstanceDomainRepository)(nil).Add), arg0, arg1, arg2)
return &MockInstanceDomainRepositoryAddCall{Call: call}
}
// MockInstanceDomainRepositoryAddCall wrap *gomock.Call
type MockInstanceDomainRepositoryAddCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockInstanceDomainRepositoryAddCall) Return(arg0 error) *MockInstanceDomainRepositoryAddCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockInstanceDomainRepositoryAddCall) Do(f func(context.Context, database.QueryExecutor, *domain.AddInstanceDomain) error) *MockInstanceDomainRepositoryAddCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockInstanceDomainRepositoryAddCall) DoAndReturn(f func(context.Context, database.QueryExecutor, *domain.AddInstanceDomain) error) *MockInstanceDomainRepositoryAddCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// CreatedAtColumn mocks base method.
func (m *MockInstanceDomainRepository) 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 *MockInstanceDomainRepositoryMockRecorder) CreatedAtColumn() *MockInstanceDomainRepositoryCreatedAtColumnCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatedAtColumn", reflect.TypeOf((*MockInstanceDomainRepository)(nil).CreatedAtColumn))
return &MockInstanceDomainRepositoryCreatedAtColumnCall{Call: call}
}
// MockInstanceDomainRepositoryCreatedAtColumnCall wrap *gomock.Call
type MockInstanceDomainRepositoryCreatedAtColumnCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockInstanceDomainRepositoryCreatedAtColumnCall) Return(arg0 database.Column) *MockInstanceDomainRepositoryCreatedAtColumnCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockInstanceDomainRepositoryCreatedAtColumnCall) Do(f func() database.Column) *MockInstanceDomainRepositoryCreatedAtColumnCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockInstanceDomainRepositoryCreatedAtColumnCall) DoAndReturn(f func() database.Column) *MockInstanceDomainRepositoryCreatedAtColumnCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// DomainColumn mocks base method.
func (m *MockInstanceDomainRepository) 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 *MockInstanceDomainRepositoryMockRecorder) DomainColumn() *MockInstanceDomainRepositoryDomainColumnCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DomainColumn", reflect.TypeOf((*MockInstanceDomainRepository)(nil).DomainColumn))
return &MockInstanceDomainRepositoryDomainColumnCall{Call: call}
}
// MockInstanceDomainRepositoryDomainColumnCall wrap *gomock.Call
type MockInstanceDomainRepositoryDomainColumnCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockInstanceDomainRepositoryDomainColumnCall) Return(arg0 database.Column) *MockInstanceDomainRepositoryDomainColumnCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockInstanceDomainRepositoryDomainColumnCall) Do(f func() database.Column) *MockInstanceDomainRepositoryDomainColumnCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockInstanceDomainRepositoryDomainColumnCall) DoAndReturn(f func() database.Column) *MockInstanceDomainRepositoryDomainColumnCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// DomainCondition mocks base method.
func (m *MockInstanceDomainRepository) DomainCondition(arg0 database.TextOperation, arg1 string) database.Condition {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DomainCondition", arg0, arg1)
ret0, _ := ret[0].(database.Condition)
return ret0
}
// DomainCondition indicates an expected call of DomainCondition.
func (mr *MockInstanceDomainRepositoryMockRecorder) DomainCondition(arg0, arg1 any) *MockInstanceDomainRepositoryDomainConditionCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DomainCondition", reflect.TypeOf((*MockInstanceDomainRepository)(nil).DomainCondition), arg0, arg1)
return &MockInstanceDomainRepositoryDomainConditionCall{Call: call}
}
// MockInstanceDomainRepositoryDomainConditionCall wrap *gomock.Call
type MockInstanceDomainRepositoryDomainConditionCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockInstanceDomainRepositoryDomainConditionCall) Return(arg0 database.Condition) *MockInstanceDomainRepositoryDomainConditionCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockInstanceDomainRepositoryDomainConditionCall) Do(f func(database.TextOperation, string) database.Condition) *MockInstanceDomainRepositoryDomainConditionCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockInstanceDomainRepositoryDomainConditionCall) DoAndReturn(f func(database.TextOperation, string) database.Condition) *MockInstanceDomainRepositoryDomainConditionCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Get mocks base method.
func (m *MockInstanceDomainRepository) Get(arg0 context.Context, arg1 database.QueryExecutor, arg2 ...database.QueryOption) (*domain.InstanceDomain, error) {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "Get", varargs...)
ret0, _ := ret[0].(*domain.InstanceDomain)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Get indicates an expected call of Get.
func (mr *MockInstanceDomainRepositoryMockRecorder) Get(arg0, arg1 any, arg2 ...any) *MockInstanceDomainRepositoryGetCall {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1}, arg2...)
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockInstanceDomainRepository)(nil).Get), varargs...)
return &MockInstanceDomainRepositoryGetCall{Call: call}
}
// MockInstanceDomainRepositoryGetCall wrap *gomock.Call
type MockInstanceDomainRepositoryGetCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockInstanceDomainRepositoryGetCall) Return(arg0 *domain.InstanceDomain, arg1 error) *MockInstanceDomainRepositoryGetCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockInstanceDomainRepositoryGetCall) Do(f func(context.Context, database.QueryExecutor, ...database.QueryOption) (*domain.InstanceDomain, error)) *MockInstanceDomainRepositoryGetCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockInstanceDomainRepositoryGetCall) DoAndReturn(f func(context.Context, database.QueryExecutor, ...database.QueryOption) (*domain.InstanceDomain, error)) *MockInstanceDomainRepositoryGetCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// InstanceIDColumn mocks base method.
func (m *MockInstanceDomainRepository) 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 *MockInstanceDomainRepositoryMockRecorder) InstanceIDColumn() *MockInstanceDomainRepositoryInstanceIDColumnCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceIDColumn", reflect.TypeOf((*MockInstanceDomainRepository)(nil).InstanceIDColumn))
return &MockInstanceDomainRepositoryInstanceIDColumnCall{Call: call}
}
// MockInstanceDomainRepositoryInstanceIDColumnCall wrap *gomock.Call
type MockInstanceDomainRepositoryInstanceIDColumnCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockInstanceDomainRepositoryInstanceIDColumnCall) Return(arg0 database.Column) *MockInstanceDomainRepositoryInstanceIDColumnCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockInstanceDomainRepositoryInstanceIDColumnCall) Do(f func() database.Column) *MockInstanceDomainRepositoryInstanceIDColumnCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockInstanceDomainRepositoryInstanceIDColumnCall) DoAndReturn(f func() database.Column) *MockInstanceDomainRepositoryInstanceIDColumnCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// InstanceIDCondition mocks base method.
func (m *MockInstanceDomainRepository) InstanceIDCondition(arg0 string) database.Condition {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "InstanceIDCondition", arg0)
ret0, _ := ret[0].(database.Condition)
return ret0
}
// InstanceIDCondition indicates an expected call of InstanceIDCondition.
func (mr *MockInstanceDomainRepositoryMockRecorder) InstanceIDCondition(arg0 any) *MockInstanceDomainRepositoryInstanceIDConditionCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceIDCondition", reflect.TypeOf((*MockInstanceDomainRepository)(nil).InstanceIDCondition), arg0)
return &MockInstanceDomainRepositoryInstanceIDConditionCall{Call: call}
}
// MockInstanceDomainRepositoryInstanceIDConditionCall wrap *gomock.Call
type MockInstanceDomainRepositoryInstanceIDConditionCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockInstanceDomainRepositoryInstanceIDConditionCall) Return(arg0 database.Condition) *MockInstanceDomainRepositoryInstanceIDConditionCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockInstanceDomainRepositoryInstanceIDConditionCall) Do(f func(string) database.Condition) *MockInstanceDomainRepositoryInstanceIDConditionCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockInstanceDomainRepositoryInstanceIDConditionCall) DoAndReturn(f func(string) database.Condition) *MockInstanceDomainRepositoryInstanceIDConditionCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// IsGeneratedColumn mocks base method.
func (m *MockInstanceDomainRepository) IsGeneratedColumn() database.Column {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IsGeneratedColumn")
ret0, _ := ret[0].(database.Column)
return ret0
}
// IsGeneratedColumn indicates an expected call of IsGeneratedColumn.
func (mr *MockInstanceDomainRepositoryMockRecorder) IsGeneratedColumn() *MockInstanceDomainRepositoryIsGeneratedColumnCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsGeneratedColumn", reflect.TypeOf((*MockInstanceDomainRepository)(nil).IsGeneratedColumn))
return &MockInstanceDomainRepositoryIsGeneratedColumnCall{Call: call}
}
// MockInstanceDomainRepositoryIsGeneratedColumnCall wrap *gomock.Call
type MockInstanceDomainRepositoryIsGeneratedColumnCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockInstanceDomainRepositoryIsGeneratedColumnCall) Return(arg0 database.Column) *MockInstanceDomainRepositoryIsGeneratedColumnCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockInstanceDomainRepositoryIsGeneratedColumnCall) Do(f func() database.Column) *MockInstanceDomainRepositoryIsGeneratedColumnCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockInstanceDomainRepositoryIsGeneratedColumnCall) DoAndReturn(f func() database.Column) *MockInstanceDomainRepositoryIsGeneratedColumnCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// IsPrimaryColumn mocks base method.
func (m *MockInstanceDomainRepository) 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 *MockInstanceDomainRepositoryMockRecorder) IsPrimaryColumn() *MockInstanceDomainRepositoryIsPrimaryColumnCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsPrimaryColumn", reflect.TypeOf((*MockInstanceDomainRepository)(nil).IsPrimaryColumn))
return &MockInstanceDomainRepositoryIsPrimaryColumnCall{Call: call}
}
// MockInstanceDomainRepositoryIsPrimaryColumnCall wrap *gomock.Call
type MockInstanceDomainRepositoryIsPrimaryColumnCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockInstanceDomainRepositoryIsPrimaryColumnCall) Return(arg0 database.Column) *MockInstanceDomainRepositoryIsPrimaryColumnCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockInstanceDomainRepositoryIsPrimaryColumnCall) Do(f func() database.Column) *MockInstanceDomainRepositoryIsPrimaryColumnCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockInstanceDomainRepositoryIsPrimaryColumnCall) DoAndReturn(f func() database.Column) *MockInstanceDomainRepositoryIsPrimaryColumnCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// IsPrimaryCondition mocks base method.
func (m *MockInstanceDomainRepository) IsPrimaryCondition(arg0 bool) database.Condition {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IsPrimaryCondition", arg0)
ret0, _ := ret[0].(database.Condition)
return ret0
}
// IsPrimaryCondition indicates an expected call of IsPrimaryCondition.
func (mr *MockInstanceDomainRepositoryMockRecorder) IsPrimaryCondition(arg0 any) *MockInstanceDomainRepositoryIsPrimaryConditionCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsPrimaryCondition", reflect.TypeOf((*MockInstanceDomainRepository)(nil).IsPrimaryCondition), arg0)
return &MockInstanceDomainRepositoryIsPrimaryConditionCall{Call: call}
}
// MockInstanceDomainRepositoryIsPrimaryConditionCall wrap *gomock.Call
type MockInstanceDomainRepositoryIsPrimaryConditionCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockInstanceDomainRepositoryIsPrimaryConditionCall) Return(arg0 database.Condition) *MockInstanceDomainRepositoryIsPrimaryConditionCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockInstanceDomainRepositoryIsPrimaryConditionCall) Do(f func(bool) database.Condition) *MockInstanceDomainRepositoryIsPrimaryConditionCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockInstanceDomainRepositoryIsPrimaryConditionCall) DoAndReturn(f func(bool) database.Condition) *MockInstanceDomainRepositoryIsPrimaryConditionCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// List mocks base method.
func (m *MockInstanceDomainRepository) List(arg0 context.Context, arg1 database.QueryExecutor, arg2 ...database.QueryOption) ([]*domain.InstanceDomain, error) {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "List", varargs...)
ret0, _ := ret[0].([]*domain.InstanceDomain)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// List indicates an expected call of List.
func (mr *MockInstanceDomainRepositoryMockRecorder) List(arg0, arg1 any, arg2 ...any) *MockInstanceDomainRepositoryListCall {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1}, arg2...)
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockInstanceDomainRepository)(nil).List), varargs...)
return &MockInstanceDomainRepositoryListCall{Call: call}
}
// MockInstanceDomainRepositoryListCall wrap *gomock.Call
type MockInstanceDomainRepositoryListCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockInstanceDomainRepositoryListCall) Return(arg0 []*domain.InstanceDomain, arg1 error) *MockInstanceDomainRepositoryListCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockInstanceDomainRepositoryListCall) Do(f func(context.Context, database.QueryExecutor, ...database.QueryOption) ([]*domain.InstanceDomain, error)) *MockInstanceDomainRepositoryListCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockInstanceDomainRepositoryListCall) DoAndReturn(f func(context.Context, database.QueryExecutor, ...database.QueryOption) ([]*domain.InstanceDomain, error)) *MockInstanceDomainRepositoryListCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// PrimaryKeyColumns mocks base method.
func (m *MockInstanceDomainRepository) PrimaryKeyColumns() []database.Column {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PrimaryKeyColumns")
ret0, _ := ret[0].([]database.Column)
return ret0
}
// PrimaryKeyColumns indicates an expected call of PrimaryKeyColumns.
func (mr *MockInstanceDomainRepositoryMockRecorder) PrimaryKeyColumns() *MockInstanceDomainRepositoryPrimaryKeyColumnsCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrimaryKeyColumns", reflect.TypeOf((*MockInstanceDomainRepository)(nil).PrimaryKeyColumns))
return &MockInstanceDomainRepositoryPrimaryKeyColumnsCall{Call: call}
}
// MockInstanceDomainRepositoryPrimaryKeyColumnsCall wrap *gomock.Call
type MockInstanceDomainRepositoryPrimaryKeyColumnsCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockInstanceDomainRepositoryPrimaryKeyColumnsCall) Return(arg0 []database.Column) *MockInstanceDomainRepositoryPrimaryKeyColumnsCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockInstanceDomainRepositoryPrimaryKeyColumnsCall) Do(f func() []database.Column) *MockInstanceDomainRepositoryPrimaryKeyColumnsCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockInstanceDomainRepositoryPrimaryKeyColumnsCall) DoAndReturn(f func() []database.Column) *MockInstanceDomainRepositoryPrimaryKeyColumnsCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// PrimaryKeyCondition mocks base method.
func (m *MockInstanceDomainRepository) PrimaryKeyCondition(arg0 string) database.Condition {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PrimaryKeyCondition", arg0)
ret0, _ := ret[0].(database.Condition)
return ret0
}
// PrimaryKeyCondition indicates an expected call of PrimaryKeyCondition.
func (mr *MockInstanceDomainRepositoryMockRecorder) PrimaryKeyCondition(arg0 any) *MockInstanceDomainRepositoryPrimaryKeyConditionCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrimaryKeyCondition", reflect.TypeOf((*MockInstanceDomainRepository)(nil).PrimaryKeyCondition), arg0)
return &MockInstanceDomainRepositoryPrimaryKeyConditionCall{Call: call}
}
// MockInstanceDomainRepositoryPrimaryKeyConditionCall wrap *gomock.Call
type MockInstanceDomainRepositoryPrimaryKeyConditionCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockInstanceDomainRepositoryPrimaryKeyConditionCall) Return(arg0 database.Condition) *MockInstanceDomainRepositoryPrimaryKeyConditionCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockInstanceDomainRepositoryPrimaryKeyConditionCall) Do(f func(string) database.Condition) *MockInstanceDomainRepositoryPrimaryKeyConditionCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockInstanceDomainRepositoryPrimaryKeyConditionCall) DoAndReturn(f func(string) database.Condition) *MockInstanceDomainRepositoryPrimaryKeyConditionCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Remove mocks base method.
func (m *MockInstanceDomainRepository) Remove(arg0 context.Context, arg1 database.QueryExecutor, arg2 database.Condition) (int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Remove", arg0, arg1, arg2)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Remove indicates an expected call of Remove.
func (mr *MockInstanceDomainRepositoryMockRecorder) Remove(arg0, arg1, arg2 any) *MockInstanceDomainRepositoryRemoveCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockInstanceDomainRepository)(nil).Remove), arg0, arg1, arg2)
return &MockInstanceDomainRepositoryRemoveCall{Call: call}
}
// MockInstanceDomainRepositoryRemoveCall wrap *gomock.Call
type MockInstanceDomainRepositoryRemoveCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockInstanceDomainRepositoryRemoveCall) Return(arg0 int64, arg1 error) *MockInstanceDomainRepositoryRemoveCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockInstanceDomainRepositoryRemoveCall) Do(f func(context.Context, database.QueryExecutor, database.Condition) (int64, error)) *MockInstanceDomainRepositoryRemoveCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockInstanceDomainRepositoryRemoveCall) DoAndReturn(f func(context.Context, database.QueryExecutor, database.Condition) (int64, error)) *MockInstanceDomainRepositoryRemoveCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// SetPrimary mocks base method.
func (m *MockInstanceDomainRepository) 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 *MockInstanceDomainRepositoryMockRecorder) SetPrimary() *MockInstanceDomainRepositorySetPrimaryCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPrimary", reflect.TypeOf((*MockInstanceDomainRepository)(nil).SetPrimary))
return &MockInstanceDomainRepositorySetPrimaryCall{Call: call}
}
// MockInstanceDomainRepositorySetPrimaryCall wrap *gomock.Call
type MockInstanceDomainRepositorySetPrimaryCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockInstanceDomainRepositorySetPrimaryCall) Return(arg0 database.Change) *MockInstanceDomainRepositorySetPrimaryCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockInstanceDomainRepositorySetPrimaryCall) Do(f func() database.Change) *MockInstanceDomainRepositorySetPrimaryCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockInstanceDomainRepositorySetPrimaryCall) DoAndReturn(f func() database.Change) *MockInstanceDomainRepositorySetPrimaryCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// SetType mocks base method.
func (m *MockInstanceDomainRepository) SetType(arg0 domain.DomainType) database.Change {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetType", arg0)
ret0, _ := ret[0].(database.Change)
return ret0
}
// SetType indicates an expected call of SetType.
func (mr *MockInstanceDomainRepositoryMockRecorder) SetType(arg0 any) *MockInstanceDomainRepositorySetTypeCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetType", reflect.TypeOf((*MockInstanceDomainRepository)(nil).SetType), arg0)
return &MockInstanceDomainRepositorySetTypeCall{Call: call}
}
// MockInstanceDomainRepositorySetTypeCall wrap *gomock.Call
type MockInstanceDomainRepositorySetTypeCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockInstanceDomainRepositorySetTypeCall) Return(arg0 database.Change) *MockInstanceDomainRepositorySetTypeCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockInstanceDomainRepositorySetTypeCall) Do(f func(domain.DomainType) database.Change) *MockInstanceDomainRepositorySetTypeCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockInstanceDomainRepositorySetTypeCall) DoAndReturn(f func(domain.DomainType) database.Change) *MockInstanceDomainRepositorySetTypeCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// SetUpdatedAt mocks base method.
func (m *MockInstanceDomainRepository) SetUpdatedAt(arg0 time.Time) database.Change {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetUpdatedAt", arg0)
ret0, _ := ret[0].(database.Change)
return ret0
}
// SetUpdatedAt indicates an expected call of SetUpdatedAt.
func (mr *MockInstanceDomainRepositoryMockRecorder) SetUpdatedAt(arg0 any) *MockInstanceDomainRepositorySetUpdatedAtCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetUpdatedAt", reflect.TypeOf((*MockInstanceDomainRepository)(nil).SetUpdatedAt), arg0)
return &MockInstanceDomainRepositorySetUpdatedAtCall{Call: call}
}
// MockInstanceDomainRepositorySetUpdatedAtCall wrap *gomock.Call
type MockInstanceDomainRepositorySetUpdatedAtCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockInstanceDomainRepositorySetUpdatedAtCall) Return(arg0 database.Change) *MockInstanceDomainRepositorySetUpdatedAtCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockInstanceDomainRepositorySetUpdatedAtCall) Do(f func(time.Time) database.Change) *MockInstanceDomainRepositorySetUpdatedAtCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockInstanceDomainRepositorySetUpdatedAtCall) DoAndReturn(f func(time.Time) database.Change) *MockInstanceDomainRepositorySetUpdatedAtCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// TypeColumn mocks base method.
func (m *MockInstanceDomainRepository) TypeColumn() database.Column {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "TypeColumn")
ret0, _ := ret[0].(database.Column)
return ret0
}
// TypeColumn indicates an expected call of TypeColumn.
func (mr *MockInstanceDomainRepositoryMockRecorder) TypeColumn() *MockInstanceDomainRepositoryTypeColumnCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TypeColumn", reflect.TypeOf((*MockInstanceDomainRepository)(nil).TypeColumn))
return &MockInstanceDomainRepositoryTypeColumnCall{Call: call}
}
// MockInstanceDomainRepositoryTypeColumnCall wrap *gomock.Call
type MockInstanceDomainRepositoryTypeColumnCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockInstanceDomainRepositoryTypeColumnCall) Return(arg0 database.Column) *MockInstanceDomainRepositoryTypeColumnCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockInstanceDomainRepositoryTypeColumnCall) Do(f func() database.Column) *MockInstanceDomainRepositoryTypeColumnCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockInstanceDomainRepositoryTypeColumnCall) DoAndReturn(f func() database.Column) *MockInstanceDomainRepositoryTypeColumnCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// TypeCondition mocks base method.
func (m *MockInstanceDomainRepository) TypeCondition(arg0 domain.DomainType) database.Condition {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "TypeCondition", arg0)
ret0, _ := ret[0].(database.Condition)
return ret0
}
// TypeCondition indicates an expected call of TypeCondition.
func (mr *MockInstanceDomainRepositoryMockRecorder) TypeCondition(arg0 any) *MockInstanceDomainRepositoryTypeConditionCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TypeCondition", reflect.TypeOf((*MockInstanceDomainRepository)(nil).TypeCondition), arg0)
return &MockInstanceDomainRepositoryTypeConditionCall{Call: call}
}
// MockInstanceDomainRepositoryTypeConditionCall wrap *gomock.Call
type MockInstanceDomainRepositoryTypeConditionCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockInstanceDomainRepositoryTypeConditionCall) Return(arg0 database.Condition) *MockInstanceDomainRepositoryTypeConditionCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockInstanceDomainRepositoryTypeConditionCall) Do(f func(domain.DomainType) database.Condition) *MockInstanceDomainRepositoryTypeConditionCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockInstanceDomainRepositoryTypeConditionCall) DoAndReturn(f func(domain.DomainType) database.Condition) *MockInstanceDomainRepositoryTypeConditionCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Update mocks base method.
func (m *MockInstanceDomainRepository) Update(arg0 context.Context, arg1 database.QueryExecutor, arg2 database.Condition, arg3 ...database.Change) (int64, error) {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1, arg2}
for _, a := range arg3 {
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 *MockInstanceDomainRepositoryMockRecorder) Update(arg0, arg1, arg2 any, arg3 ...any) *MockInstanceDomainRepositoryUpdateCall {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1, arg2}, arg3...)
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockInstanceDomainRepository)(nil).Update), varargs...)
return &MockInstanceDomainRepositoryUpdateCall{Call: call}
}
// MockInstanceDomainRepositoryUpdateCall wrap *gomock.Call
type MockInstanceDomainRepositoryUpdateCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockInstanceDomainRepositoryUpdateCall) Return(arg0 int64, arg1 error) *MockInstanceDomainRepositoryUpdateCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockInstanceDomainRepositoryUpdateCall) Do(f func(context.Context, database.QueryExecutor, database.Condition, ...database.Change) (int64, error)) *MockInstanceDomainRepositoryUpdateCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockInstanceDomainRepositoryUpdateCall) DoAndReturn(f func(context.Context, database.QueryExecutor, database.Condition, ...database.Change) (int64, error)) *MockInstanceDomainRepositoryUpdateCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// UpdatedAtColumn mocks base method.
func (m *MockInstanceDomainRepository) 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 *MockInstanceDomainRepositoryMockRecorder) UpdatedAtColumn() *MockInstanceDomainRepositoryUpdatedAtColumnCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatedAtColumn", reflect.TypeOf((*MockInstanceDomainRepository)(nil).UpdatedAtColumn))
return &MockInstanceDomainRepositoryUpdatedAtColumnCall{Call: call}
}
// MockInstanceDomainRepositoryUpdatedAtColumnCall wrap *gomock.Call
type MockInstanceDomainRepositoryUpdatedAtColumnCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockInstanceDomainRepositoryUpdatedAtColumnCall) Return(arg0 database.Column) *MockInstanceDomainRepositoryUpdatedAtColumnCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockInstanceDomainRepositoryUpdatedAtColumnCall) Do(f func() database.Column) *MockInstanceDomainRepositoryUpdatedAtColumnCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockInstanceDomainRepositoryUpdatedAtColumnCall) DoAndReturn(f func() database.Column) *MockInstanceDomainRepositoryUpdatedAtColumnCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
@@ -0,0 +1,52 @@
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 InstancesDomainRepo struct {
domain.InstanceDomainRepository
mock *MockInstanceDomainRepository
}
func NewInstancesDomainRepo(ctrl *gomock.Controller) *InstancesDomainRepo {
return &InstancesDomainRepo{
mock: NewMockInstanceDomainRepository(ctrl),
InstanceDomainRepository: repository.InstanceDomainRepository(),
}
}
func (r *InstancesDomainRepo) EXPECT() *MockInstanceDomainRepositoryMockRecorder {
return r.mock.EXPECT()
}
func (r *InstancesDomainRepo) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.InstanceDomain, error) {
return r.mock.Get(ctx, client, opts...)
}
func (r *InstancesDomainRepo) List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*domain.InstanceDomain, error) {
return r.mock.List(ctx, client, opts...)
}
func (r *InstancesDomainRepo) Add(ctx context.Context, client database.QueryExecutor, domain *domain.AddInstanceDomain) error {
return r.mock.Add(ctx, client, domain)
}
func (r *InstancesDomainRepo) 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 *InstancesDomainRepo) Remove(ctx context.Context, client database.QueryExecutor, condition database.Condition) (int64, error) {
return r.mock.Remove(ctx, client, condition)
}
+56
View File
@@ -0,0 +1,56 @@
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 InstanceRepo struct {
domain.InstanceRepository
mock *MockInstanceRepository
}
func NewInstanceRepo(ctrl *gomock.Controller) *InstanceRepo {
return &InstanceRepo{
mock: NewMockInstanceRepository(ctrl),
InstanceRepository: repository.InstanceRepository(),
}
}
func (r *InstanceRepo) EXPECT() *MockInstanceRepositoryMockRecorder {
return r.mock.EXPECT()
}
func (r *InstanceRepo) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.Instance, error) {
return r.mock.Get(ctx, client, opts...)
}
func (r *InstanceRepo) List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*domain.Instance, error) {
return r.mock.List(ctx, client, opts...)
}
func (r *InstanceRepo) Create(ctx context.Context, client database.QueryExecutor, instance *domain.Instance) error {
return r.mock.Create(ctx, client, instance)
}
func (r *InstanceRepo) Update(ctx context.Context, client database.QueryExecutor, instanceID string, changes ...database.Change) (int64, error) {
return r.mock.Update(ctx, client, instanceID, changes...)
}
func (r *InstanceRepo) Delete(ctx context.Context, client database.QueryExecutor, instanceID string) (int64, error) {
return r.mock.Delete(ctx, client, instanceID)
}
func (r *InstanceRepo) LoadDomains() domain.InstanceRepository {
return r.mock.LoadDomains()
}
@@ -22,6 +22,10 @@ func NewOrgDomainRepo(ctrl *gomock.Controller) *OrgDomainRepo {
}
}
func (r *OrgDomainRepo) EXPECT() *MockOrganizationDomainRepositoryMockRecorder {
return r.mock.EXPECT()
}
func (r *OrgDomainRepo) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.OrganizationDomain, error) {
return r.mock.Get(ctx, client, opts...)
+192
View File
@@ -0,0 +1,192 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/zitadel/zitadel/backend/v3/domain (interfaces: PermissionChecker)
//
// Generated by this command:
//
// mockgen -typed -package domainmock -destination ./mock/permission.mock.go . PermissionChecker
//
// Package domainmock is a generated GoMock package.
package domainmock
import (
context "context"
reflect "reflect"
gomock "go.uber.org/mock/gomock"
)
// MockPermissionChecker is a mock of PermissionChecker interface.
type MockPermissionChecker struct {
ctrl *gomock.Controller
recorder *MockPermissionCheckerMockRecorder
}
// MockPermissionCheckerMockRecorder is the mock recorder for MockPermissionChecker.
type MockPermissionCheckerMockRecorder struct {
mock *MockPermissionChecker
}
// NewMockPermissionChecker creates a new mock instance.
func NewMockPermissionChecker(ctrl *gomock.Controller) *MockPermissionChecker {
mock := &MockPermissionChecker{ctrl: ctrl}
mock.recorder = &MockPermissionCheckerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockPermissionChecker) EXPECT() *MockPermissionCheckerMockRecorder {
return m.recorder
}
// CheckInstancePermission mocks base method.
func (m *MockPermissionChecker) CheckInstancePermission(arg0 context.Context, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CheckInstancePermission", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// CheckInstancePermission indicates an expected call of CheckInstancePermission.
func (mr *MockPermissionCheckerMockRecorder) CheckInstancePermission(arg0, arg1 any) *MockPermissionCheckerCheckInstancePermissionCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckInstancePermission", reflect.TypeOf((*MockPermissionChecker)(nil).CheckInstancePermission), arg0, arg1)
return &MockPermissionCheckerCheckInstancePermissionCall{Call: call}
}
// MockPermissionCheckerCheckInstancePermissionCall wrap *gomock.Call
type MockPermissionCheckerCheckInstancePermissionCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockPermissionCheckerCheckInstancePermissionCall) Return(arg0 error) *MockPermissionCheckerCheckInstancePermissionCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockPermissionCheckerCheckInstancePermissionCall) Do(f func(context.Context, string) error) *MockPermissionCheckerCheckInstancePermissionCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockPermissionCheckerCheckInstancePermissionCall) DoAndReturn(f func(context.Context, string) error) *MockPermissionCheckerCheckInstancePermissionCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// CheckOrganizationPermission mocks base method.
func (m *MockPermissionChecker) CheckOrganizationPermission(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CheckOrganizationPermission", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// CheckOrganizationPermission indicates an expected call of CheckOrganizationPermission.
func (mr *MockPermissionCheckerMockRecorder) CheckOrganizationPermission(arg0, arg1, arg2 any) *MockPermissionCheckerCheckOrganizationPermissionCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckOrganizationPermission", reflect.TypeOf((*MockPermissionChecker)(nil).CheckOrganizationPermission), arg0, arg1, arg2)
return &MockPermissionCheckerCheckOrganizationPermissionCall{Call: call}
}
// MockPermissionCheckerCheckOrganizationPermissionCall wrap *gomock.Call
type MockPermissionCheckerCheckOrganizationPermissionCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockPermissionCheckerCheckOrganizationPermissionCall) Return(arg0 error) *MockPermissionCheckerCheckOrganizationPermissionCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockPermissionCheckerCheckOrganizationPermissionCall) Do(f func(context.Context, string, string) error) *MockPermissionCheckerCheckOrganizationPermissionCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockPermissionCheckerCheckOrganizationPermissionCall) DoAndReturn(f func(context.Context, string, string) error) *MockPermissionCheckerCheckOrganizationPermissionCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// CheckProjectGrantPermission mocks base method.
func (m *MockPermissionChecker) CheckProjectGrantPermission(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CheckProjectGrantPermission", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// CheckProjectGrantPermission indicates an expected call of CheckProjectGrantPermission.
func (mr *MockPermissionCheckerMockRecorder) CheckProjectGrantPermission(arg0, arg1, arg2 any) *MockPermissionCheckerCheckProjectGrantPermissionCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckProjectGrantPermission", reflect.TypeOf((*MockPermissionChecker)(nil).CheckProjectGrantPermission), arg0, arg1, arg2)
return &MockPermissionCheckerCheckProjectGrantPermissionCall{Call: call}
}
// MockPermissionCheckerCheckProjectGrantPermissionCall wrap *gomock.Call
type MockPermissionCheckerCheckProjectGrantPermissionCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockPermissionCheckerCheckProjectGrantPermissionCall) Return(arg0 error) *MockPermissionCheckerCheckProjectGrantPermissionCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockPermissionCheckerCheckProjectGrantPermissionCall) Do(f func(context.Context, string, string) error) *MockPermissionCheckerCheckProjectGrantPermissionCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockPermissionCheckerCheckProjectGrantPermissionCall) DoAndReturn(f func(context.Context, string, string) error) *MockPermissionCheckerCheckProjectGrantPermissionCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// CheckProjectPermission mocks base method.
func (m *MockPermissionChecker) CheckProjectPermission(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CheckProjectPermission", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// CheckProjectPermission indicates an expected call of CheckProjectPermission.
func (mr *MockPermissionCheckerMockRecorder) CheckProjectPermission(arg0, arg1, arg2 any) *MockPermissionCheckerCheckProjectPermissionCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckProjectPermission", reflect.TypeOf((*MockPermissionChecker)(nil).CheckProjectPermission), arg0, arg1, arg2)
return &MockPermissionCheckerCheckProjectPermissionCall{Call: call}
}
// MockPermissionCheckerCheckProjectPermissionCall wrap *gomock.Call
type MockPermissionCheckerCheckProjectPermissionCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockPermissionCheckerCheckProjectPermissionCall) Return(arg0 error) *MockPermissionCheckerCheckProjectPermissionCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockPermissionCheckerCheckProjectPermissionCall) Do(f func(context.Context, string, string) error) *MockPermissionCheckerCheckProjectPermissionCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockPermissionCheckerCheckProjectPermissionCall) DoAndReturn(f func(context.Context, string, string) error) *MockPermissionCheckerCheckProjectPermissionCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
+9
View File
@@ -2,6 +2,13 @@ package domain
import "context"
const (
InstanceReadPermission = "instance.read"
InstanceWritePermission = "instance.write"
OrganizationReadPermission = "organization.read"
OrganizationWritePermission = "organization.write"
)
// PermissionRepository is the interface that manages and checks Zitadel permissions.
//
// TODO(muhlemmer): This just defines the checker methods, rest to be done later.
@@ -10,6 +17,8 @@ type PermissionRepository interface {
}
// PermissionChecker defines the methods needed to check permissions.
//
//go:generate mockgen -typed -package domainmock -destination ./mock/permission.mock.go . PermissionChecker
type PermissionChecker interface {
// Check if the authenticated user has the given permission on instance level.
CheckInstancePermission(ctx context.Context, permission string) error
@@ -6,10 +6,16 @@ import (
"connectrpc.com/connect"
"google.golang.org/protobuf/types/known/timestamppb"
instancev2 "github.com/zitadel/zitadel/backend/v3/api/instance/v2"
"github.com/zitadel/zitadel/internal/api/authz"
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
)
func (s *Server) DeleteInstance(ctx context.Context, request *connect.Request[instance.DeleteInstanceRequest]) (*connect.Response[instance.DeleteInstanceResponse], error) {
if authz.GetFeatures(ctx).EnableRelationalTables {
return instancev2.DeleteInstance(ctx, request)
}
obj, err := s.command.RemoveInstance(ctx, request.Msg.GetInstanceId())
if err != nil {
return nil, err
@@ -22,6 +28,10 @@ func (s *Server) DeleteInstance(ctx context.Context, request *connect.Request[in
}
func (s *Server) UpdateInstance(ctx context.Context, request *connect.Request[instance.UpdateInstanceRequest]) (*connect.Response[instance.UpdateInstanceResponse], error) {
if authz.GetFeatures(ctx).EnableRelationalTables {
return instancev2.UpdateInstance(ctx, request)
}
obj, err := s.command.UpdateInstance(ctx, request.Msg.GetInstanceName())
if err != nil {
return nil, err
@@ -4,15 +4,18 @@ package instance_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/zitadel/zitadel/internal/integration"
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
)
@@ -20,66 +23,77 @@ func TestDeleteInstace(t *testing.T) {
t.Parallel()
// Given
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
inst := integration.NewInstance(ctxWithSysAuthZ)
instEventStore := integration.NewInstance(ctxWithSysAuthZ)
t.Cleanup(func() {
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
instRelational := integration.NewInstance(ctxWithSysAuthZ)
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, instRelational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
})
tt := []struct {
testName string
inputRequest *instance.DeleteInstanceRequest
inputContext context.Context
expectedErrorMsg string
expectedErrorCode codes.Code
expectedInstanceID string
}{
{
testName: "when invalid context should return unauthN error",
inputRequest: &instance.DeleteInstanceRequest{
InstanceId: " ",
},
inputContext: context.Background(),
expectedErrorCode: codes.Unauthenticated,
expectedErrorMsg: "auth header missing",
},
{
testName: "when invalid input should return invalid argument error",
inputRequest: &instance.DeleteInstanceRequest{
InstanceId: inst.ID() + "invalid",
},
inputContext: ctxWithSysAuthZ,
expectedErrorCode: codes.NotFound,
expectedErrorMsg: "Instance not found (COMMA-AE3GS)",
},
{
testName: "when delete succeeds should return deletion date",
inputRequest: &instance.DeleteInstanceRequest{
InstanceId: inst.ID(),
},
inputContext: ctxWithSysAuthZ,
expectedInstanceID: inst.ID(),
},
instances := []*integration.Instance{
instEventStore,
instRelational,
}
t.Cleanup(func() {
instRelational.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
instEventStore.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instEventStore.ID()})
})
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// Test
res, err := inst.Client.InstanceV2Beta.DeleteInstance(tc.inputContext, tc.inputRequest)
for _, inst := range instances {
tt := []struct {
testName string
inputRequest *instance.DeleteInstanceRequest
inputContext context.Context
expectedErrorMsg string
expectedErrorCode codes.Code
expectedInstanceID string
}{
{
testName: "when invalid context should return unauthN error",
inputRequest: &instance.DeleteInstanceRequest{
InstanceId: " ",
},
inputContext: context.Background(),
expectedErrorCode: codes.Unauthenticated,
expectedErrorMsg: "auth header missing",
},
{
testName: "when invalid input should return invalid argument error",
inputRequest: &instance.DeleteInstanceRequest{
InstanceId: inst.ID() + "invalid",
},
inputContext: ctxWithSysAuthZ,
expectedErrorCode: codes.NotFound,
expectedErrorMsg: "not found",
},
{
testName: "when delete succeeds should return deletion date",
inputRequest: &instance.DeleteInstanceRequest{
InstanceId: inst.ID(),
},
inputContext: ctxWithSysAuthZ,
expectedInstanceID: inst.ID(),
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// Test
res, err := inst.Client.InstanceV2Beta.DeleteInstance(tc.inputContext, tc.inputRequest)
// Verify
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
if tc.expectedErrorMsg == "" {
require.NotNil(t, res)
require.NotEmpty(t, res.GetDeletionDate())
}
})
// Verify
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
assert.Contains(t, status.Convert(err).Message(), tc.expectedErrorMsg)
if tc.expectedErrorMsg == "" {
require.NotNil(t, res)
require.NotEmpty(t, res.GetDeletionDate())
}
})
}
}
}
@@ -92,72 +106,90 @@ func TestUpdateInstace(t *testing.T) {
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
inst := integration.NewInstance(ctxWithSysAuthZ)
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
instEventStore := integration.NewInstance(ctxWithSysAuthZ)
orgOwnerESCtx := instEventStore.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
instRelational := integration.NewInstance(ctxWithSysAuthZ)
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, instRelational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
})
orgOwnerRelationalCtx := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
instances := []struct {
instance *integration.Instance
ctx context.Context
}{
{instance: instEventStore, ctx: orgOwnerESCtx},
{instance: instRelational, ctx: orgOwnerRelationalCtx},
}
t.Cleanup(func() {
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
instEventStore.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instEventStore.ID()})
instRelational.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
})
tt := []struct {
testName string
inputRequest *instance.UpdateInstanceRequest
inputContext context.Context
expectedErrorMsg string
expectedErrorCode codes.Code
expectedNewName string
}{
{
testName: "when invalid context should return unauthN error",
inputRequest: &instance.UpdateInstanceRequest{
InstanceId: inst.ID(),
InstanceName: " ",
for i, inst := range instances {
tt := []struct {
testName string
inputRequest *instance.UpdateInstanceRequest
inputContext context.Context
expectedErrorMsg string
expectedErrorCode codes.Code
expectedNewName string
}{
{
testName: "when invalid context should return unauthN error",
inputRequest: &instance.UpdateInstanceRequest{
InstanceId: inst.instance.ID(),
InstanceName: " ",
},
inputContext: context.Background(),
expectedErrorCode: codes.Unauthenticated,
expectedErrorMsg: "auth header missing",
},
inputContext: context.Background(),
expectedErrorCode: codes.Unauthenticated,
expectedErrorMsg: "auth header missing",
},
{
testName: "when unauthZ context should return unauthZ error",
inputRequest: &instance.UpdateInstanceRequest{
InstanceId: inst.ID(),
InstanceName: " ",
{
testName: "when unauthZ context should return unauthZ error",
inputRequest: &instance.UpdateInstanceRequest{
InstanceId: inst.instance.ID(),
InstanceName: " ",
},
inputContext: inst.ctx,
expectedErrorCode: codes.PermissionDenied,
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
},
inputContext: orgOwnerCtx,
expectedErrorCode: codes.PermissionDenied,
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
},
{
testName: "when update succeeds should change instance name",
inputRequest: &instance.UpdateInstanceRequest{
InstanceId: inst.ID(),
InstanceName: "an-updated-name",
{
testName: "when update succeeds should change instance name",
inputRequest: &instance.UpdateInstanceRequest{
InstanceId: inst.instance.ID(),
InstanceName: fmt.Sprintf("an-updated-name %d", i),
},
inputContext: ctxWithSysAuthZ,
expectedNewName: fmt.Sprintf("an-updated-name %d", i),
},
inputContext: ctxWithSysAuthZ,
expectedNewName: "an-updated-name",
},
}
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// Test
res, err := inst.Client.InstanceV2Beta.UpdateInstance(tc.inputContext, tc.inputRequest)
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// Test
res, err := inst.instance.Client.InstanceV2Beta.UpdateInstance(tc.inputContext, tc.inputRequest)
// Verify
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
if tc.expectedErrorMsg == "" {
// Verify
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
if tc.expectedErrorMsg == "" {
require.NotNil(t, res)
assert.NotEmpty(t, res.GetChangeDate())
require.NotNil(t, res)
assert.NotEmpty(t, res.GetChangeDate())
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputContext, time.Minute)
require.EventuallyWithT(t, func(tt *assert.CollectT) {
retrievedInstance, err := inst.instance.Client.InstanceV2Beta.GetInstance(tc.inputContext, &instance.GetInstanceRequest{InstanceId: inst.instance.ID()})
require.Nil(tt, err)
assert.Equal(tt, tc.expectedNewName, retrievedInstance.GetInstance().GetName())
}, retryDuration, tick, "timeout waiting for expected execution result")
}
})
}
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputContext, time.Minute)
require.EventuallyWithT(t, func(tt *assert.CollectT) {
retrievedInstance, err := inst.Client.InstanceV2Beta.GetInstance(tc.inputContext, &instance.GetInstanceRequest{InstanceId: inst.ID()})
require.Nil(tt, err)
assert.Equal(tt, tc.expectedNewName, retrievedInstance.GetInstance().GetName())
}, retryDuration, tick, "timeout waiting for expected execution result")
}
})
}
}
@@ -4,16 +4,18 @@ package instance_test
import (
"context"
"slices"
"fmt"
"testing"
"time"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/zitadel/zitadel/internal/integration"
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
@@ -27,43 +29,76 @@ func TestGetInstance(t *testing.T) {
defer cancel()
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
inst := integration.NewInstance(ctxWithSysAuthZ)
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
instEventStore := integration.NewInstance(ctxWithSysAuthZ)
orgOwnerCtxES := instEventStore.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
instRelational := integration.NewInstance(ctxWithSysAuthZ)
orgOwnerCtxREL := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, instRelational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
assert.True(tCollect, got.GetEnableRelationalTables().GetEnabled())
})
t.Cleanup(func() {
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
instRelational.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
instEventStore.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instEventStore.ID()})
})
tt := []struct {
testName string
instance *integration.Instance
inputContext context.Context
expectedInstanceID string
expectedErrorMsg string
expectedErrorCode codes.Code
}{
{
testName: "when unauthN context should return unauthN error",
testName: "eventstore instance - when unauthN context should return unauthN error",
inputContext: context.Background(),
instance: instEventStore,
expectedErrorCode: codes.Unauthenticated,
expectedErrorMsg: "auth header missing",
},
{
testName: "when unauthZ context should return unauthZ error",
inputContext: orgOwnerCtx,
testName: "eventstore instance - when unauthZ context should return unauthZ error",
inputContext: orgOwnerCtxES,
instance: instEventStore,
expectedErrorCode: codes.PermissionDenied,
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
},
{
testName: "when request succeeds should return matching instance",
testName: "eventstore instance - when request succeeds should return matching instance",
inputContext: ctxWithSysAuthZ,
expectedInstanceID: inst.ID(),
instance: instEventStore,
expectedInstanceID: instEventStore.ID(),
},
// Relational
{
testName: "relational instance - when unauthN context should return unauthN error",
inputContext: context.Background(),
instance: instRelational,
expectedErrorCode: codes.Unauthenticated,
expectedErrorMsg: "auth header missing",
},
{
testName: "relational instance - when unauthZ context should return unauthZ error",
inputContext: orgOwnerCtxREL,
instance: instRelational,
expectedErrorCode: codes.PermissionDenied,
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
},
{
testName: "relational instance - when request succeeds should return matching instance",
inputContext: ctxWithSysAuthZ,
instance: instRelational,
expectedInstanceID: instRelational.ID(),
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// Test
res, err := inst.Client.InstanceV2Beta.GetInstance(tc.inputContext, &instance.GetInstanceRequest{InstanceId: inst.ID()})
res, err := tc.instance.Client.InstanceV2Beta.GetInstance(tc.inputContext, &instance.GetInstanceRequest{InstanceId: tc.instance.ID()})
// Verify
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
@@ -86,26 +121,26 @@ func TestListInstances(t *testing.T) {
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
instances := make([]*integration.Instance, 2)
instances := make([]*integration.Instance, 5)
inst := integration.NewInstance(ctxWithSysAuthZ)
inst2 := integration.NewInstance(ctxWithSysAuthZ)
instances[0], instances[1] = inst, inst2
inst3 := integration.NewInstance(ctxWithSysAuthZ)
inst4 := integration.NewInstance(ctxWithSysAuthZ)
inst5 := integration.NewInstance(ctxWithSysAuthZ)
instances[0], instances[1], instances[2], instances[3], instances[4] = inst, inst2, inst3, inst4, inst5
t.Cleanup(func() {
inst.Client.FeatureV2.ResetInstanceFeatures(ctxWithSysAuthZ, &feature.ResetInstanceFeaturesRequest{})
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst2.ID()})
inst2.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst2.ID()})
inst3.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst3.ID()})
inst4.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst4.ID()})
inst5.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst5.ID()})
})
// Sort in descending order
slices.SortFunc(instances, func(i1, i2 *integration.Instance) int {
res := i1.Instance.Details.CreationDate.AsTime().Compare(i2.Instance.Details.CreationDate.AsTime())
if res == 0 {
return res
}
return -res
})
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
relTableState := integration.RelationalTablesEnableMatrix()
tt := []struct {
testName string
@@ -136,43 +171,57 @@ func TestListInstances(t *testing.T) {
{
testName: "when valid request with filter should return paginated response",
inputRequest: &instance.ListInstancesRequest{
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
Pagination: &filter.PaginationRequest{Offset: 1, Limit: 3},
SortingColumn: instance.FieldName_FIELD_NAME_CREATION_DATE.Enum(),
Queries: []*instance.Query{
{
Query: &instance.Query_IdQuery{
IdQuery: &instance.IdsQuery{
Ids: []string{inst.ID(), inst2.ID()},
Ids: []string{inst.ID(), inst2.ID(), inst3.ID(), inst4.ID(), inst5.ID()},
},
},
},
{
Query: &instance.Query_DomainQuery{
DomainQuery: &instance.DomainsQuery{
Domains: []string{inst4.Domain, inst3.Domain, inst2.Domain, inst.Domain},
},
},
},
},
},
inputContext: ctxWithSysAuthZ,
expectedInstances: []string{inst2.ID(), inst.ID()},
expectedInstances: []string{inst3.ID(), inst2.ID(), inst.ID()},
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// Test
res, err := inst.Client.InstanceV2Beta.ListInstances(tc.inputContext, tc.inputRequest)
// Verify
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
if tc.expectedErrorMsg == "" {
require.NotNil(t, res)
require.Len(t, res.GetInstances(), len(tc.expectedInstances))
for i, ins := range res.GetInstances() {
assert.Equal(t, tc.expectedInstances[i], ins.GetId())
}
}
for _, stateCase := range relTableState {
integration.EnsureInstanceFeature(t, ctx, inst, 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.testName), func(t *testing.T) {
// Test
res, err := inst.Client.InstanceV2Beta.ListInstances(tc.inputContext, tc.inputRequest)
// Verify
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
if tc.expectedErrorMsg == "" {
require.NotNil(t, res)
require.Len(t, res.GetInstances(), len(tc.expectedInstances))
for i, ins := range res.GetInstances() {
assert.Equal(t, tc.expectedInstances[i], ins.GetId())
}
}
})
}
}
}
func TestListCustomDomains(t *testing.T) {
+11 -1
View File
@@ -5,11 +5,17 @@ import (
"connectrpc.com/connect"
instancev2 "github.com/zitadel/zitadel/backend/v3/api/instance/v2"
"github.com/zitadel/zitadel/internal/api/authz"
filter "github.com/zitadel/zitadel/internal/api/grpc/filter/v2beta"
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
)
func (s *Server) GetInstance(ctx context.Context, _ *connect.Request[instance.GetInstanceRequest]) (*connect.Response[instance.GetInstanceResponse], error) {
func (s *Server) GetInstance(ctx context.Context, request *connect.Request[instance.GetInstanceRequest]) (*connect.Response[instance.GetInstanceResponse], error) {
if authz.GetFeatures(ctx).EnableRelationalTables {
return instancev2.GetInstance(ctx, request)
}
inst, err := s.query.Instance(ctx, true)
if err != nil {
return nil, err
@@ -21,6 +27,10 @@ func (s *Server) GetInstance(ctx context.Context, _ *connect.Request[instance.Ge
}
func (s *Server) ListInstances(ctx context.Context, req *connect.Request[instance.ListInstancesRequest]) (*connect.Response[instance.ListInstancesResponse], error) {
if authz.GetFeatures(ctx).EnableRelationalTables {
return instancev2.ListInstances(ctx, req)
}
queries, err := ListInstancesRequestToModel(req.Msg, s.systemDefaults)
if err != nil {
return nil, err
+4 -4
View File
@@ -38,13 +38,13 @@ type RelationalTableFeatureMatrix struct {
// 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)},
},
{
State: "when relational tables are enabled",
FeatureSet: &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)},
},
}
}