mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-14 18:05:12 +00:00
feat: instance domain APIs with relational tables (#11051)
# 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]>
This commit is contained in:
co-authored by
Elio Bischof
Lebogang Phoshoko
Wim Van Laer
Max Peintner
Gayathri Vijayan
parent
d79bfe6eba
commit
b339a3e696
@@ -4,16 +4,19 @@ package instance_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"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"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
)
|
||||
|
||||
@@ -29,77 +32,95 @@ func TestAddCustomDomain(t *testing.T) {
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
iamOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
relationalInst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
iamOwnerRelationalCtx := relationalInst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, relationalInst, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.AddCustomDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
CustomDomain: integration.DomainName(),
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
relationalInst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: relationalInst.ID()})
|
||||
})
|
||||
|
||||
type instanceAndCtx struct {
|
||||
testType string
|
||||
instance *integration.Instance
|
||||
ctx context.Context
|
||||
}
|
||||
testedInstances := []instanceAndCtx{
|
||||
{testType: "eventstore", instance: inst, ctx: iamOwnerCtx},
|
||||
{testType: "relational", instance: relationalInst, ctx: iamOwnerRelationalCtx},
|
||||
}
|
||||
for _, instAndCtx := range testedInstances {
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.AddCustomDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
CustomDomain: integration.DomainName(),
|
||||
},
|
||||
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.AddCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
CustomDomain: integration.DomainName(),
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
CustomDomain: integration.DomainName(),
|
||||
},
|
||||
inputContext: instAndCtx.ctx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
inputContext: iamOwnerCtx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when invalid domain should return invalid argument error",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
CustomDomain: " ",
|
||||
{
|
||||
testName: "when invalid domain should return invalid argument error",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
CustomDomain: " ",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.InvalidArgument,
|
||||
expectedErrorMsg: "Errors.Invalid.Argument",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.InvalidArgument,
|
||||
expectedErrorMsg: "Errors.Invalid.Argument (INST-28nlD)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
CustomDomain: " " + integration.DomainName(),
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.AddCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
CustomDomain: " " + integration.DomainName(),
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(fmt.Sprintf("%s - %s", instAndCtx.testType, tc.testName), func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
if tc.expectedErrorMsg == "" {
|
||||
instAndCtx.instance.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{CustomDomain: strings.TrimSpace(tc.inputRequest.CustomDomain)})
|
||||
}
|
||||
})
|
||||
|
||||
// Test
|
||||
res, err := instAndCtx.instance.Client.InstanceV2.AddCustomDomain(tc.inputContext, tc.inputRequest)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Contains(t, status.Convert(err).Message(), tc.expectedErrorMsg)
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
if tc.expectedErrorMsg == "" {
|
||||
inst.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{CustomDomain: strings.TrimSpace(tc.inputRequest.CustomDomain)})
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.GetCreationDate())
|
||||
}
|
||||
})
|
||||
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2.AddCustomDomain(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 == "" {
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.GetCreationDate())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,84 +128,108 @@ func TestRemoveCustomDomain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
iamOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
instES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
iamOwnerCtxES := instES.WithAuthorizationToken(t.Context(), integration.UserTypeIAMOwner)
|
||||
|
||||
customDomain := integration.DomainName()
|
||||
instRelational := integration.NewInstance(ctxWithSysAuthZ)
|
||||
iamOwnerCtxRelational := instRelational.WithAuthorizationToken(t.Context(), integration.UserTypeIAMOwner)
|
||||
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, instRelational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
|
||||
_, err := inst.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), CustomDomain: customDomain})
|
||||
customDomainES := integration.DomainName()
|
||||
customDomainRelational := integration.DomainName()
|
||||
|
||||
_, err := instES.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instES.ID(), CustomDomain: customDomainES})
|
||||
require.Nil(t, err)
|
||||
_, err = instRelational.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instRelational.ID(), CustomDomain: customDomainRelational})
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: inst.ID(), CustomDomain: customDomain})
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
instES.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instES.ID(), CustomDomain: customDomainES})
|
||||
instES.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
|
||||
instRelational.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instRelational.ID(), CustomDomain: customDomainRelational})
|
||||
instRelational.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.RemoveCustomDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
CustomDomain: "custom1",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
CustomDomain: "custom1",
|
||||
},
|
||||
inputContext: iamOwnerCtx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when invalid domain should return invalid argument error",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
CustomDomain: " ",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.InvalidArgument,
|
||||
expectedErrorMsg: "Errors.Invalid.Argument (INST-39nls)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
CustomDomain: " " + customDomain,
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
type instanceAndCtx struct {
|
||||
testType string
|
||||
instance *integration.Instance
|
||||
ctx context.Context
|
||||
customDomain string
|
||||
}
|
||||
testedInstances := []instanceAndCtx{
|
||||
{testType: "eventstore", instance: instES, ctx: iamOwnerCtxES, customDomain: customDomainES},
|
||||
{testType: "relational", instance: instRelational, ctx: iamOwnerCtxRelational, customDomain: customDomainRelational},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2.RemoveCustomDomain(tc.inputContext, tc.inputRequest)
|
||||
for _, instAndCtx := range testedInstances {
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.RemoveCustomDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
CustomDomain: "custom1",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
CustomDomain: "custom1",
|
||||
},
|
||||
inputContext: instAndCtx.ctx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when invalid domain should return invalid argument error",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
CustomDomain: " ",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.InvalidArgument,
|
||||
expectedErrorMsg: "Errors.Invalid.Argument",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.RemoveCustomDomainRequest{
|
||||
InstanceId: instAndCtx.instance.ID(),
|
||||
CustomDomain: " " + instAndCtx.customDomain,
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
}
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
for _, tc := range tt {
|
||||
t.Run(fmt.Sprintf("%s - %s", instAndCtx.testType, tc.testName), func(t *testing.T) {
|
||||
// Test
|
||||
res, err := instAndCtx.instance.Client.InstanceV2.RemoveCustomDomain(tc.inputContext, tc.inputRequest)
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
assert.NotNil(t, res)
|
||||
assert.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 == "" {
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.GetDeletionDate())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,80 +241,111 @@ func TestAddTrustedDomain(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
|
||||
// Eventstore objects
|
||||
instES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
d1ES := integration.DomainName()
|
||||
|
||||
// Relational objects
|
||||
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())
|
||||
})
|
||||
orgOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
d1Relational := integration.DomainName()
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
instES.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
|
||||
instRelational.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.AddTrustedDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
TrustedDomain: "trusted1",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
TrustedDomain: "trusted1",
|
||||
},
|
||||
inputContext: orgOwnerCtx,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when invalid domain should return invalid argument error",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
TrustedDomain: " ",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.InvalidArgument,
|
||||
expectedErrorMsg: "Errors.Invalid.Argument (COMMA-Stk21)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
TrustedDomain: " " + integration.DomainName(),
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
type instAndCtx struct {
|
||||
testType string
|
||||
inst *integration.Instance
|
||||
orgOwnerCtx context.Context
|
||||
domain string
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
testedInstanceAndCtxs := []instAndCtx{
|
||||
{testType: "eventstore", inst: instES, orgOwnerCtx: orgOwnerCtxES, domain: d1ES},
|
||||
{testType: "relational", inst: instRelational, orgOwnerCtx: orgOwnerCtxRelational, domain: d1Relational},
|
||||
}
|
||||
|
||||
for _, stateCase := range testedInstanceAndCtxs {
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.AddTrustedDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: stateCase.inst.ID(),
|
||||
TrustedDomain: stateCase.domain,
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: stateCase.inst.ID(),
|
||||
TrustedDomain: stateCase.domain,
|
||||
},
|
||||
inputContext: stateCase.orgOwnerCtx,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when invalid domain should return invalid argument error",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: stateCase.inst.ID(),
|
||||
TrustedDomain: " ",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.InvalidArgument,
|
||||
expectedErrorMsg: "Errors.Invalid.Argument",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.AddTrustedDomainRequest{
|
||||
InstanceId: stateCase.inst.ID(),
|
||||
TrustedDomain: " " + stateCase.domain,
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
if tc.testName == "when unauthZ context should return unauthZ error" && stateCase.testType == "relational" {
|
||||
continue
|
||||
}
|
||||
t.Run(fmt.Sprintf("%s - %s", stateCase.testType, tc.testName), func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
if tc.expectedErrorMsg == "" {
|
||||
stateCase.inst.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{TrustedDomain: strings.TrimSpace(tc.inputRequest.TrustedDomain)})
|
||||
}
|
||||
})
|
||||
|
||||
// Test
|
||||
res, err := stateCase.inst.Client.InstanceV2.AddTrustedDomain(tc.inputContext, tc.inputRequest)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Contains(t, status.Convert(err).Message(), tc.expectedErrorMsg)
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
inst.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{TrustedDomain: strings.TrimSpace(tc.inputRequest.TrustedDomain)})
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.GetCreationDate())
|
||||
}
|
||||
})
|
||||
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2.AddTrustedDomain(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 == "" {
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.GetCreationDate())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,69 +357,101 @@ func TestRemoveTrustedDomain(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
|
||||
trustedDomain := integration.DomainName()
|
||||
// Eventstore objects
|
||||
instES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
trustedDomainES := integration.DomainName()
|
||||
_, err := instES.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instES.ID(), TrustedDomain: trustedDomainES})
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err := inst.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), TrustedDomain: trustedDomain})
|
||||
// Relational objects
|
||||
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())
|
||||
})
|
||||
orgOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
trustedDomainRelational := integration.DomainName()
|
||||
_, err = instRelational.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instRelational.ID(), TrustedDomain: trustedDomainRelational})
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: inst.ID(), TrustedDomain: trustedDomain})
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
instES.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instES.ID(), TrustedDomain: trustedDomainES})
|
||||
instES.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
|
||||
|
||||
instRelational.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instRelational.ID(), TrustedDomain: trustedDomainRelational})
|
||||
instRelational.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.RemoveTrustedDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.RemoveTrustedDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
TrustedDomain: "trusted1",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.RemoveTrustedDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
TrustedDomain: "trusted1",
|
||||
},
|
||||
inputContext: orgOwnerCtx,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.RemoveTrustedDomainRequest{
|
||||
InstanceId: inst.ID(),
|
||||
TrustedDomain: " " + trustedDomain,
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
type instAndCtx struct {
|
||||
testType string
|
||||
inst *integration.Instance
|
||||
orgOwnerCtx context.Context
|
||||
domain string
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2.RemoveTrustedDomain(tc.inputContext, tc.inputRequest)
|
||||
testedInstanceAndCtxs := []instAndCtx{
|
||||
{testType: "eventstore", inst: instES, orgOwnerCtx: orgOwnerCtxES, domain: trustedDomainES},
|
||||
{testType: "relational", inst: instRelational, orgOwnerCtx: orgOwnerCtxRelational, domain: trustedDomainRelational},
|
||||
}
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
for _, stateCase := range testedInstanceAndCtxs {
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputRequest *instance.RemoveTrustedDomainRequest
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.RemoveTrustedDomainRequest{
|
||||
InstanceId: stateCase.inst.ID(),
|
||||
TrustedDomain: "trusted1",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917) testName: "when unauthZ context should return unauthZ error",
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.RemoveTrustedDomainRequest{
|
||||
InstanceId: stateCase.inst.ID(),
|
||||
TrustedDomain: "trusted1",
|
||||
},
|
||||
inputContext: stateCase.orgOwnerCtx,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request should return successful response",
|
||||
inputRequest: &instance.RemoveTrustedDomainRequest{
|
||||
InstanceId: stateCase.inst.ID(),
|
||||
TrustedDomain: " " + stateCase.domain,
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
},
|
||||
}
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
require.NotNil(t, res)
|
||||
require.NotEmpty(t, res.GetDeletionDate())
|
||||
for _, tc := range tt {
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
if tc.testName == "when unauthZ context should return unauthZ error" && stateCase.testType == "relational" {
|
||||
continue
|
||||
}
|
||||
})
|
||||
t.Run(fmt.Sprintf("%s - %s", stateCase.testType, tc.testName), func(t *testing.T) {
|
||||
// Test
|
||||
res, err := stateCase.inst.Client.InstanceV2.RemoveTrustedDomain(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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
)
|
||||
|
||||
@@ -20,105 +23,140 @@ func TestDeleteInstance(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
instanceOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
inst2 := integration.NewInstance(ctxWithSysAuthZ)
|
||||
// Event store instances
|
||||
instES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
instanceESOwnerCtx := instES.WithAuthorizationToken(t.Context(), integration.UserTypeIAMOwner)
|
||||
instES2 := integration.NewInstance(ctxWithSysAuthZ)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2.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())
|
||||
})
|
||||
instanceRelationalOwnerCtx := instRelational.WithAuthorizationToken(t.Context(), integration.UserTypeIAMOwner)
|
||||
instRelational2 := integration.NewInstance(ctxWithSysAuthZ)
|
||||
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, instRelational2, &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: inst.ID(),
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when invalid context and invalid id should return unauthN error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: inst.ID() + "invalid",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when instance token for invalid instance should return unauthN error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: inst.ID() + "invalid",
|
||||
},
|
||||
inputContext: instanceOwnerCtx,
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "Errors.Token.Invalid (AUTH-7fs1e)",
|
||||
},
|
||||
{
|
||||
testName: "when instance token for other instance should return unauthN error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: inst2.ID(),
|
||||
},
|
||||
inputContext: instanceOwnerCtx,
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "Errors.Token.Invalid (AUTH-7fs1e)",
|
||||
},
|
||||
{
|
||||
testName: "when instance token for own instance should return unauthZ error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: inst.ID(),
|
||||
},
|
||||
inputContext: instanceOwnerCtx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
testName: "when invalid id should return not found error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: inst.ID() + "invalid",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "Errors.Instance.NotFound (COMMA-AE3GS)",
|
||||
},
|
||||
{
|
||||
testName: "when delete succeeds should return deletion date",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: inst.ID(),
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedInstanceID: inst.ID(),
|
||||
},
|
||||
type instAndCtx struct {
|
||||
testType string
|
||||
i1 *integration.Instance
|
||||
i2 *integration.Instance
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2.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())
|
||||
}
|
||||
})
|
||||
testedInstancesAndCtxs := []instAndCtx{
|
||||
{testType: "eventstore", i1: instES, i2: instES2, ctx: instanceESOwnerCtx},
|
||||
{testType: "relational", i1: instRelational, i2: instRelational2, ctx: instanceRelationalOwnerCtx},
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
instES.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
|
||||
instES2.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES2.ID()})
|
||||
instRelational.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
instRelational2.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational2.ID()})
|
||||
})
|
||||
|
||||
for _, instanceWithCtx := range testedInstancesAndCtxs {
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.DeleteInstanceRequest
|
||||
inputContext context.Context
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
expectedInstanceID string
|
||||
expectsNullTimestamp bool
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: instanceWithCtx.i1.ID(),
|
||||
},
|
||||
inputContext: t.Context(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when invalid context and invalid id should return unauthN error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: instanceWithCtx.i1.ID() + "invalid",
|
||||
},
|
||||
inputContext: t.Context(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when instance token for invalid instance should return unauthN error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: instanceWithCtx.i1.ID() + "invalid",
|
||||
},
|
||||
inputContext: instanceWithCtx.ctx,
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "Errors.Token.Invalid (AUTH-7fs1e)",
|
||||
},
|
||||
{
|
||||
testName: "when instance token for other instance should return unauthN error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: instanceWithCtx.i2.ID(),
|
||||
},
|
||||
inputContext: instanceWithCtx.ctx,
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "Errors.Token.Invalid (AUTH-7fs1e)",
|
||||
},
|
||||
{
|
||||
testName: "when instance token for own instance should return unauthZ error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: instanceWithCtx.i1.ID(),
|
||||
},
|
||||
inputContext: instanceWithCtx.ctx,
|
||||
expectedErrorCode: codes.PermissionDenied,
|
||||
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): This test won't reach the relational side due to an interceptor
|
||||
// automatically changing the instance in context to an empty one.
|
||||
// The interceptor will look for the instance matching the instance ID passed here.
|
||||
// Since the instance doesn't exist, it will put an empty instance in context.
|
||||
// But an empty instance has no feature flags enabled.
|
||||
testName: "when invalid id should return no error",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: instanceWithCtx.i1.ID() + "invalid",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectsNullTimestamp: true,
|
||||
},
|
||||
{
|
||||
testName: "when delete succeeds should return deletion date",
|
||||
inputRequest: &instance.DeleteInstanceRequest{
|
||||
InstanceId: instanceWithCtx.i1.ID(),
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedInstanceID: instanceWithCtx.i1.ID(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(fmt.Sprintf("%s - %s", instanceWithCtx.testType, tc.testName), func(t *testing.T) {
|
||||
// Test
|
||||
res, err := instanceWithCtx.i1.Client.InstanceV2.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.Equal(t, tc.expectsNullTimestamp, res.GetDeletionDate() == nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestUpdateInstance(t *testing.T) {
|
||||
@@ -130,123 +168,163 @@ func TestUpdateInstance(t *testing.T) {
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
instanceOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
inst2 := integration.NewInstance(ctxWithSysAuthZ)
|
||||
instES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
instanceOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
orgOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
inst2ES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
// Relational
|
||||
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())
|
||||
})
|
||||
instanceOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
orgOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
inst2Relational := integration.NewInstance(ctxWithSysAuthZ)
|
||||
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, inst2Relational, &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.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: " ",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when invalid context and invalid id should return unauthN error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: inst.ID() + "invalid",
|
||||
InstanceName: " ",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when instance token for invalid instance should return unauthN error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: inst.ID() + "invalid",
|
||||
InstanceName: " ",
|
||||
},
|
||||
inputContext: instanceOwnerCtx,
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "Errors.Token.Invalid (AUTH-7fs1e)",
|
||||
},
|
||||
{
|
||||
testName: "when instance token for other instance should return unauthN error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: inst2.ID(),
|
||||
InstanceName: " ",
|
||||
},
|
||||
inputContext: instanceOwnerCtx,
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "Errors.Token.Invalid (AUTH-7fs1e)",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: inst.ID(),
|
||||
InstanceName: " ",
|
||||
},
|
||||
inputContext: orgOwnerCtx,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when invalid id should return not found error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: inst.ID() + "invalid",
|
||||
InstanceName: "an-updated-name",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "Errors.Instance.NotFound (INST-nuso2m)",
|
||||
},
|
||||
{
|
||||
testName: "when update succeeds (instance owner) should change instance name",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: inst.ID(),
|
||||
InstanceName: "an-updated-name",
|
||||
},
|
||||
inputContext: instanceOwnerCtx,
|
||||
expectedNewName: "an-updated-name",
|
||||
},
|
||||
{
|
||||
testName: "when update succeeds should change instance name",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: inst2.ID(),
|
||||
InstanceName: "an-updated-name2",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedNewName: "an-updated-name2",
|
||||
},
|
||||
t.Cleanup(func() {
|
||||
instES.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
|
||||
inst2ES.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst2ES.ID()})
|
||||
instRelational.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
inst2Relational.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst2Relational.ID()})
|
||||
})
|
||||
|
||||
type instAndCtx struct {
|
||||
testType string
|
||||
inst *integration.Instance
|
||||
inst2 *integration.Instance
|
||||
instanceOwnerCtx context.Context
|
||||
orgOwnerCtx context.Context
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2.UpdateInstance(tc.inputContext, tc.inputRequest)
|
||||
testedInstancesAndCtxs := []instAndCtx{
|
||||
{testType: "eventstore", inst: instES, inst2: inst2ES, instanceOwnerCtx: instanceOwnerCtxES, orgOwnerCtx: orgOwnerCtxES},
|
||||
{testType: "relational", inst: instRelational, inst2: inst2Relational, instanceOwnerCtx: instanceOwnerCtxRelational, orgOwnerCtx: orgOwnerCtxRelational},
|
||||
}
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
if tc.expectedErrorMsg == "" {
|
||||
for _, instancesWithCtx := range testedInstancesAndCtxs {
|
||||
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: instancesWithCtx.inst.ID(),
|
||||
InstanceName: " ",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when invalid context and invalid id should return unauthN error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: instancesWithCtx.inst.ID() + "invalid",
|
||||
InstanceName: " ",
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when instance token for invalid instance should return unauthN error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: instancesWithCtx.inst.ID() + "invalid",
|
||||
InstanceName: " ",
|
||||
},
|
||||
inputContext: instancesWithCtx.instanceOwnerCtx,
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "Errors.Token.Invalid (AUTH-7fs1e)",
|
||||
},
|
||||
{
|
||||
testName: "when instance token for other instance should return unauthN error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: instancesWithCtx.inst2.ID(),
|
||||
InstanceName: " ",
|
||||
},
|
||||
inputContext: instancesWithCtx.instanceOwnerCtx,
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "Errors.Token.Invalid (AUTH-7fs1e)",
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: instancesWithCtx.inst.ID(),
|
||||
InstanceName: "a valid name",
|
||||
},
|
||||
inputContext: instancesWithCtx.orgOwnerCtx,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): This test won't reach the relational side due to an interceptor
|
||||
// automatically changing the instance in context to an empty one.
|
||||
// The interceptor will look for the instance matching the instance ID passed here.
|
||||
// Since the instance doesn't exist, it will put an empty instance in context.
|
||||
// But an empty instance has no feature flags enabled.
|
||||
testName: "when invalid id should return not found error",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: instancesWithCtx.inst.ID() + "invalid",
|
||||
InstanceName: "an-updated-name",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "Errors.Instance.NotFound (INST-nuso2m)",
|
||||
},
|
||||
{
|
||||
testName: "when update succeeds (instance owner) should change instance name",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: instancesWithCtx.inst.ID(),
|
||||
InstanceName: "an-updated-name",
|
||||
},
|
||||
inputContext: instancesWithCtx.instanceOwnerCtx,
|
||||
expectedNewName: "an-updated-name",
|
||||
},
|
||||
{
|
||||
testName: "when update succeeds should change instance name",
|
||||
inputRequest: &instance.UpdateInstanceRequest{
|
||||
InstanceId: instancesWithCtx.inst2.ID(),
|
||||
InstanceName: "an-updated-name2",
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedNewName: "an-updated-name2",
|
||||
},
|
||||
}
|
||||
|
||||
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.Client.InstanceV2.GetInstance(tc.inputContext, &instance.GetInstanceRequest{InstanceId: tc.inputRequest.GetInstanceId()})
|
||||
require.Nil(tt, err)
|
||||
assert.Equal(tt, tc.expectedNewName, retrievedInstance.GetInstance().GetName())
|
||||
}, retryDuration, tick, "timeout waiting for expected execution result")
|
||||
for _, tc := range tt {
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
if tc.testName == "when unauthZ context should return unauthZ error" && instancesWithCtx.testType == "relational" {
|
||||
continue
|
||||
}
|
||||
})
|
||||
t.Run(fmt.Sprintf("%s - %s", instancesWithCtx.testType, tc.testName), func(t *testing.T) {
|
||||
// Test
|
||||
res, err := instES.Client.InstanceV2.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 == "" {
|
||||
|
||||
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 := instES.Client.InstanceV2.GetInstance(tc.inputContext, &instance.GetInstanceRequest{InstanceId: tc.inputRequest.GetInstanceId()})
|
||||
require.Nil(tt, err)
|
||||
assert.Equal(tt, tc.expectedNewName, retrievedInstance.GetInstance().GetName())
|
||||
}, retryDuration, tick, "timeout waiting for expected execution result")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,19 @@ package instance_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"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"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||
@@ -27,70 +30,110 @@ func TestGetInstance(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
instanceOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
organizationOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
instES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
instanceOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
organizationOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
|
||||
// Relational
|
||||
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())
|
||||
})
|
||||
instanceOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
organizationOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
instES.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
|
||||
instRelational.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputInstanceID string
|
||||
expectedInstanceID string
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when unauthN context should return unauthN error",
|
||||
inputContext: context.Background(),
|
||||
inputInstanceID: inst.ID(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputContext: organizationOwnerCtx,
|
||||
inputInstanceID: inst.ID(),
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when request succeeds should return matching instance (systemUser)",
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
inputInstanceID: inst.ID(),
|
||||
expectedInstanceID: inst.ID(),
|
||||
},
|
||||
{
|
||||
testName: "when request succeeds should return matching instance (own context)",
|
||||
inputContext: instanceOwnerCtx,
|
||||
expectedInstanceID: inst.ID(),
|
||||
},
|
||||
{
|
||||
testName: "when instance not found should return not found error",
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
inputInstanceID: "invalid",
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "Errors.IAM.NotFound (QUERY-n0wng)",
|
||||
},
|
||||
type instAndCtx struct {
|
||||
testType string
|
||||
inst *integration.Instance
|
||||
instanceOwnerCtx context.Context
|
||||
orgOwnerCtx context.Context
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2.GetInstance(tc.inputContext, &instance.GetInstanceRequest{InstanceId: tc.inputInstanceID})
|
||||
testedInstanceAndCtxs := []instAndCtx{
|
||||
{testType: "eventstore", inst: instES, instanceOwnerCtx: instanceOwnerCtxES, orgOwnerCtx: organizationOwnerCtxES},
|
||||
{testType: "relational", inst: instRelational, instanceOwnerCtx: instanceOwnerCtxRelational, orgOwnerCtx: organizationOwnerCtxRelational},
|
||||
}
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
for _, instWithCtx := range testedInstanceAndCtxs {
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputContext context.Context
|
||||
inputInstanceID string
|
||||
expectedInstanceID string
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when unauthN context should return unauthN error",
|
||||
inputContext: context.Background(),
|
||||
inputInstanceID: instWithCtx.inst.ID(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputContext: instWithCtx.orgOwnerCtx,
|
||||
inputInstanceID: instWithCtx.inst.ID(),
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when request succeeds should return matching instance (systemUser)",
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
inputInstanceID: instWithCtx.inst.ID(),
|
||||
expectedInstanceID: instWithCtx.inst.ID(),
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): Decide if we should set the instance in context.
|
||||
testName: "when request succeeds should return matching instance (own context)",
|
||||
inputContext: instWithCtx.instanceOwnerCtx,
|
||||
expectedInstanceID: instWithCtx.inst.ID(),
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): This test won't reach the relational side due to an interceptor
|
||||
// automatically changing the instance in context to an empty one.
|
||||
// The interceptor will look for the instance matching the instance ID passed here.
|
||||
// Since the instance doesn't exist, it will put an empty instance in context.
|
||||
// But an empty instance has no feature flags enabled.
|
||||
testName: "when instance not found should return not found error",
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
inputInstanceID: "invalid",
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "Errors.IAM.NotFound (QUERY-n0wng)",
|
||||
},
|
||||
}
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedInstanceID, res.GetInstance().GetId())
|
||||
faultyTestCasesForRelational := []string{
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
"when unauthZ context should return unauthZ error",
|
||||
// TODO(IAM-Marco): Decide if we should set the instance in context.
|
||||
"when request succeeds should return matching instance (own context)",
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
if instWithCtx.testType == "relational" && slices.Contains(faultyTestCasesForRelational, tc.testName) {
|
||||
continue
|
||||
}
|
||||
})
|
||||
t.Run(fmt.Sprintf("%s - %s", instWithCtx.testType, tc.testName), func(t *testing.T) {
|
||||
// Test
|
||||
res, err := instWithCtx.inst.Client.InstanceV2.GetInstance(tc.inputContext, &instance.GetInstanceRequest{InstanceId: tc.inputInstanceID})
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedInstanceID, res.GetInstance().GetId())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,27 +146,27 @@ 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.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst2.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
|
||||
inst2.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst2.ID()})
|
||||
inst3.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst3.ID()})
|
||||
inst4.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst4.ID()})
|
||||
inst5.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst5.ID()})
|
||||
})
|
||||
|
||||
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
|
||||
relTableState := integration.RelationalTablesEnableMatrix()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.ListInstancesRequest
|
||||
@@ -142,6 +185,7 @@ func TestListInstances(t *testing.T) {
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.ListInstancesRequest{
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
@@ -153,43 +197,61 @@ 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,
|
||||
Filters: []*instance.Filter{
|
||||
{
|
||||
Filter: &instance.Filter_InIdsFilter{
|
||||
InIdsFilter: &filter.InIDsFilter{
|
||||
Ids: []string{inst.ID(), inst2.ID()},
|
||||
Ids: []string{inst.ID(), inst2.ID(), inst3.ID(), inst4.ID(), inst5.ID()},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Filter: &instance.Filter_CustomDomainsFilter{
|
||||
CustomDomainsFilter: &instance.CustomDomainsFilter{
|
||||
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.InstanceV2.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 {
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
if tc.testName == "when unauthZ context should return unauthZ error" && stateCase.State == "when relational tables are enabled" {
|
||||
continue
|
||||
}
|
||||
t.Run(fmt.Sprintf("%s - %s", stateCase.State, tc.testName), func(t *testing.T) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2.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) {
|
||||
@@ -200,115 +262,150 @@ func TestListCustomDomains(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
|
||||
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
instanceOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
d1, d2 := "custom."+integration.DomainName(), "custom."+integration.DomainName()
|
||||
|
||||
_, err := inst.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), CustomDomain: d1})
|
||||
// Eventstore objects
|
||||
instES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
instanceOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
d1ES, d2ES := "custom."+integration.DomainName(), "custom."+integration.DomainName()
|
||||
_, err := instES.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instES.ID(), CustomDomain: d1ES})
|
||||
require.Nil(t, err)
|
||||
_, err = inst.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), CustomDomain: d2})
|
||||
_, err = instES.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instES.ID(), CustomDomain: d2ES})
|
||||
require.Nil(t, err)
|
||||
|
||||
// Relational objects
|
||||
instRelational := integration.NewInstance(ctxWithSysAuthZ)
|
||||
integration.EnsureInstanceFeature(t, ctx, instRelational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
orgOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
instanceOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
d1Relational, d2Relational := "custom."+integration.DomainName(), "custom."+integration.DomainName()
|
||||
_, err = instRelational.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instRelational.ID(), CustomDomain: d1Relational})
|
||||
require.Nil(t, err)
|
||||
_, err = instRelational.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instRelational.ID(), CustomDomain: d2Relational})
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: inst.ID(), CustomDomain: d1})
|
||||
inst.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: inst.ID(), CustomDomain: d2})
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
instES.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instES.ID(), CustomDomain: d1ES})
|
||||
instES.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instES.ID(), CustomDomain: d2ES})
|
||||
instES.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
|
||||
|
||||
instRelational.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instRelational.ID(), CustomDomain: d1Relational})
|
||||
instRelational.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instRelational.ID(), CustomDomain: d2Relational})
|
||||
instRelational.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.ListCustomDomainsRequest
|
||||
inputContext context.Context
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
expectedDomains []string
|
||||
testData := []struct {
|
||||
testType string
|
||||
inst *integration.Instance
|
||||
sysOwnerCtx context.Context
|
||||
instOwnerCtx context.Context
|
||||
orgOwnerCtx context.Context
|
||||
expectedDomains []string
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance.CustomDomainFilter{
|
||||
{
|
||||
Filter: &instance.CustomDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: orgOwnerCtx,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response (systemUser)",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance.CustomDomainFilter{
|
||||
{
|
||||
Filter: &instance.CustomDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedDomains: []string{d1, d2},
|
||||
},
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response (own context)",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance.CustomDomainFilter{
|
||||
{
|
||||
Filter: &instance.CustomDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: instanceOwnerCtx,
|
||||
expectedDomains: []string{d1},
|
||||
},
|
||||
{testType: "eventstore", inst: instES, sysOwnerCtx: ctxWithSysAuthZ, instOwnerCtx: instanceOwnerCtxES, orgOwnerCtx: orgOwnerCtxES, expectedDomains: []string{d1ES, d2ES}},
|
||||
{testType: "relational", inst: instRelational, sysOwnerCtx: ctxWithSysAuthZ, instOwnerCtx: instanceOwnerCtxRelational, orgOwnerCtx: orgOwnerCtxRelational, expectedDomains: []string{d1Relational, d2Relational}},
|
||||
}
|
||||
for _, td := range testData {
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputContext, time.Minute)
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2.ListCustomDomains(tc.inputContext, tc.inputRequest)
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.ListCustomDomainsRequest
|
||||
inputContext context.Context
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
expectedDomains []string
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
InstanceId: td.inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
InstanceId: td.inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance.CustomDomainFilter{
|
||||
{
|
||||
Filter: &instance.CustomDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: td.orgOwnerCtx,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response (systemUser)",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
InstanceId: td.inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance.CustomDomainFilter{
|
||||
{
|
||||
Filter: &instance.CustomDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: td.sysOwnerCtx,
|
||||
expectedDomains: td.expectedDomains,
|
||||
},
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response (own context)",
|
||||
inputRequest: &instance.ListCustomDomainsRequest{
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance.CustomDomainFilter{
|
||||
{
|
||||
Filter: &instance.CustomDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: td.instOwnerCtx,
|
||||
expectedDomains: []string{td.expectedDomains[0]},
|
||||
},
|
||||
}
|
||||
|
||||
// Verify
|
||||
assert.Equal(collect, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(collect, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
for _, tc := range tt {
|
||||
if tc.testName == "when unauthZ context should return unauthZ error" && td.testType == "relational" {
|
||||
continue
|
||||
}
|
||||
t.Run(fmt.Sprintf("%s - %s", td.testType, tc.testName), func(t *testing.T) {
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputContext, time.Minute)
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
// Test
|
||||
res, err := td.inst.Client.InstanceV2.ListCustomDomains(tc.inputContext, tc.inputRequest)
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
domains := []string{}
|
||||
for _, d := range res.GetDomains() {
|
||||
domains = append(domains, d.GetDomain())
|
||||
// Verify
|
||||
assert.Equal(collect, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(collect, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
domains := []string{}
|
||||
for _, d := range res.GetDomains() {
|
||||
domains = append(domains, d.GetDomain())
|
||||
}
|
||||
|
||||
assert.Subset(collect, domains, tc.expectedDomains)
|
||||
}
|
||||
|
||||
assert.Subset(collect, domains, tc.expectedDomains)
|
||||
}
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,108 +417,144 @@ func TestListTrustedDomains(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
|
||||
inst := integration.NewInstance(ctxWithSysAuthZ)
|
||||
|
||||
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
instanceOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
d1, d2 := "trusted."+integration.DomainName(), "trusted."+integration.DomainName()
|
||||
|
||||
_, err := inst.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), TrustedDomain: d1})
|
||||
// Eventstore objects
|
||||
instES := integration.NewInstance(ctxWithSysAuthZ)
|
||||
orgOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
instanceOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
d1ES, d2ES := "trusted."+integration.DomainName(), "trusted."+integration.DomainName()
|
||||
_, err := instES.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instES.ID(), TrustedDomain: d1ES})
|
||||
require.Nil(t, err)
|
||||
_, err = inst.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), TrustedDomain: d2})
|
||||
_, err = instES.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instES.ID(), TrustedDomain: d2ES})
|
||||
require.Nil(t, err)
|
||||
|
||||
// Relational objects
|
||||
instRelational := integration.NewInstance(ctxWithSysAuthZ)
|
||||
integration.EnsureInstanceFeature(t, ctx, instRelational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
|
||||
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
|
||||
})
|
||||
orgOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
|
||||
instanceOwnerCtxRelational := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
d1Relational, d2Relational := "trusted."+integration.DomainName(), "trusted."+integration.DomainName()
|
||||
_, err = instRelational.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instRelational.ID(), TrustedDomain: d1Relational})
|
||||
require.Nil(t, err)
|
||||
_, err = instRelational.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instRelational.ID(), TrustedDomain: d2Relational})
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
inst.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: inst.ID(), TrustedDomain: d1})
|
||||
inst.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: inst.ID(), TrustedDomain: d2})
|
||||
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
|
||||
instES.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instES.ID(), TrustedDomain: d1ES})
|
||||
instES.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instES.ID(), TrustedDomain: d2ES})
|
||||
instES.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
|
||||
|
||||
instRelational.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instRelational.ID(), TrustedDomain: d1Relational})
|
||||
instRelational.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instRelational.ID(), TrustedDomain: d2Relational})
|
||||
instRelational.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.ListTrustedDomainsRequest
|
||||
inputContext context.Context
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
expectedDomains []string
|
||||
testData := []struct {
|
||||
testType string
|
||||
inst *integration.Instance
|
||||
sysOwnerCtx context.Context
|
||||
instOwnerCtx context.Context
|
||||
orgOwnerCtx context.Context
|
||||
expectedDomains []string
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
},
|
||||
inputContext: orgOwnerCtx,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response (systemUser)",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
InstanceId: inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance.TrustedDomainFilter{
|
||||
{
|
||||
Filter: &instance.TrustedDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{Domain: "trusted", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: ctxWithSysAuthZ,
|
||||
expectedDomains: []string{d1, d2},
|
||||
},
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response (own context)",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance.TrustedDomainFilter{
|
||||
{
|
||||
Filter: &instance.TrustedDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{Domain: "trusted", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: instanceOwnerCtx,
|
||||
expectedDomains: []string{d1},
|
||||
},
|
||||
{testType: "eventstore", inst: instES, sysOwnerCtx: ctxWithSysAuthZ, instOwnerCtx: instanceOwnerCtxES, orgOwnerCtx: orgOwnerCtxES, expectedDomains: []string{d1ES, d2ES}},
|
||||
{testType: "relational", inst: instRelational, sysOwnerCtx: ctxWithSysAuthZ, instOwnerCtx: instanceOwnerCtxRelational, orgOwnerCtx: orgOwnerCtxRelational, expectedDomains: []string{d1Relational, d2Relational}},
|
||||
}
|
||||
for _, td := range testData {
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *instance.ListTrustedDomainsRequest
|
||||
inputContext context.Context
|
||||
expectedErrorMsg string
|
||||
expectedErrorCode codes.Code
|
||||
expectedDomains []string
|
||||
}{
|
||||
{
|
||||
testName: "when invalid context should return unauthN error",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
InstanceId: td.inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
},
|
||||
inputContext: context.Background(),
|
||||
expectedErrorCode: codes.Unauthenticated,
|
||||
expectedErrorMsg: "auth header missing",
|
||||
},
|
||||
{
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
testName: "when unauthZ context should return unauthZ error",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
InstanceId: td.inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
},
|
||||
inputContext: td.orgOwnerCtx,
|
||||
expectedErrorCode: codes.NotFound,
|
||||
expectedErrorMsg: "membership not found (AUTHZ-cdgFk)",
|
||||
},
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response (systemUser)",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
InstanceId: td.inst.ID(),
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance.TrustedDomainFilter{
|
||||
{
|
||||
Filter: &instance.TrustedDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{Domain: "trusted", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: td.sysOwnerCtx,
|
||||
expectedDomains: td.expectedDomains,
|
||||
},
|
||||
{
|
||||
testName: "when valid request with filter should return paginated response (own context)",
|
||||
inputRequest: &instance.ListTrustedDomainsRequest{
|
||||
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
|
||||
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
|
||||
Filters: []*instance.TrustedDomainFilter{
|
||||
{
|
||||
Filter: &instance.TrustedDomainFilter_DomainFilter{
|
||||
DomainFilter: &instance.DomainFilter{Domain: "trusted", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
inputContext: td.instOwnerCtx,
|
||||
expectedDomains: []string{td.expectedDomains[0]},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputContext, time.Minute)
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
// Test
|
||||
res, err := inst.Client.InstanceV2.ListTrustedDomains(tc.inputContext, tc.inputRequest)
|
||||
for _, tc := range tt {
|
||||
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
|
||||
if tc.testName == "when unauthZ context should return unauthZ error" && td.testType == "relational" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Verify
|
||||
assert.Equal(collect, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(collect, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
t.Run(fmt.Sprintf("%s - %s", td.testType, tc.testName), func(t *testing.T) {
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputContext, time.Minute)
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
// Test
|
||||
res, err := td.inst.Client.InstanceV2.ListTrustedDomains(tc.inputContext, tc.inputRequest)
|
||||
|
||||
if tc.expectedErrorMsg == "" {
|
||||
require.NotNil(t, res)
|
||||
// Verify
|
||||
assert.Equal(collect, tc.expectedErrorCode, status.Code(err))
|
||||
assert.Equal(collect, tc.expectedErrorMsg, status.Convert(err).Message())
|
||||
|
||||
domains := []string{}
|
||||
for _, d := range res.GetTrustedDomain() {
|
||||
domains = append(domains, d.GetDomain())
|
||||
if tc.expectedErrorMsg == "" {
|
||||
require.NotNil(t, res)
|
||||
|
||||
domains := []string{}
|
||||
for _, d := range res.GetTrustedDomain() {
|
||||
domains = append(domains, d.GetDomain())
|
||||
}
|
||||
|
||||
assert.Subset(collect, domains, tc.expectedDomains)
|
||||
}
|
||||
|
||||
assert.Subset(collect, domains, tc.expectedDomains)
|
||||
}
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user