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,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")
}
})
}
})
}
}