mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
# Which Problems Are Solved This PR implements the endpoints listed in https://github.com/zitadel/zitadel/issues/10444 . # How the Problems Are Solved The implementation follows the same pattern as the Instance one. The `Add` and `Remove` are implementing the `Commander` interface, while `List` Domains endpoints are implementing the `Querier` interace. The PR adds also some forgotten calls to the relational logic for the Instance API. The commits are identifiable by the label `instance:` prepended to them. This is the complete list: bafce01b0 , 6fe7257ab , e27bc9c34 , 8133ede73 , fde667953 , a3e1135ed , ee1a6f9c1 # Additional Context - Closes #10444 --------- Co-authored-by: Elio Bischof <[email protected]> Co-authored-by: Lebogang Phoshoko <[email protected]> Co-authored-by: Wim Van Laer <[email protected]> Co-authored-by: Max Peintner <[email protected]> Co-authored-by: Gayathri Vijayan <[email protected]>
329 lines
10 KiB
Go
329 lines
10 KiB
Go
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/v2"
|
|
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
|
)
|
|
|
|
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,
|
|
},
|
|
|
|
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,
|
|
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,
|
|
},
|
|
expectedSortingDirection: database.OrderDirectionDesc,
|
|
expectedOrderBy: database.Columns{database.NewColumn("instances", "id")},
|
|
},
|
|
{
|
|
name: "sort by id ASC",
|
|
request: &instance.ListInstancesRequest{
|
|
SortingColumn: instance.FieldName_FIELD_NAME_ID,
|
|
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,
|
|
},
|
|
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,
|
|
Filters: []*instance.Filter{
|
|
{Filter: &instance.Filter_InIdsFilter{InIdsFilter: &filter.InIDsFilter{
|
|
Ids: []string{"instance-1", "instance-2"},
|
|
}}},
|
|
{Filter: &instance.Filter_CustomDomainsFilter{CustomDomainsFilter: &instance.CustomDomainsFilter{
|
|
Domains: []string{"domain1.example.com", "domain2.example.com"},
|
|
}}},
|
|
},
|
|
},
|
|
expectedError: zerrors.ThrowInternal(listErr, "DOM-AIRPxN", "Errors.Instance.List"),
|
|
},
|
|
{
|
|
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,
|
|
Filters: []*instance.Filter{
|
|
{Filter: &instance.Filter_InIdsFilter{InIdsFilter: &filter.InIDsFilter{
|
|
Ids: []string{"instance-1", "instance-2"},
|
|
}}},
|
|
{Filter: &instance.Filter_CustomDomainsFilter{CustomDomainsFilter: &instance.CustomDomainsFilter{
|
|
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", "Errors.PermissionDenied"),
|
|
},
|
|
{
|
|
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)
|
|
|
|
})
|
|
}
|
|
}
|