chore(test): streamline integration tests for instance management (#12243)

- Removed redundant relational instance handling in integration tests
for adding and removing custom domains.
- Simplified test cases by consolidating instance creation and context
management.
- Enhanced clarity and maintainability of test logic by reducing
complexity in test structures.

# Which Problems Are Solved

`backed/v3` tests are flaky, but the code is not productive. Development
is paused for the moment. Disabling flaky tests helps us to maintain the
product easier.

# How the Problems Are Solved

- Removed redundant relational instance handling in integration tests
for adding and removing custom domains.
- Simplified test cases by consolidating instance creation and context
management.
- Enhanced clarity and maintainability of test logic by reducing
complexity in test structures.newly introduced terms).

# Additional Context

- related to https://github.com/zitadel/zitadel/pull/12241

---------

Co-authored-by: Livio Spring <[email protected]>
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
This commit is contained in:
Tim Möhlmann
2026-06-05 11:36:45 +00:00
committed by GitHub
co-authored by Livio Spring Copilot Autofix powered by AI
parent 7019a03ec4
commit f85a2373c5
11 changed files with 1769 additions and 2453 deletions
@@ -4,19 +4,16 @@ 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"
)
@@ -32,98 +29,80 @@ func TestAddCustomDomain(t *testing.T) {
inst := integration.NewInstance(ctxWithSysAuthZ)
iamOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
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())
})
t.Cleanup(func() {
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
relationalInst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: relationalInst.ID()})
})
// ugly fix to wait until instances are projected in all read models.
time.Sleep(10 * time.Second)
type instanceAndCtx struct {
testType string
instance *integration.Instance
ctx context.Context
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(),
},
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(),
},
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: " ",
},
inputContext: ctxWithSysAuthZ,
expectedErrorCode: codes.InvalidArgument,
expectedErrorMsg: "Errors.Invalid.Argument",
},
{
testName: "when valid request should return successful response",
inputRequest: &instance.AddCustomDomainRequest{
InstanceId: inst.ID(),
CustomDomain: " " + integration.DomainName(),
},
inputContext: ctxWithSysAuthZ,
},
}
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",
},
{
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)",
},
{
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",
},
{
testName: "when valid request should return successful response",
inputRequest: &instance.AddCustomDomainRequest{
InstanceId: instAndCtx.instance.ID(),
CustomDomain: " " + integration.DomainName(),
},
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 == "" {
assert.NotNil(t, res)
assert.NotEmpty(t, res.GetCreationDate())
inst.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{CustomDomain: strings.TrimSpace(tc.inputRequest.CustomDomain)})
}
})
}
// Test
res, err := inst.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)
if tc.expectedErrorMsg == "" {
assert.NotNil(t, res)
assert.NotEmpty(t, res.GetCreationDate())
}
})
}
}
@@ -135,104 +114,80 @@ func TestRemoveCustomDomain(t *testing.T) {
defer cancel()
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
instES := integration.NewInstance(ctxWithSysAuthZ)
iamOwnerCtxES := instES.WithAuthorizationToken(t.Context(), integration.UserTypeIAMOwner)
inst := integration.NewInstance(ctxWithSysAuthZ)
iamOwnerCtx := inst.WithAuthorizationToken(t.Context(), integration.UserTypeIAMOwner)
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())
})
customDomain := integration.DomainName()
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})
_, err := inst.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), CustomDomain: customDomain})
require.Nil(t, err)
t.Cleanup(func() {
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()})
inst.Client.InstanceV2.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: inst.ID(), CustomDomain: customDomain})
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
})
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},
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",
},
{
testName: "when valid request should return successful response",
inputRequest: &instance.RemoveCustomDomainRequest{
InstanceId: inst.ID(),
CustomDomain: " " + customDomain,
},
inputContext: ctxWithSysAuthZ,
},
}
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,
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// Test
res, err := inst.Client.InstanceV2.RemoveCustomDomain(tc.inputContext, tc.inputRequest)
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)
// Verify
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
assert.Contains(t, status.Convert(err).Message(), tc.expectedErrorMsg)
// 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())
}
})
}
if tc.expectedErrorMsg == "" {
assert.NotNil(t, res)
assert.NotEmpty(t, res.GetDeletionDate())
}
})
}
}
@@ -245,110 +200,81 @@ func TestAddTrustedDomain(t *testing.T) {
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
// 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()
inst := integration.NewInstance(ctxWithSysAuthZ)
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
d1 := integration.DomainName()
t.Cleanup(func() {
instES.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
instRelational.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
})
type instAndCtx struct {
testType string
inst *integration.Instance
orgOwnerCtx context.Context
domain string
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: d1,
},
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: d1,
},
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",
},
{
testName: "when valid request should return successful response",
inputRequest: &instance.AddTrustedDomainRequest{
InstanceId: inst.ID(),
TrustedDomain: " " + d1,
},
inputContext: ctxWithSysAuthZ,
},
}
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)
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
t.Cleanup(func() {
if tc.expectedErrorMsg == "" {
assert.NotNil(t, res)
assert.NotEmpty(t, res.GetCreationDate())
inst.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{TrustedDomain: strings.TrimSpace(tc.inputRequest.TrustedDomain)})
}
})
}
// Test
res, err := 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 == "" {
assert.NotNil(t, res)
assert.NotEmpty(t, res.GetCreationDate())
}
})
}
}
@@ -361,100 +287,67 @@ func TestRemoveTrustedDomain(t *testing.T) {
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
// 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)
// 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})
inst := integration.NewInstance(ctxWithSysAuthZ)
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
trustedDomain := integration.DomainName()
_, err := inst.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), TrustedDomain: trustedDomain})
require.Nil(t, err)
t.Cleanup(func() {
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()})
inst.Client.InstanceV2.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: inst.ID(), TrustedDomain: trustedDomain})
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
})
type instAndCtx struct {
testType string
inst *integration.Instance
orgOwnerCtx context.Context
domain string
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,
},
}
testedInstanceAndCtxs := []instAndCtx{
{testType: "eventstore", inst: instES, orgOwnerCtx: orgOwnerCtxES, domain: trustedDomainES},
{testType: "relational", inst: instRelational, orgOwnerCtx: orgOwnerCtxRelational, domain: trustedDomainRelational},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// Test
res, err := inst.Client.InstanceV2.RemoveTrustedDomain(tc.inputContext, tc.inputRequest)
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,
},
}
// Verify
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
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
if tc.expectedErrorMsg == "" {
require.NotNil(t, res)
require.NotEmpty(t, res.GetDeletionDate())
}
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,18 +4,15 @@ 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"
)
@@ -28,135 +25,101 @@ func TestDeleteInstance(t *testing.T) {
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
// Event store instances
instES := integration.NewInstance(ctxWithSysAuthZ)
instanceESOwnerCtx := instES.WithAuthorizationToken(t.Context(), integration.UserTypeIAMOwner)
instES2 := integration.NewInstance(ctxWithSysAuthZ)
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())
})
type instAndCtx struct {
testType string
i1 *integration.Instance
i2 *integration.Instance
ctx context.Context
}
testedInstancesAndCtxs := []instAndCtx{
{testType: "eventstore", i1: instES, i2: instES2, ctx: instanceESOwnerCtx},
{testType: "relational", i1: instRelational, i2: instRelational2, ctx: instanceRelationalOwnerCtx},
}
inst := integration.NewInstance(ctxWithSysAuthZ)
instanceOwnerCtx := inst.WithAuthorizationToken(t.Context(), integration.UserTypeIAMOwner)
inst2 := integration.NewInstance(ctxWithSysAuthZ)
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()})
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
inst2.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst2.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",
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: inst.ID(),
},
{
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",
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: inst.ID() + "invalid",
},
{
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)",
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: inst.ID() + "invalid",
},
{
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)",
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(),
},
{
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)",
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(),
},
{
// 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,
inputContext: instanceOwnerCtx,
expectedErrorCode: codes.PermissionDenied,
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
},
{
testName: "when invalid id should return no error",
inputRequest: &instance.DeleteInstanceRequest{
InstanceId: inst.ID() + "invalid",
},
{
testName: "when delete succeeds should return deletion date",
inputRequest: &instance.DeleteInstanceRequest{
InstanceId: instanceWithCtx.i1.ID(),
},
inputContext: ctxWithSysAuthZ,
expectedInstanceID: instanceWithCtx.i1.ID(),
inputContext: ctxWithSysAuthZ,
expectsNullTimestamp: true,
},
{
testName: "when delete succeeds should return deletion date",
inputRequest: &instance.DeleteInstanceRequest{
InstanceId: inst.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)
}
})
}
inputContext: ctxWithSysAuthZ,
expectedInstanceID: inst.ID(),
},
}
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.Equal(t, tc.expectsNullTimestamp, res.GetDeletionDate() == nil)
}
})
}
}
func TestUpdateInstance(t *testing.T) {
@@ -168,163 +131,124 @@ func TestUpdateInstance(t *testing.T) {
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
instES := integration.NewInstance(ctxWithSysAuthZ)
instanceOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
orgOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
inst2ES := integration.NewInstance(ctxWithSysAuthZ)
// 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())
})
inst := integration.NewInstance(ctxWithSysAuthZ)
instanceOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
inst2 := integration.NewInstance(ctxWithSysAuthZ)
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()})
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
inst2.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst2.ID()})
})
type instAndCtx struct {
testType string
inst *integration.Instance
inst2 *integration.Instance
instanceOwnerCtx context.Context
orgOwnerCtx context.Context
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: "a valid name",
},
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",
},
}
testedInstancesAndCtxs := []instAndCtx{
{testType: "eventstore", inst: instES, inst2: inst2ES, instanceOwnerCtx: instanceOwnerCtxES, orgOwnerCtx: orgOwnerCtxES},
{testType: "relational", inst: instRelational, inst2: inst2Relational, instanceOwnerCtx: instanceOwnerCtxRelational, orgOwnerCtx: orgOwnerCtxRelational},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// Test
res, err := inst.Client.InstanceV2.UpdateInstance(tc.inputContext, tc.inputRequest)
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",
},
}
// Verify
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
if tc.expectedErrorMsg == "" {
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
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")
}
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,12 +4,9 @@ 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"
@@ -30,110 +27,70 @@ func TestGetInstance(t *testing.T) {
defer cancel()
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
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)
inst := integration.NewInstance(ctxWithSysAuthZ)
instanceOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
organizationOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
t.Cleanup(func() {
instES.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
instRelational.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
inst.Client.InstanceV2.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
})
type instAndCtx struct {
testType string
inst *integration.Instance
instanceOwnerCtx context.Context
orgOwnerCtx context.Context
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.Instance.NotFound (QUERY-n0wng)",
},
}
testedInstanceAndCtxs := []instAndCtx{
{testType: "eventstore", inst: instES, instanceOwnerCtx: instanceOwnerCtxES, orgOwnerCtx: organizationOwnerCtxES},
{testType: "relational", inst: instRelational, instanceOwnerCtx: instanceOwnerCtxRelational, orgOwnerCtx: organizationOwnerCtxRelational},
}
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})
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.Instance.NotFound (QUERY-n0wng)",
},
}
// Verify
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
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
if tc.expectedErrorMsg == "" {
require.NoError(t, err)
assert.Equal(t, tc.expectedInstanceID, res.GetInstance().GetId())
}
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())
}
})
}
})
}
}
@@ -165,8 +122,6 @@ func TestListInstances(t *testing.T) {
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
relTableState := integration.RelationalTablesEnableMatrix(t, ctx, ctxWithSysAuthZ)
tt := []struct {
testName string
inputRequest *instance.ListInstancesRequest
@@ -185,7 +140,6 @@ 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},
@@ -221,32 +175,25 @@ func TestListInstances(t *testing.T) {
},
}
for _, stateCase := range relTableState {
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// Test
res, err := inst.Client.InstanceV2.ListInstances(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" && stateCase.Name == "when relational tables are enabled" {
continue
}
t.Run(fmt.Sprintf("%s - %s", stateCase.Name, 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())
// 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)
if tc.expectedErrorMsg == "" {
require.NotNil(t, res)
require.Len(t, res.GetInstances(), len(tc.expectedInstances))
require.Len(t, res.GetInstances(), len(tc.expectedInstances))
for i, ins := range res.GetInstances() {
assert.Equal(t, tc.expectedInstances[i], ins.GetId())
}
for i, ins := range res.GetInstances() {
assert.Equal(t, tc.expectedInstances[i], ins.GetId())
}
})
}
}
})
}
}
@@ -259,149 +206,115 @@ func TestListCustomDomains(t *testing.T) {
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
// 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})
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})
require.Nil(t, err)
_, 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})
_, err = inst.Client.InstanceV2.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), CustomDomain: d2})
require.Nil(t, err)
t.Cleanup(func() {
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()})
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()})
})
testData := []struct {
testType string
inst *integration.Instance
sysOwnerCtx context.Context
instOwnerCtx context.Context
orgOwnerCtx context.Context
expectedDomains []string
expectedDomains := []string{d1, d2}
tt := []struct {
testName string
inputRequest *instance.ListCustomDomainsRequest
inputContext context.Context
expectedErrorMsg string
expectedErrorCode codes.Code
expectedDomains []string
}{
{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}},
{
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: 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: instanceOwnerCtx,
expectedDomains: []string{expectedDomains[0]},
},
}
for _, td := range testData {
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]},
},
}
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)
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)
// Verify
assert.Equal(collect, tc.expectedErrorCode, status.Code(err))
assert.Equal(collect, tc.expectedErrorMsg, status.Convert(err).Message())
// 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)
if tc.expectedErrorMsg == "" {
domains := []string{}
for _, d := range res.GetDomains() {
domains = append(domains, d.GetDomain())
}
}, retryDuration, tick)
})
}
assert.Subset(collect, domains, tc.expectedDomains)
}
}, retryDuration, tick)
})
}
}
@@ -414,143 +327,108 @@ func TestListTrustedDomains(t *testing.T) {
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
// 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})
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})
require.Nil(t, err)
_, 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})
_, err = inst.Client.InstanceV2.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), TrustedDomain: d2})
require.Nil(t, err)
t.Cleanup(func() {
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()})
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()})
})
testData := []struct {
testType string
inst *integration.Instance
sysOwnerCtx context.Context
instOwnerCtx context.Context
orgOwnerCtx context.Context
expectedDomains []string
expectedDomains := []string{d1, d2}
tt := []struct {
testName string
inputRequest *instance.ListTrustedDomainsRequest
inputContext context.Context
expectedErrorMsg string
expectedErrorCode codes.Code
expectedDomains []string
}{
{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}},
{
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: 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: instanceOwnerCtx,
expectedDomains: []string{expectedDomains[0]},
},
}
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 {
// 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
}
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)
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)
// Verify
assert.Equal(collect, tc.expectedErrorCode, status.Code(err))
assert.Equal(collect, tc.expectedErrorMsg, status.Convert(err).Message())
// Verify
assert.Equal(collect, tc.expectedErrorCode, status.Code(err))
assert.Equal(collect, tc.expectedErrorMsg, status.Convert(err).Message())
if tc.expectedErrorMsg == "" {
require.NotNil(t, res)
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)
domains := []string{}
for _, d := range res.GetTrustedDomain() {
domains = append(domains, d.GetDomain())
}
}, retryDuration, tick)
})
}
assert.Subset(collect, domains, tc.expectedDomains)
}
}, retryDuration, tick)
})
}
}
@@ -4,19 +4,16 @@ 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"
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
)
@@ -32,99 +29,77 @@ func TestAddCustomDomain(t *testing.T) {
inst := integration.NewInstance(ctxWithSysAuthZ)
iamOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
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())
},
)
t.Cleanup(func() {
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
relationalInst.Client.InstanceV2Beta.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},
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(),
Domain: integration.DomainName(),
},
inputContext: context.Background(),
expectedErrorCode: codes.Unauthenticated,
expectedErrorMsg: "auth header missing",
},
{
testName: "when unauthZ context should return unauthZ error",
inputRequest: &instance.AddCustomDomainRequest{
InstanceId: inst.ID(),
Domain: integration.DomainName(),
},
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(),
Domain: " ",
},
inputContext: ctxWithSysAuthZ,
expectedErrorCode: codes.InvalidArgument,
expectedErrorMsg: "Errors.Invalid.Argument",
},
{
testName: "when valid request should return successful response",
inputRequest: &instance.AddCustomDomainRequest{
InstanceId: inst.ID(),
Domain: " " + integration.DomainName(),
},
inputContext: ctxWithSysAuthZ,
},
}
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(),
Domain: integration.DomainName(),
},
inputContext: context.Background(),
expectedErrorCode: codes.Unauthenticated,
expectedErrorMsg: "auth header missing",
},
{
testName: "when unauthZ context should return unauthZ error",
inputRequest: &instance.AddCustomDomainRequest{
InstanceId: instAndCtx.instance.ID(),
Domain: integration.DomainName(),
},
inputContext: instAndCtx.ctx,
expectedErrorCode: codes.PermissionDenied,
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
},
{
testName: "when invalid domain should return invalid argument error",
inputRequest: &instance.AddCustomDomainRequest{
InstanceId: instAndCtx.instance.ID(),
Domain: " ",
},
inputContext: ctxWithSysAuthZ,
expectedErrorCode: codes.InvalidArgument,
expectedErrorMsg: "Errors.Invalid.Argument",
},
{
testName: "when valid request should return successful response",
inputRequest: &instance.AddCustomDomainRequest{
InstanceId: instAndCtx.instance.ID(),
Domain: " " + integration.DomainName(),
},
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.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{Domain: strings.TrimSpace(tc.inputRequest.Domain)})
}
})
// Test
res, err := instAndCtx.instance.Client.InstanceV2Beta.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 == "" {
assert.NotNil(t, res)
assert.NotEmpty(t, res.GetCreationDate())
inst.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{Domain: strings.TrimSpace(tc.inputRequest.Domain)})
}
})
}
// Test
res, err := inst.Client.InstanceV2Beta.AddCustomDomain(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 == "" {
assert.NotNil(t, res)
assert.NotEmpty(t, res.GetCreationDate())
}
})
}
}
@@ -136,104 +111,80 @@ func TestRemoveCustomDomain(t *testing.T) {
defer cancel()
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
instES := integration.NewInstance(ctxWithSysAuthZ)
iamOwnerCtxES := instES.WithAuthorizationToken(t.Context(), integration.UserTypeIAMOwner)
inst := integration.NewInstance(ctxWithSysAuthZ)
iamOwnerCtx := inst.WithAuthorizationToken(t.Context(), integration.UserTypeIAMOwner)
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())
})
customDomain := integration.DomainName()
customDomainES := integration.DomainName()
customDomainRelational := integration.DomainName()
_, err := instES.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instES.ID(), Domain: customDomainES})
require.Nil(t, err)
_, err = instRelational.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instRelational.ID(), Domain: customDomainRelational})
_, err := inst.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), Domain: customDomain})
require.Nil(t, err)
t.Cleanup(func() {
instES.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instES.ID(), Domain: customDomainES})
instES.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
instRelational.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instRelational.ID(), Domain: customDomainRelational})
instRelational.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
inst.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: inst.ID(), Domain: customDomain})
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
})
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},
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(),
Domain: "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(),
Domain: "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(),
Domain: " ",
},
inputContext: ctxWithSysAuthZ,
expectedErrorCode: codes.InvalidArgument,
expectedErrorMsg: "Errors.Invalid.Argument",
},
{
testName: "when valid request should return successful response",
inputRequest: &instance.RemoveCustomDomainRequest{
InstanceId: inst.ID(),
Domain: " " + customDomain,
},
inputContext: ctxWithSysAuthZ,
},
}
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(),
Domain: "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(),
Domain: "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(),
Domain: " ",
},
inputContext: ctxWithSysAuthZ,
expectedErrorCode: codes.InvalidArgument,
expectedErrorMsg: "Errors.Invalid.Argument",
},
{
testName: "when valid request should return successful response",
inputRequest: &instance.RemoveCustomDomainRequest{
InstanceId: instAndCtx.instance.ID(),
Domain: " " + instAndCtx.customDomain,
},
inputContext: ctxWithSysAuthZ,
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// Test
res, err := inst.Client.InstanceV2Beta.RemoveCustomDomain(tc.inputContext, tc.inputRequest)
for _, tc := range tt {
t.Run(fmt.Sprintf("%s - %s", instAndCtx.testType, tc.testName), func(t *testing.T) {
// Test
res, err := instAndCtx.instance.Client.InstanceV2Beta.RemoveCustomDomain(tc.inputContext, tc.inputRequest)
// Verify
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
assert.Contains(t, status.Convert(err).Message(), tc.expectedErrorMsg)
// 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())
}
})
}
if tc.expectedErrorMsg == "" {
assert.NotNil(t, res)
assert.NotEmpty(t, res.GetDeletionDate())
}
})
}
}
@@ -246,99 +197,75 @@ func TestAddTrustedDomain(t *testing.T) {
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
// Eventstore objects
instES := integration.NewInstance(ctxWithSysAuthZ)
orgOwnerCtxES := instES.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
d1ES := "trusted" + 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 := "trusted" + integration.DomainName()
inst := integration.NewInstance(ctxWithSysAuthZ)
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
d1 := "trusted" + integration.DomainName()
t.Cleanup(func() {
instES.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
instRelational.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
})
type instAndCtx struct {
testType string
inst *integration.Instance
orgOwnerCtx context.Context
domain string
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(),
Domain: d1,
},
inputContext: context.Background(),
expectedErrorCode: codes.Unauthenticated,
expectedErrorMsg: "auth header missing",
},
{
testName: "when unauthZ context should return unauthZ error",
inputRequest: &instance.AddTrustedDomainRequest{
InstanceId: inst.ID(),
Domain: d1,
},
inputContext: orgOwnerCtx,
expectedErrorCode: codes.PermissionDenied,
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
},
{
testName: "when invalid domain should return invalid argument error",
inputRequest: &instance.AddTrustedDomainRequest{
InstanceId: inst.ID(),
Domain: " ",
},
inputContext: ctxWithSysAuthZ,
expectedErrorCode: codes.InvalidArgument,
expectedErrorMsg: "Errors.Invalid.Argument",
},
{
testName: "when valid request should return successful response",
inputRequest: &instance.AddTrustedDomainRequest{
InstanceId: inst.ID(),
Domain: " " + d1,
},
inputContext: ctxWithSysAuthZ,
},
}
testedInstanceAndCtxs := []instAndCtx{
{testType: "eventstore", inst: instES, orgOwnerCtx: orgOwnerCtxES, domain: d1ES},
{testType: "relational", inst: instRelational, orgOwnerCtx: orgOwnerCtxRelational, domain: d1Relational},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// Test
res, err := inst.Client.InstanceV2Beta.AddTrustedDomain(tc.inputContext, tc.inputRequest)
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(),
Domain: stateCase.domain,
},
inputContext: context.Background(),
expectedErrorCode: codes.Unauthenticated,
expectedErrorMsg: "auth header missing",
},
{
testName: "when unauthZ context should return unauthZ error",
inputRequest: &instance.AddTrustedDomainRequest{
InstanceId: stateCase.inst.ID(),
Domain: stateCase.domain,
},
inputContext: stateCase.orgOwnerCtx,
expectedErrorCode: codes.PermissionDenied,
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
},
{
testName: "when invalid domain should return invalid argument error",
inputRequest: &instance.AddTrustedDomainRequest{
InstanceId: stateCase.inst.ID(),
Domain: " ",
},
inputContext: ctxWithSysAuthZ,
expectedErrorCode: codes.InvalidArgument,
expectedErrorMsg: "Errors.Invalid.Argument",
},
{
testName: "when valid request should return successful response",
inputRequest: &instance.AddTrustedDomainRequest{
InstanceId: stateCase.inst.ID(),
Domain: " " + stateCase.domain,
},
inputContext: ctxWithSysAuthZ,
},
}
// Verify
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
assert.Contains(t, status.Convert(err).Message(), tc.expectedErrorMsg)
for _, tc := range tt {
t.Run(fmt.Sprintf("%s - %s", stateCase.testType, tc.testName), func(t *testing.T) {
// Test
res, err := stateCase.inst.Client.InstanceV2Beta.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 == "" {
assert.NotNil(t, res)
assert.NotEmpty(t, res.GetCreationDate())
}
})
}
if tc.expectedErrorMsg == "" {
assert.NotNil(t, res)
assert.NotEmpty(t, res.GetCreationDate())
}
})
}
}
@@ -350,95 +277,68 @@ func TestRemoveTrustedDomain(t *testing.T) {
defer cancel()
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
// Eventstore objects
instES := integration.NewInstance(ctxWithSysAuthZ)
orgOwnerCtxES := instES.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
trustedDomainES := integration.DomainName()
_, err := instES.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instES.ID(), Domain: trustedDomainES})
require.Nil(t, err)
// 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.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instRelational.ID(), Domain: trustedDomainRelational})
inst := integration.NewInstance(ctxWithSysAuthZ)
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
trustedDomain := integration.DomainName()
_, err := inst.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), Domain: trustedDomain})
require.Nil(t, err)
t.Cleanup(func() {
instES.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instES.ID(), Domain: trustedDomainES})
instES.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
instRelational.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instRelational.ID(), Domain: trustedDomainRelational})
instRelational.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
inst.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: inst.ID(), Domain: trustedDomain})
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
})
type instAndCtx struct {
testType string
inst *integration.Instance
orgOwnerCtx context.Context
domain string
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(),
Domain: "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(),
Domain: "trusted1",
},
inputContext: orgOwnerCtx,
expectedErrorCode: codes.PermissionDenied,
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
},
{
testName: "when valid request should return successful response",
inputRequest: &instance.RemoveTrustedDomainRequest{
InstanceId: inst.ID(),
Domain: " " + trustedDomain,
},
inputContext: ctxWithSysAuthZ,
},
}
testedInstanceAndCtxs := []instAndCtx{
{testType: "eventstore", inst: instES, orgOwnerCtx: orgOwnerCtxES, domain: trustedDomainES},
{testType: "relational", inst: instRelational, orgOwnerCtx: orgOwnerCtxRelational, domain: trustedDomainRelational},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// Test
res, err := inst.Client.InstanceV2Beta.RemoveTrustedDomain(tc.inputContext, tc.inputRequest)
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(),
Domain: "trusted1",
},
inputContext: context.Background(),
expectedErrorCode: codes.Unauthenticated,
expectedErrorMsg: "auth header missing",
},
{
testName: "when unauthZ context should return unauthZ error",
inputRequest: &instance.RemoveTrustedDomainRequest{
InstanceId: stateCase.inst.ID(),
Domain: "trusted1",
},
inputContext: stateCase.orgOwnerCtx,
expectedErrorCode: codes.PermissionDenied,
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
},
{
testName: "when valid request should return successful response",
inputRequest: &instance.RemoveTrustedDomainRequest{
InstanceId: stateCase.inst.ID(),
Domain: " " + stateCase.domain,
},
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", stateCase.testType, tc.testName), func(t *testing.T) {
// Test
res, err := stateCase.inst.Client.InstanceV2Beta.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())
}
})
}
if tc.expectedErrorMsg == "" {
require.NotNil(t, res)
require.NotEmpty(t, res.GetDeletionDate())
}
})
}
}
@@ -4,18 +4,15 @@ package instance_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/zitadel/zitadel/internal/integration"
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
)
@@ -28,76 +25,60 @@ func TestDeleteInstace(t *testing.T) {
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
instEventStore := integration.NewInstance(ctxWithSysAuthZ)
inst := integration.NewInstance(ctxWithSysAuthZ)
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())
})
type instanceWithType struct {
instType string
instance *integration.Instance
}
instances := []instanceWithType{
{instType: "eventstore", instance: instEventStore},
{instType: "relational", instance: instRelational},
}
t.Cleanup(func() {
instRelational.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
instEventStore.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instEventStore.ID()})
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
})
for _, inst := range instances {
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: " ",
},
inputContext: context.Background(),
expectedErrorCode: codes.Unauthenticated,
expectedErrorMsg: "auth header missing",
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: " ",
},
{
testName: "when instance not found should return no error",
inputRequest: &instance.DeleteInstanceRequest{
InstanceId: inst.instance.ID() + "invalid",
},
inputContext: ctxWithSysAuthZ,
expectsNullTimestamp: true,
inputContext: context.Background(),
expectedErrorCode: codes.Unauthenticated,
expectedErrorMsg: "auth header missing",
},
{
testName: "when instance not found should return no error",
inputRequest: &instance.DeleteInstanceRequest{
InstanceId: inst.ID() + "invalid",
},
{
testName: "when delete succeeds should return deletion date",
inputRequest: &instance.DeleteInstanceRequest{
InstanceId: inst.instance.ID(),
},
inputContext: ctxWithSysAuthZ,
expectedInstanceID: inst.instance.ID(),
inputContext: ctxWithSysAuthZ,
expectsNullTimestamp: true,
},
{
testName: "when delete succeeds should return deletion date",
inputRequest: &instance.DeleteInstanceRequest{
InstanceId: inst.ID(),
},
}
for _, tc := range tt {
t.Run(fmt.Sprintf("%s - %s", inst.instType, tc.testName), func(t *testing.T) {
// Test
res, err := inst.instance.Client.InstanceV2Beta.DeleteInstance(tc.inputContext, tc.inputRequest)
inputContext: ctxWithSysAuthZ,
expectedInstanceID: inst.ID(),
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// Test
res, err := inst.Client.InstanceV2Beta.DeleteInstance(tc.inputContext, tc.inputRequest)
// Verify
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
assert.Contains(t, status.Convert(err).Message(), tc.expectedErrorMsg)
if tc.expectedErrorMsg == "" {
require.NotNil(t, res)
require.Equal(t, tc.expectsNullTimestamp, res.GetDeletionDate() == nil)
}
})
}
// Verify
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
assert.Contains(t, status.Convert(err).Message(), tc.expectedErrorMsg)
if tc.expectedErrorMsg == "" {
require.NotNil(t, res)
require.Equal(t, tc.expectsNullTimestamp, res.GetDeletionDate() == nil)
}
})
}
}
@@ -110,90 +91,72 @@ func TestUpdateInstace(t *testing.T) {
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
instEventStore := integration.NewInstance(ctxWithSysAuthZ)
orgOwnerESCtx := instEventStore.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
instRelational := integration.NewInstance(ctxWithSysAuthZ)
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, instRelational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
})
orgOwnerRelationalCtx := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
instances := []struct {
instance *integration.Instance
ctx context.Context
}{
{instance: instEventStore, ctx: orgOwnerESCtx},
{instance: instRelational, ctx: orgOwnerRelationalCtx},
}
inst := integration.NewInstance(ctxWithSysAuthZ)
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
t.Cleanup(func() {
instEventStore.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instEventStore.ID()})
instRelational.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
})
for i, inst := range instances {
tt := []struct {
testName string
inputRequest *instance.UpdateInstanceRequest
inputContext context.Context
expectedErrorMsg string
expectedErrorCode codes.Code
expectedNewName string
}{
{
testName: "when invalid context should return unauthN error",
inputRequest: &instance.UpdateInstanceRequest{
InstanceId: inst.instance.ID(),
InstanceName: " ",
},
inputContext: context.Background(),
expectedErrorCode: codes.Unauthenticated,
expectedErrorMsg: "auth header missing",
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: " ",
},
{
testName: "when unauthZ context should return unauthZ error",
inputRequest: &instance.UpdateInstanceRequest{
InstanceId: inst.instance.ID(),
InstanceName: " ",
},
inputContext: inst.ctx,
expectedErrorCode: codes.PermissionDenied,
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
inputContext: context.Background(),
expectedErrorCode: codes.Unauthenticated,
expectedErrorMsg: "auth header missing",
},
{
testName: "when unauthZ context should return unauthZ error",
inputRequest: &instance.UpdateInstanceRequest{
InstanceId: inst.ID(),
InstanceName: " ",
},
{
testName: "when update succeeds should change instance name",
inputRequest: &instance.UpdateInstanceRequest{
InstanceId: inst.instance.ID(),
InstanceName: fmt.Sprintf("an-updated-name %d", i),
},
inputContext: ctxWithSysAuthZ,
expectedNewName: fmt.Sprintf("an-updated-name %d", i),
inputContext: orgOwnerCtx,
expectedErrorCode: codes.PermissionDenied,
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
},
{
testName: "when update succeeds should change instance name",
inputRequest: &instance.UpdateInstanceRequest{
InstanceId: inst.ID(),
InstanceName: "an-updated-name",
},
}
inputContext: ctxWithSysAuthZ,
expectedNewName: "an-updated-name",
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// Test
res, err := inst.instance.Client.InstanceV2Beta.UpdateInstance(tc.inputContext, tc.inputRequest)
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// Test
res, err := inst.Client.InstanceV2Beta.UpdateInstance(tc.inputContext, tc.inputRequest)
// Verify
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
if tc.expectedErrorMsg == "" {
// Verify
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
if tc.expectedErrorMsg == "" {
require.NotNil(t, res)
assert.NotEmpty(t, res.GetChangeDate())
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputContext, time.Minute)
require.EventuallyWithT(t, func(tt *assert.CollectT) {
retrievedInstance, err := inst.instance.Client.InstanceV2Beta.GetInstance(tc.inputContext, &instance.GetInstanceRequest{InstanceId: inst.instance.ID()})
require.Nil(tt, err)
assert.Equal(tt, tc.expectedNewName, retrievedInstance.GetInstance().GetName())
}, retryDuration, tick, "timeout waiting for expected execution result")
}
})
}
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.InstanceV2Beta.GetInstance(tc.inputContext, &instance.GetInstanceRequest{InstanceId: inst.ID()})
require.Nil(tt, err)
assert.Equal(tt, tc.expectedNewName, retrievedInstance.GetInstance().GetName())
}, retryDuration, tick, "timeout waiting for expected execution result")
}
})
}
}
@@ -4,11 +4,9 @@ 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"
@@ -29,76 +27,43 @@ func TestGetInstance(t *testing.T) {
defer cancel()
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
instEventStore := integration.NewInstance(ctxWithSysAuthZ)
orgOwnerCtxES := instEventStore.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
instRelational := integration.NewInstance(ctxWithSysAuthZ)
orgOwnerCtxREL := instRelational.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, instRelational, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
assert.True(tCollect, got.GetEnableRelationalTables().GetEnabled())
})
inst := integration.NewInstance(ctxWithSysAuthZ)
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
t.Cleanup(func() {
instRelational.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
instEventStore.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instEventStore.ID()})
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
})
tt := []struct {
testName string
instance *integration.Instance
inputContext context.Context
expectedInstanceID string
expectedErrorMsg string
expectedErrorCode codes.Code
}{
{
testName: "eventstore instance - when unauthN context should return unauthN error",
testName: "when unauthN context should return unauthN error",
inputContext: context.Background(),
instance: instEventStore,
expectedErrorCode: codes.Unauthenticated,
expectedErrorMsg: "auth header missing",
},
{
testName: "eventstore instance - when unauthZ context should return unauthZ error",
inputContext: orgOwnerCtxES,
instance: instEventStore,
testName: "when unauthZ context should return unauthZ error",
inputContext: orgOwnerCtx,
expectedErrorCode: codes.PermissionDenied,
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
},
{
testName: "eventstore instance - when request succeeds should return matching instance",
testName: "when request succeeds should return matching instance",
inputContext: ctxWithSysAuthZ,
instance: instEventStore,
expectedInstanceID: instEventStore.ID(),
},
// Relational
{
testName: "relational instance - when unauthN context should return unauthN error",
inputContext: context.Background(),
instance: instRelational,
expectedErrorCode: codes.Unauthenticated,
expectedErrorMsg: "auth header missing",
},
{
testName: "relational instance - when unauthZ context should return unauthZ error",
inputContext: orgOwnerCtxREL,
instance: instRelational,
expectedErrorCode: codes.PermissionDenied,
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
},
{
testName: "relational instance - when request succeeds should return matching instance",
inputContext: ctxWithSysAuthZ,
instance: instRelational,
expectedInstanceID: instRelational.ID(),
expectedInstanceID: inst.ID(),
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// Test
res, err := tc.instance.Client.InstanceV2Beta.GetInstance(tc.inputContext, &instance.GetInstanceRequest{InstanceId: tc.instance.ID()})
res, err := inst.Client.InstanceV2Beta.GetInstance(tc.inputContext, &instance.GetInstanceRequest{InstanceId: inst.ID()})
// Verify
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
@@ -140,8 +105,6 @@ func TestListInstances(t *testing.T) {
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
relTableState := integration.RelationalTablesEnableMatrix(t, ctx, ctxWithSysAuthZ)
tt := []struct {
testName string
inputRequest *instance.ListInstancesRequest
@@ -195,29 +158,26 @@ func TestListInstances(t *testing.T) {
},
}
for _, stateCase := range relTableState {
for _, tc := range tt {
t.Run(fmt.Sprintf("%s - %s", stateCase.Name, tc.testName), func(t *testing.T) {
// Test
res, err := inst.Client.InstanceV2Beta.ListInstances(tc.inputContext, tc.inputRequest)
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// Test
res, err := inst.Client.InstanceV2Beta.ListInstances(tc.inputContext, tc.inputRequest)
// Verify
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
// 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)
if tc.expectedErrorMsg == "" {
require.NotNil(t, res)
require.Len(t, res.GetInstances(), len(tc.expectedInstances))
require.Len(t, res.GetInstances(), len(tc.expectedInstances))
for i, ins := range res.GetInstances() {
assert.Equal(t, tc.expectedInstances[i], ins.GetId())
}
for i, ins := range res.GetInstances() {
assert.Equal(t, tc.expectedInstances[i], ins.GetId())
}
})
}
}
})
}
}
func TestListCustomDomains(t *testing.T) {
@@ -229,125 +189,98 @@ func TestListCustomDomains(t *testing.T) {
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
// Eventstore objects
instES := integration.NewInstance(ctxWithSysAuthZ)
orgOwnerCtxES := instES.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
d1ES, d2ES := "custom."+integration.DomainName(), "custom."+integration.DomainName()
_, err := instES.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instES.ID(), Domain: d1ES})
inst := integration.NewInstance(ctxWithSysAuthZ)
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
d1, d2 := "custom."+integration.DomainName(), "custom."+integration.DomainName()
_, err := inst.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), Domain: d1})
require.Nil(t, err)
_, err = instES.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instES.ID(), Domain: 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.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
d1Relational, d2Relational := "custom."+integration.DomainName(), "custom."+integration.DomainName()
_, err = instRelational.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instRelational.ID(), Domain: d1Relational})
require.Nil(t, err)
_, err = instRelational.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: instRelational.ID(), Domain: d2Relational})
_, err = inst.Client.InstanceV2Beta.AddCustomDomain(ctxWithSysAuthZ, &instance.AddCustomDomainRequest{InstanceId: inst.ID(), Domain: d2})
require.Nil(t, err)
t.Cleanup(func() {
instES.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instES.ID(), Domain: d1ES})
instES.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instES.ID(), Domain: d2ES})
instES.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
instRelational.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instRelational.ID(), Domain: d1Relational})
instRelational.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: instRelational.ID(), Domain: d2Relational})
instRelational.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
inst.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: inst.ID(), Domain: d1})
inst.Client.InstanceV2Beta.RemoveCustomDomain(ctxWithSysAuthZ, &instance.RemoveCustomDomainRequest{InstanceId: inst.ID(), Domain: d2})
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
})
testData := []struct {
testType string
inst *integration.Instance
orgOwnerCtx context.Context
expectedDomains []string
expectedDomains := []string{d1, d2}
tt := []struct {
testName string
inputRequest *instance.ListCustomDomainsRequest
inputContext context.Context
expectedErrorMsg string
expectedErrorCode codes.Code
expectedDomains []string
}{
{testType: "eventstore", inst: instES, orgOwnerCtx: orgOwnerCtxES, expectedDomains: []string{d1ES, d2ES}},
{testType: "relational", inst: instRelational, orgOwnerCtx: orgOwnerCtxRelational, expectedDomains: []string{d1Relational, d2Relational}},
{
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,
Queries: []*instance.DomainSearchQuery{
{
Query: &instance.DomainSearchQuery_DomainQuery{
DomainQuery: &instance.DomainQuery{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
},
},
},
},
inputContext: orgOwnerCtx,
expectedErrorCode: codes.PermissionDenied,
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
},
{
testName: "when valid request with filter should return paginated response",
inputRequest: &instance.ListCustomDomainsRequest{
InstanceId: inst.ID(),
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
Queries: []*instance.DomainSearchQuery{
{
Query: &instance.DomainSearchQuery_DomainQuery{
DomainQuery: &instance.DomainQuery{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
},
},
},
},
inputContext: ctxWithSysAuthZ,
expectedDomains: expectedDomains,
},
}
for _, td := range testData {
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",
},
{
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,
Queries: []*instance.DomainSearchQuery{
{
Query: &instance.DomainSearchQuery_DomainQuery{
DomainQuery: &instance.DomainQuery{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
},
},
},
},
inputContext: td.orgOwnerCtx,
expectedErrorCode: codes.PermissionDenied,
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
},
{
testName: "when valid request with filter should return paginated response",
inputRequest: &instance.ListCustomDomainsRequest{
InstanceId: td.inst.ID(),
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE,
Queries: []*instance.DomainSearchQuery{
{
Query: &instance.DomainSearchQuery_DomainQuery{
DomainQuery: &instance.DomainQuery{Domain: "custom", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
},
},
},
},
inputContext: ctxWithSysAuthZ,
expectedDomains: td.expectedDomains,
},
}
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.InstanceV2Beta.ListCustomDomains(tc.inputContext, tc.inputRequest)
for _, tc := range tt {
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.InstanceV2Beta.ListCustomDomains(tc.inputContext, tc.inputRequest)
// Verify
assert.Equal(collect, tc.expectedErrorCode, status.Code(err))
assert.Equal(collect, tc.expectedErrorMsg, status.Convert(err).Message())
// 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)
if tc.expectedErrorMsg == "" {
domains := []string{}
for _, d := range res.GetDomains() {
domains = append(domains, d.GetDomain())
}
}, retryDuration, tick)
})
}
assert.Subset(collect, domains, tc.expectedDomains)
}
}, retryDuration, tick)
})
}
}
@@ -360,117 +293,91 @@ func TestListTrustedDomains(t *testing.T) {
ctxWithSysAuthZ := integration.WithSystemAuthorization(ctx)
// Eventstore objects
instES := integration.NewInstance(ctxWithSysAuthZ)
orgOwnerCtxES := instES.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
d1ES, d2ES := "trusted."+integration.DomainName(), "trusted."+integration.DomainName()
_, err := instES.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instES.ID(), Domain: d1ES})
inst := integration.NewInstance(ctxWithSysAuthZ)
orgOwnerCtx := inst.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
d1, d2 := "trusted."+integration.DomainName(), "trusted."+integration.DomainName()
_, err := inst.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), Domain: d1})
require.Nil(t, err)
_, err = instES.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instES.ID(), Domain: 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.WithAuthorization(context.Background(), integration.UserTypeOrgOwner)
d1Relational, d2Relational := "trusted."+integration.DomainName(), "trusted."+integration.DomainName()
_, err = instRelational.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instRelational.ID(), Domain: d1Relational})
require.Nil(t, err)
_, err = instRelational.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: instRelational.ID(), Domain: d2Relational})
_, err = inst.Client.InstanceV2Beta.AddTrustedDomain(ctxWithSysAuthZ, &instance.AddTrustedDomainRequest{InstanceId: inst.ID(), Domain: d2})
require.Nil(t, err)
t.Cleanup(func() {
instES.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instES.ID(), Domain: d1ES})
instES.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instES.ID(), Domain: d2ES})
instES.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instES.ID()})
instRelational.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instRelational.ID(), Domain: d1Relational})
instRelational.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: instRelational.ID(), Domain: d2Relational})
instRelational.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: instRelational.ID()})
inst.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: inst.ID(), Domain: d1})
inst.Client.InstanceV2Beta.RemoveTrustedDomain(ctxWithSysAuthZ, &instance.RemoveTrustedDomainRequest{InstanceId: inst.ID(), Domain: d2})
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
})
testData := []struct {
testType string
inst *integration.Instance
orgOwnerCtx context.Context
expectedDomains []string
expectedDomains := []string{d1, d2}
tt := []struct {
testName string
inputRequest *instance.ListTrustedDomainsRequest
inputContext context.Context
expectedErrorMsg string
expectedErrorCode codes.Code
expectedDomains []string
}{
{testType: "eventstore", inst: instES, orgOwnerCtx: orgOwnerCtxES, expectedDomains: []string{d1ES, d2ES}},
{testType: "relational", inst: instRelational, 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",
{
testName: "when invalid context should return unauthN error",
inputRequest: &instance.ListTrustedDomainsRequest{
InstanceId: inst.ID(),
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
},
{
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.PermissionDenied,
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
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},
},
{
testName: "when valid request with filter should return paginated response",
inputRequest: &instance.ListTrustedDomainsRequest{
InstanceId: td.inst.ID(),
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
Queries: []*instance.TrustedDomainSearchQuery{
{
Query: &instance.TrustedDomainSearchQuery_DomainQuery{
DomainQuery: &instance.DomainQuery{Domain: "trusted", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
},
inputContext: orgOwnerCtx,
expectedErrorCode: codes.PermissionDenied,
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
},
{
testName: "when valid request with filter should return paginated response",
inputRequest: &instance.ListTrustedDomainsRequest{
InstanceId: inst.ID(),
Pagination: &filter.PaginationRequest{Offset: 0, Limit: 10},
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
Queries: []*instance.TrustedDomainSearchQuery{
{
Query: &instance.TrustedDomainSearchQuery_DomainQuery{
DomainQuery: &instance.DomainQuery{Domain: "trusted", Method: object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS},
},
},
},
inputContext: ctxWithSysAuthZ,
expectedDomains: td.expectedDomains,
},
}
inputContext: ctxWithSysAuthZ,
expectedDomains: expectedDomains,
},
}
for _, tc := range tt {
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.InstanceV2Beta.ListTrustedDomains(tc.inputContext, tc.inputRequest)
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.InstanceV2Beta.ListTrustedDomains(tc.inputContext, tc.inputRequest)
// Verify
assert.Equal(collect, tc.expectedErrorCode, status.Code(err))
assert.Equal(collect, tc.expectedErrorMsg, status.Convert(err).Message())
// Verify
assert.Equal(collect, tc.expectedErrorCode, status.Code(err))
assert.Equal(collect, tc.expectedErrorMsg, status.Convert(err).Message())
if tc.expectedErrorMsg == "" {
require.NotNil(t, res)
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)
domains := []string{}
for _, d := range res.GetTrustedDomain() {
domains = append(domains, d.GetDomain())
}
}, retryDuration, tick)
})
}
assert.Subset(collect, domains, tc.expectedDomains)
}
}, retryDuration, tick)
})
}
}
@@ -19,13 +19,6 @@ import (
"github.com/zitadel/zitadel/pkg/grpc/org/v2"
)
// TODO(IAM-Marco): When permission checks will be implemented, this test needs to be updated to
// add the feature flag switch:
//
// relTableState := integration.RelationalTablesEnableMatrix()
//
// See TestServer_ListOrganizations in org/v2beta/integration_test
// See https://github.com/zitadel/zitadel/issues/10219
func TestServer_ListOrganizations(t *testing.T) {
ListOrgIinstance := integration.NewInstance(CTX)
listOrgIAmOwnerCtx := ListOrgIinstance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
File diff suppressed because it is too large Load Diff
@@ -15,8 +15,6 @@ import (
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/integration"
featurepb "github.com/zitadel/zitadel/pkg/grpc/feature/v2"
instancepb "github.com/zitadel/zitadel/pkg/grpc/instance/v2"
objpb "github.com/zitadel/zitadel/pkg/grpc/object"
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
"github.com/zitadel/zitadel/pkg/grpc/session/v2"
@@ -250,8 +248,7 @@ type sessionAttr struct {
Details *object.Details
}
// listSessionsEnv holds the environment context for ListSessions tests,
// allowing the same test cases to run against both the eventstore and relational backends.
// listSessionsEnv holds the environment context for ListSessions tests.
type listSessionsEnv struct {
ownerCtx context.Context // IAMOwner-level context; can list all sessions
loginCtx context.Context // login-user context; used for session creation in deps
@@ -260,13 +257,9 @@ type listSessionsEnv struct {
loginUserID string
testUser *user.AddHumanUserResponse
newUser func(ctx context.Context) *user.AddHumanUserResponse
// isRelational controls behavior that currently differs between the eventstore
// and relational backends (e.g. sequence field, permission-check semantics).
isRelational bool
}
// runListSessionsTestCases defines and runs the full ListSessions test suite against env.
// This is called by both [TestServer_ListSessions] (eventstore) and [TestServer_ListSessions_Relational].
func runListSessionsTestCases(t *testing.T, env listSessionsEnv) {
t.Helper()
@@ -331,29 +324,17 @@ func runListSessionsTestCases(t *testing.T, env listSessionsEnv) {
},
},
{
// In the eventstore path, the session exists (TotalResult=1) but the permission
// callback hides its content (Sessions=[]). In the relational path the permission
// check is not yet implemented (returns SQL true), so the session is fully visible.
// TODO(IAM-Marco): align expectations once relational permission checks are implemented.
name: "list sessions, no permission",
ctx: env.noPermCtx,
req: &session.ListSessionsRequest{Queries: []*session.SearchQuery{}},
dep: func(t *testing.T, req *session.ListSessionsRequest) []*sessionAttr {
info := newSess(t, env.loginCtx, "", "", nil, nil)
req.Queries = append(req.Queries, &session.SearchQuery{Query: &session.SearchQuery_IdsQuery{IdsQuery: &session.IDsQuery{Ids: []string{info.ID}}}})
if env.isRelational {
return []*sessionAttr{info}
}
return []*sessionAttr{}
},
want: &session.ListSessionsResponse{
Details: &object.ListDetails{TotalResult: 1, Timestamp: timestamppb.Now()},
Sessions: func() []*session.Session {
if env.isRelational {
return []*session.Session{{}}
}
return []*session.Session{}
}(),
Details: &object.ListDetails{TotalResult: 1, Timestamp: timestamppb.Now()},
Sessions: []*session.Session{},
},
},
{
@@ -670,11 +651,7 @@ func runListSessionsTestCases(t *testing.T, env listSessionsEnv) {
for i := range infos {
tt.want.Sessions[i].Id = infos[i].ID
if env.isRelational {
tt.want.Sessions[i].Sequence = 0
} else {
tt.want.Sessions[i].Sequence = infos[i].Details.GetSequence()
}
tt.want.Sessions[i].Sequence = infos[i].Details.GetSequence()
tt.want.Sessions[i].CreationDate = infos[i].Details.GetChangeDate()
tt.want.Sessions[i].ChangeDate = infos[i].Details.GetChangeDate()
@@ -765,37 +742,3 @@ func TestServer_ListSessions_with_expiration_date_filter(t *testing.T) {
assert.Equal(t, session2.SessionId, listSessionsResponse2.Sessions[0].Id)
}
func TestServer_ListSessions_Relational(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
defer cancel()
sysAuthZ := integration.WithSystemAuthorization(ctx)
inst := integration.NewInstance(sysAuthZ)
integration.EnsureInstanceFeature(t, sysAuthZ, inst,
&featurepb.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)},
func(tCollect *assert.CollectT, got *featurepb.GetInstanceFeaturesResponse) {
assert.True(tCollect, got.GetEnableRelationalTables().GetEnabled())
},
)
t.Cleanup(func() {
inst.Client.InstanceV2.DeleteInstance(sysAuthZ, &instancepb.DeleteInstanceRequest{InstanceId: inst.ID()})
})
instOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
loginCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeLogin)
noPermCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeNoPermission)
runListSessionsTestCases(t, listSessionsEnv{
ownerCtx: instOwnerCtx,
loginCtx: loginCtx,
noPermCtx: noPermCtx,
client: inst.Client.SessionV2,
loginUserID: inst.Users.Get(integration.UserTypeLogin).ID,
testUser: inst.CreateHumanUser(instOwnerCtx),
newUser: func(ctx context.Context) *user.AddHumanUserResponse { return inst.CreateHumanUser(ctx) },
isRelational: true,
})
}
@@ -19,10 +19,10 @@ import (
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/backend/v3/domain"
"github.com/zitadel/zitadel/internal/integration"
"github.com/zitadel/zitadel/internal/integration/sink"
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
"github.com/zitadel/zitadel/pkg/grpc/instance/v2"
mgmt "github.com/zitadel/zitadel/pkg/grpc/management"
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
"github.com/zitadel/zitadel/pkg/grpc/session/v2"
@@ -901,120 +901,106 @@ func TestServer_SetSession_expired(t *testing.T) {
func TestServer_DeleteSession_token(t *testing.T) {
sysAuthZ := integration.WithSystemAuthorization(CTX)
relTableState := integration.RelationalTablesEnableMatrix(t, IAMOwnerCTX, sysAuthZ)
for _, stateCase := range relTableState {
t.Run(stateCase.Name, func(t *testing.T) {
loginCTX := stateCase.Inst.WithAuthorizationToken(t.Context(), integration.UserTypeLogin)
createResp, err := stateCase.Inst.Client.SessionV2.CreateSession(loginCTX, &session.CreateSessionRequest{})
require.NoError(t, err)
inst := integration.NewInstance(sysAuthZ)
instOwner := inst.WithAuthorizationToken(IAMOwnerCTX, integration.UserTypeIAMOwner)
t.Cleanup(func() {
_, err := inst.Client.InstanceV2.DeleteInstance(integration.WithSystemAuthorization(context.Background()), &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
require.NoError(t, err)
})
//TODO: remove after create session also uses the relational tables directly
if stateCase.Enabled {
time.Sleep(5 * time.Second)
}
loginCTX := inst.WithAuthorizationToken(t.Context(), integration.UserTypeLogin)
createResp, err := inst.Client.SessionV2.CreateSession(loginCTX, &session.CreateSessionRequest{})
require.NoError(t, err)
_, err = stateCase.Inst.Client.SessionV2.DeleteSession(stateCase.InstOwner, &session.DeleteSessionRequest{
SessionId: createResp.GetSessionId(),
SessionToken: gu.Ptr("invalid"),
})
require.Error(t, err)
if stateCase.Enabled {
integration.RequireStatusError(t, err, domain.ErrSessionTokenInvalid(nil))
}
_, err = inst.Client.SessionV2.DeleteSession(instOwner, &session.DeleteSessionRequest{
SessionId: createResp.GetSessionId(),
SessionToken: gu.Ptr("invalid"),
})
require.Error(t, err)
_, err = stateCase.Inst.Client.SessionV2.DeleteSession(stateCase.InstOwner, &session.DeleteSessionRequest{
SessionId: createResp.GetSessionId(),
SessionToken: gu.Ptr(createResp.GetSessionToken()),
})
require.NoError(t, err)
})
}
_, err = inst.Client.SessionV2.DeleteSession(instOwner, &session.DeleteSessionRequest{
SessionId: createResp.GetSessionId(),
SessionToken: gu.Ptr(createResp.GetSessionToken()),
})
require.NoError(t, err)
}
func TestServer_DeleteSession_own_session(t *testing.T) {
sysAuthZ := integration.WithSystemAuthorization(CTX)
relTableState := integration.RelationalTablesEnableMatrix(t, IAMOwnerCTX, sysAuthZ)
for _, stateCase := range relTableState {
t.Run(stateCase.Name, func(t *testing.T) {
loginCTX := stateCase.Inst.WithAuthorizationToken(t.Context(), integration.UserTypeLogin)
// create two users for the test and a session each to get tokens for authorization
user1 := stateCase.Inst.CreateUserTypeHuman(stateCase.InstOwner, integration.Email())
stateCase.Inst.SetUserPassword(stateCase.InstOwner, user1.GetId(), integration.UserPassword, false)
_, token1, _, _ := stateCase.Inst.CreatePasswordSession(t, loginCTX, user1.GetId(), integration.UserPassword)
inst := integration.NewInstance(sysAuthZ)
instOwner := inst.WithAuthorizationToken(IAMOwnerCTX, integration.UserTypeIAMOwner)
t.Cleanup(func() {
_, err := inst.Client.InstanceV2.DeleteInstance(integration.WithSystemAuthorization(context.Background()), &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
require.NoError(t, err)
})
user2 := stateCase.Inst.CreateUserTypeHuman(stateCase.InstOwner, integration.Email())
stateCase.Inst.SetUserPassword(stateCase.InstOwner, user2.GetId(), integration.UserPassword, false)
_, token2, _, _ := stateCase.Inst.CreatePasswordSession(t, loginCTX, user2.GetId(), integration.UserPassword)
loginCTX := inst.WithAuthorizationToken(t.Context(), integration.UserTypeLogin)
// create two users for the test and a session each to get tokens for authorization
user1 := inst.CreateUserTypeHuman(instOwner, integration.Email())
inst.SetUserPassword(instOwner, user1.GetId(), integration.UserPassword, false)
_, token1, _, _ := inst.CreatePasswordSession(t, loginCTX, user1.GetId(), integration.UserPassword)
// create a new session for the first user
createResp, err := stateCase.Inst.Client.SessionV2.CreateSession(loginCTX, &session.CreateSessionRequest{
Checks: &session.Checks{
User: &session.CheckUser{
Search: &session.CheckUser_UserId{
UserId: user1.GetId(),
},
},
user2 := inst.CreateUserTypeHuman(instOwner, integration.Email())
inst.SetUserPassword(instOwner, user2.GetId(), integration.UserPassword, false)
_, token2, _, _ := inst.CreatePasswordSession(t, loginCTX, user2.GetId(), integration.UserPassword)
// create a new session for the first user
createResp, err := inst.Client.SessionV2.CreateSession(loginCTX, &session.CreateSessionRequest{
Checks: &session.Checks{
User: &session.CheckUser{
Search: &session.CheckUser_UserId{
UserId: user1.GetId(),
},
})
require.NoError(t, err)
},
},
})
require.NoError(t, err)
//TODO: remove after create session also uses the relational tables directly
if stateCase.Enabled {
time.Sleep(5 * time.Second)
}
// delete the new (user1) session must not be possible with user (has no permission)
_, err = inst.Client.SessionV2.DeleteSession(integration.WithAuthorizationToken(t.Context(), token2), &session.DeleteSessionRequest{
SessionId: createResp.GetSessionId(),
})
require.Error(t, err)
// delete the new (user1) session must not be possible with user (has no permission)
_, err = stateCase.Inst.Client.SessionV2.DeleteSession(integration.WithAuthorizationToken(t.Context(), token2), &session.DeleteSessionRequest{
SessionId: createResp.GetSessionId(),
})
require.Error(t, err)
if stateCase.Enabled {
integration.RequireStatusError(t, err, domain.ErrAuthMissingPermission(nil, "", domain.SessionDeletePermission))
}
// delete the new (user1) session by themselves
_, err = stateCase.Inst.Client.SessionV2.DeleteSession(integration.WithAuthorizationToken(t.Context(), token1), &session.DeleteSessionRequest{
SessionId: createResp.GetSessionId(),
})
require.NoError(t, err)
})
}
// delete the new (user1) session by themselves
_, err = inst.Client.SessionV2.DeleteSession(integration.WithAuthorizationToken(t.Context(), token1), &session.DeleteSessionRequest{
SessionId: createResp.GetSessionId(),
})
require.NoError(t, err)
}
func TestServer_DeleteSession_with_permission(t *testing.T) {
sysAuthZ := integration.WithSystemAuthorization(CTX)
relTableState := integration.RelationalTablesEnableMatrix(t, IAMOwnerCTX, sysAuthZ)
inst := integration.NewInstance(sysAuthZ)
instOwner := inst.WithAuthorizationToken(IAMOwnerCTX, integration.UserTypeIAMOwner)
t.Cleanup(func() {
_, err := inst.Client.InstanceV2.DeleteInstance(integration.WithSystemAuthorization(context.Background()), &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
require.NoError(t, err)
})
for _, stateCase := range relTableState {
t.Run(stateCase.Name, func(t *testing.T) {
loginCTX := stateCase.Inst.WithAuthorizationToken(t.Context(), integration.UserTypeLogin)
user1 := stateCase.Inst.CreateUserTypeHuman(stateCase.InstOwner, integration.Email())
createResp, err := stateCase.Inst.Client.SessionV2.CreateSession(loginCTX, &session.CreateSessionRequest{
Checks: &session.Checks{
User: &session.CheckUser{
Search: &session.CheckUser_UserId{
UserId: user1.GetId(),
},
},
loginCTX := inst.WithAuthorizationToken(t.Context(), integration.UserTypeLogin)
user1 := inst.CreateUserTypeHuman(instOwner, integration.Email())
createResp, err := inst.Client.SessionV2.CreateSession(loginCTX, &session.CreateSessionRequest{
Checks: &session.Checks{
User: &session.CheckUser{
Search: &session.CheckUser_UserId{
UserId: user1.GetId(),
},
})
require.NoError(t, err)
},
},
})
require.NoError(t, err)
if stateCase.Enabled {
t.Skip("TODO: remove after permission checks are implemented in the relational tables")
}
// delete the new session by ORG_OWNER
_, err = stateCase.Inst.Client.SessionV2.DeleteSession(
stateCase.Inst.WithAuthorizationToken(t.Context(), integration.UserTypeOrgOwner),
&session.DeleteSessionRequest{
SessionId: createResp.GetSessionId(),
},
)
require.NoError(t, err)
})
}
// delete the new session by ORG_OWNER
_, err = inst.Client.SessionV2.DeleteSession(
inst.WithAuthorizationToken(t.Context(), integration.UserTypeOrgOwner),
&session.DeleteSessionRequest{
SessionId: createResp.GetSessionId(),
},
)
require.NoError(t, err)
}
func TestServer_DeleteSession_expired(t *testing.T) {
@@ -1024,32 +1010,31 @@ func TestServer_DeleteSession_expired(t *testing.T) {
})
sysAuthZ := integration.WithSystemAuthorization(CTX)
relTableState := integration.RelationalTablesEnableMatrix(t, IAMOwnerCTX, sysAuthZ)
inst := integration.NewInstance(sysAuthZ)
t.Cleanup(func() {
_, err := inst.Client.InstanceV2.DeleteInstance(integration.WithSystemAuthorization(context.Background()), &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
require.NoError(t, err)
})
for _, stateCase := range relTableState {
t.Run(stateCase.Name, func(t *testing.T) {
loginCTX := stateCase.Inst.WithAuthorizationToken(t.Context(), integration.UserTypeLogin)
createResp, err := stateCase.Inst.Client.SessionV2.CreateSession(loginCTX, &session.CreateSessionRequest{
Lifetime: durationpb.New(5 * time.Second),
})
require.NoError(t, err)
loginCTX := inst.WithAuthorizationToken(t.Context(), integration.UserTypeLogin)
createResp, err := inst.Client.SessionV2.CreateSession(loginCTX, &session.CreateSessionRequest{
Lifetime: durationpb.New(5 * time.Second),
})
require.NoError(t, err)
// wait until the token expires
time.Sleep(10 * time.Second)
_, err = stateCase.Inst.Client.SessionV2.DeleteSession(stateCase.Inst.WithAuthorizationToken(t.Context(), integration.UserTypeOrgOwner), &session.DeleteSessionRequest{
SessionId: createResp.GetSessionId(),
SessionToken: gu.Ptr(createResp.GetSessionToken()),
})
require.NoError(t, err)
// wait until the token expires
time.Sleep(10 * time.Second)
_, err = inst.Client.SessionV2.DeleteSession(inst.WithAuthorizationToken(t.Context(), integration.UserTypeOrgOwner), &session.DeleteSessionRequest{
SessionId: createResp.GetSessionId(),
SessionToken: gu.Ptr(createResp.GetSessionToken()),
})
require.NoError(t, err)
// get session should return an error
sessionResp, err := stateCase.Inst.Client.SessionV2.GetSession(stateCase.Inst.WithAuthorizationToken(t.Context(), integration.UserTypeOrgOwner),
&session.GetSessionRequest{SessionId: createResp.GetSessionId()})
require.Error(t, err)
// TODO: implement error check once the query side is relational
require.Nil(t, sessionResp)
})
}
// get session should return an error
sessionResp, err := inst.Client.SessionV2.GetSession(inst.WithAuthorizationToken(t.Context(), integration.UserTypeOrgOwner),
&session.GetSessionRequest{SessionId: createResp.GetSessionId()})
require.Error(t, err)
require.Nil(t, sessionResp)
}
func Test_ZITADEL_API_missing_authentication(t *testing.T) {
-38
View File
@@ -5,7 +5,6 @@ import (
"testing"
"time"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -29,40 +28,3 @@ func EnsureInstanceFeature(t *testing.T, ctx context.Context, instance *Instance
tick,
"timed out waiting for ensuring instance feature")
}
type RelationalTableFeatureMatrix struct {
Name string
Inst *Instance
InstOwner context.Context
Enabled bool
}
// TODO(IAM-Marco): Once we have gotten rid of eventstore, this can be removed
func RelationalTablesEnableMatrix(t *testing.T, ctx context.Context, sysAuthZ context.Context) []RelationalTableFeatureMatrix {
return []RelationalTableFeatureMatrix{
func() RelationalTableFeatureMatrix {
inst := NewInstance(sysAuthZ)
instOwner := inst.WithAuthorizationToken(ctx, UserTypeIAMOwner)
return RelationalTableFeatureMatrix{
Name: "when relational tables are disabled",
Inst: inst,
InstOwner: instOwner,
}
}(),
func() RelationalTableFeatureMatrix {
inst := NewInstance(sysAuthZ)
instOwner := inst.WithAuthorizationToken(ctx, UserTypeIAMOwner)
EnsureInstanceFeature(t, ctx, inst, &feature.SetInstanceFeaturesRequest{
EnableRelationalTables: gu.Ptr(true),
}, func(tt *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
assert.True(tt, got.GetEnableRelationalTables().GetEnabled(), "expected relational tables to be enabled")
})
return RelationalTableFeatureMatrix{
Name: "when relational tables are enabled",
Inst: inst,
InstOwner: instOwner,
Enabled: true,
}
}(),
}
}