feat: Feature flag for relational tables (#10599)

# Which Problems Are Solved

This PR introduces a new feature flag `EnableRelationalTables` that will
be used in following implementations to decide whether Zitadel should
use the relational model or the event sourcing one.

# TODO

  - [x] Implement flag at system level
- [x] Display the flag on console:
https://github.com/zitadel/zitadel/pull/10615

# How the Problems Are Solved

  - Implement loading the flag from config
- Add persistence of the flag through gRPC endpoint
(SetInstanceFeatures)
- Implement reading of the flag through gRPC endpoint
(GetInstanceFeatures)

# Additional Changes

Some minor refactoring to remove un-needed generics annotations

# Additional Context

- Closes #10574

---------

Co-authored-by: Silvan <[email protected]>
This commit is contained in:
Marco A.
2025-09-02 09:48:46 +00:00
committed by GitHub
co-authored by Silvan
parent e3dff2482e
commit 75a67be669
46 changed files with 669 additions and 91 deletions
+2
View File
@@ -141,6 +141,7 @@ type InstanceSetupFeatures struct {
LoginV2 *InstanceSetupFeatureLoginV2
PermissionCheckV2 *bool
ConsoleUseV2UserApi *bool
EnableRelationalTables *bool
}
type InstanceSetupFeatureLoginV2 struct {
@@ -174,6 +175,7 @@ func (f *InstanceSetupFeatures) ToInstanceFeatures() (_ *InstanceFeatures, err e
LoginV2: loginV2,
PermissionCheckV2: f.PermissionCheckV2,
ConsoleUseV2UserApi: f.ConsoleUseV2UserApi,
EnableRelationalTables: f.EnableRelationalTables,
}, nil
}
+5 -2
View File
@@ -23,10 +23,11 @@ type InstanceFeatures struct {
LoginV2 *feature.LoginV2
PermissionCheckV2 *bool
ConsoleUseV2UserApi *bool
EnableRelationalTables *bool
}
func (m *InstanceFeatures) isEmpty() bool {
return m.LoginDefaultOrg == nil &&
return m == nil || (m.LoginDefaultOrg == nil &&
m.UserSchema == nil &&
m.TokenExchange == nil &&
// nil check to allow unset improvements
@@ -35,7 +36,9 @@ func (m *InstanceFeatures) isEmpty() bool {
m.OIDCSingleV1SessionTermination == nil &&
m.EnableBackChannelLogout == nil &&
m.LoginV2 == nil &&
m.PermissionCheckV2 == nil && m.ConsoleUseV2UserApi == nil
m.PermissionCheckV2 == nil &&
m.ConsoleUseV2UserApi == nil &&
m.EnableRelationalTables == nil)
}
func (c *Commands) SetInstanceFeatures(ctx context.Context, f *InstanceFeatures) (*domain.ObjectDetails, error) {
@@ -76,6 +76,7 @@ func (m *InstanceFeaturesWriteModel) Query() *eventstore.SearchQueryBuilder {
feature_v2.InstanceLoginVersion,
feature_v2.InstancePermissionCheckV2,
feature_v2.InstanceConsoleUseV2UserApi,
feature_v2.InstanceEnableRelationalTables,
).
Builder().ResourceOwner(m.ResourceOwner)
}
@@ -117,6 +118,9 @@ func reduceInstanceFeature(features *InstanceFeatures, key feature.Key, value an
case feature.KeyConsoleUseV2UserApi:
v := value.(bool)
features.ConsoleUseV2UserApi = &v
case feature.KeyEnableRelationalTables:
v := value.(bool)
features.EnableRelationalTables = &v
}
}
@@ -133,5 +137,6 @@ func (wm *InstanceFeaturesWriteModel) setCommands(ctx context.Context, f *Instan
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.LoginV2, f.LoginV2, feature_v2.InstanceLoginVersion)
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.PermissionCheckV2, f.PermissionCheckV2, feature_v2.InstancePermissionCheckV2)
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.ConsoleUseV2UserApi, f.ConsoleUseV2UserApi, feature_v2.InstanceConsoleUseV2UserApi)
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.EnableRelationalTables, f.EnableRelationalTables, feature_v2.InstanceEnableRelationalTables)
return cmds
}
@@ -0,0 +1,144 @@
package command
import (
"testing"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/internal/feature"
)
func Test_reduceInstanceFeature(t *testing.T) {
type args struct {
features *InstanceFeatures
key feature.Key
value any
}
tt := []struct {
name string
args args
expected *InstanceFeatures
}{
{
name: "key unspecified",
args: args{
features: &InstanceFeatures{},
key: feature.KeyUnspecified,
value: true,
},
expected: &InstanceFeatures{},
},
{
name: "login default org",
args: args{
features: &InstanceFeatures{},
key: feature.KeyLoginDefaultOrg,
value: true,
},
expected: &InstanceFeatures{LoginDefaultOrg: gu.Ptr(true)},
},
{
name: "token exchange",
args: args{
features: &InstanceFeatures{},
key: feature.KeyTokenExchange,
value: true,
},
expected: &InstanceFeatures{TokenExchange: gu.Ptr(true)},
},
{
name: "user schema",
args: args{
features: &InstanceFeatures{},
key: feature.KeyUserSchema,
value: true,
},
expected: &InstanceFeatures{UserSchema: gu.Ptr(true)},
},
{
name: "improved performance",
args: args{
features: &InstanceFeatures{},
key: feature.KeyImprovedPerformance,
value: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeProject},
},
expected: &InstanceFeatures{ImprovedPerformance: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeProject}},
},
{
name: "debug OIDC parent error",
args: args{
features: &InstanceFeatures{},
key: feature.KeyDebugOIDCParentError,
value: true,
},
expected: &InstanceFeatures{DebugOIDCParentError: gu.Ptr(true)},
},
{
name: "OIDC single v1 session termination",
args: args{
features: &InstanceFeatures{},
key: feature.KeyOIDCSingleV1SessionTermination,
value: true,
},
expected: &InstanceFeatures{OIDCSingleV1SessionTermination: gu.Ptr(true)},
},
{
name: "enable back channel logout",
args: args{
features: &InstanceFeatures{},
key: feature.KeyEnableBackChannelLogout,
value: true,
},
expected: &InstanceFeatures{EnableBackChannelLogout: gu.Ptr(true)},
},
{
name: "login v2",
args: args{
features: &InstanceFeatures{},
key: feature.KeyLoginV2,
value: &feature.LoginV2{},
},
expected: &InstanceFeatures{LoginV2: &feature.LoginV2{}},
},
{
name: "permission check v2",
args: args{
features: &InstanceFeatures{},
key: feature.KeyPermissionCheckV2,
value: true,
},
expected: &InstanceFeatures{PermissionCheckV2: gu.Ptr(true)},
},
{
name: "console use v2 user api",
args: args{
features: &InstanceFeatures{},
key: feature.KeyConsoleUseV2UserApi,
value: true,
},
expected: &InstanceFeatures{ConsoleUseV2UserApi: gu.Ptr(true)},
},
{
name: "enable relational tables",
args: args{
features: &InstanceFeatures{},
key: feature.KeyEnableRelationalTables,
value: true,
},
expected: &InstanceFeatures{EnableRelationalTables: gu.Ptr(true)},
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// Test
reduceInstanceFeature(tc.args.features, tc.args.key, tc.args.value)
// Verify
assert.Equal(t, tc.expected, tc.args.features)
})
}
}
+91 -15
View File
@@ -12,12 +12,15 @@ import (
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/feature"
feature_v1 "github.com/zitadel/zitadel/internal/repository/feature"
"github.com/zitadel/zitadel/internal/repository/feature/feature_v2"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestCommands_SetInstanceFeatures(t *testing.T) {
t.Parallel()
ctx := authz.WithInstanceID(context.Background(), "instance1")
aggregate := feature_v2.NewAggregate("instance1", "instance1")
@@ -53,7 +56,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
eventstore: expectEventstore(
expectFilter(),
expectPush(
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceLoginDefaultOrgEventType, true,
),
@@ -70,7 +73,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
name: "set LoginDefaultOrg, update from v1",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(feature_v1.NewSetEvent[feature_v1.Boolean](
eventFromEventPusher(feature_v1.NewSetEvent(
ctx, &eventstore.Aggregate{
ID: "instance1",
ResourceOwner: "instance1",
@@ -82,7 +85,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
)),
),
expectPush(
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceLoginDefaultOrgEventType, true,
),
@@ -100,7 +103,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
eventstore: expectEventstore(
expectFilter(),
expectPush(
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceUserSchemaEventType, true,
),
@@ -118,7 +121,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
eventstore: expectEventstore(
expectFilter(),
expectPushFailed(io.ErrClosedPipe,
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceConsoleUseV2UserApi, true,
),
@@ -134,24 +137,27 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
eventstore: expectEventstore(
expectFilter(),
expectPush(
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceLoginDefaultOrgEventType, true,
),
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceUserSchemaEventType, true,
),
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType, true,
),
feature_v2.NewSetEvent(ctx, aggregate,
feature_v2.InstanceEnableRelationalTables, true),
),
),
args: args{ctx, &InstanceFeatures{
LoginDefaultOrg: gu.Ptr(true),
UserSchema: gu.Ptr(true),
OIDCSingleV1SessionTermination: gu.Ptr(true),
EnableRelationalTables: gu.Ptr(true),
}},
want: &domain.ObjectDetails{
ResourceOwner: "instance1",
@@ -162,7 +168,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
eventstore: expectEventstore(
// throw in some set events, reset and set again.
expectFilter(
eventFromEventPusher(feature_v2.NewSetEvent[bool](
eventFromEventPusher(feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceLoginDefaultOrgEventType, true,
)),
@@ -170,17 +176,17 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
ctx, aggregate,
feature_v2.InstanceResetEventType,
)),
eventFromEventPusher(feature_v2.NewSetEvent[bool](
eventFromEventPusher(feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceLoginDefaultOrgEventType, false,
)),
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType, false,
),
),
expectPush(
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceLoginDefaultOrgEventType, true,
),
@@ -197,6 +203,8 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
c := &Commands{
eventstore: tt.eventstore(t),
}
@@ -227,7 +235,7 @@ func TestCommands_ResetInstanceFeatures(t *testing.T) {
name: "push error",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(feature_v2.NewSetEvent[bool](
eventFromEventPusher(feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceLoginDefaultOrgEventType, true,
)),
@@ -242,7 +250,7 @@ func TestCommands_ResetInstanceFeatures(t *testing.T) {
name: "success",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(feature_v2.NewSetEvent[bool](
eventFromEventPusher(feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceLoginDefaultOrgEventType, true,
)),
@@ -259,7 +267,7 @@ func TestCommands_ResetInstanceFeatures(t *testing.T) {
name: "no change after previous reset",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(feature_v2.NewSetEvent[bool](
eventFromEventPusher(feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceLoginDefaultOrgEventType, true,
)),
@@ -294,3 +302,71 @@ func TestCommands_ResetInstanceFeatures(t *testing.T) {
})
}
}
func TestInstanceFeatures_isEmpty(t *testing.T) {
t.Parallel()
tt := []struct {
name string
features *InstanceFeatures
want bool
}{
{
name: "nil features",
features: nil,
want: true,
},
{
name: "empty features",
features: &InstanceFeatures{},
want: true,
},
{
name: "LoginDefaultOrg set",
features: &InstanceFeatures{
LoginDefaultOrg: gu.Ptr(true),
},
want: false,
},
{
name: "UserSchema set",
features: &InstanceFeatures{
UserSchema: gu.Ptr(true),
},
want: false,
},
{
name: "TokenExchange set",
features: &InstanceFeatures{
TokenExchange: gu.Ptr(true),
},
want: false,
},
{
name: "ImprovedPerformance set",
features: &InstanceFeatures{
ImprovedPerformance: []feature.ImprovedPerformanceType{},
},
want: false,
},
{
name: "multiple fields set",
features: &InstanceFeatures{
LoginDefaultOrg: gu.Ptr(true),
UserSchema: gu.Ptr(false),
PermissionCheckV2: gu.Ptr(true),
EnableRelationalTables: gu.Ptr(true),
},
want: false,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := tc.features.isEmpty()
assert.Equal(t, tc.want, got)
})
}
}
+127
View File
@@ -3,12 +3,15 @@ package command
import (
"context"
"encoding/json"
"net/url"
"slices"
"strings"
"testing"
"time"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"golang.org/x/text/language"
@@ -18,6 +21,7 @@ import (
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/feature"
"github.com/zitadel/zitadel/internal/id"
id_mock "github.com/zitadel/zitadel/internal/id/mock"
"github.com/zitadel/zitadel/internal/repository/instance"
@@ -1727,3 +1731,126 @@ func TestCommandSide_RemoveInstance(t *testing.T) {
})
}
}
func TestInstanceSetupFeatures_ToInstanceFeatures(t *testing.T) {
t.Parallel()
type fields struct {
LoginDefaultOrg *bool
UserSchema *bool
TokenExchange *bool
ImprovedPerformance []feature.ImprovedPerformanceType
DebugOIDCParentError *bool
OIDCSingleV1SessionTermination *bool
EnableBackChannelLogout *bool
LoginV2 *InstanceSetupFeatureLoginV2
PermissionCheckV2 *bool
ConsoleUseV2UserApi *bool
EnableRelationalTables *bool
}
correctlyParsedURI, err := url.Parse("https://example.com")
require.NoError(t, err)
tt := []struct {
name string
fields fields
want *InstanceFeatures
wantErr bool
}{
{
name: "nil features returns nil",
fields: fields{},
want: &InstanceFeatures{},
},
{
name: "all fields no login v2",
fields: fields{
LoginDefaultOrg: gu.Ptr(true),
UserSchema: gu.Ptr(false),
TokenExchange: gu.Ptr(true),
ImprovedPerformance: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeOrgDomainVerified},
DebugOIDCParentError: gu.Ptr(true),
OIDCSingleV1SessionTermination: gu.Ptr(false),
EnableBackChannelLogout: gu.Ptr(true),
PermissionCheckV2: gu.Ptr(true),
ConsoleUseV2UserApi: gu.Ptr(false),
EnableRelationalTables: gu.Ptr(true),
},
want: &InstanceFeatures{
LoginDefaultOrg: gu.Ptr(true),
UserSchema: gu.Ptr(false),
TokenExchange: gu.Ptr(true),
ImprovedPerformance: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeOrgDomainVerified},
DebugOIDCParentError: gu.Ptr(true),
OIDCSingleV1SessionTermination: gu.Ptr(false),
EnableBackChannelLogout: gu.Ptr(true),
LoginV2: nil,
PermissionCheckV2: gu.Ptr(true),
ConsoleUseV2UserApi: gu.Ptr(false),
EnableRelationalTables: gu.Ptr(true),
},
},
{
name: "with login v2 no base uri",
fields: fields{
LoginV2: &InstanceSetupFeatureLoginV2{
Required: true,
},
},
want: &InstanceFeatures{
LoginV2: &feature.LoginV2{
Required: true,
},
},
},
{
name: "with login v2 valid base uri",
fields: fields{
LoginV2: &InstanceSetupFeatureLoginV2{
Required: true,
BaseURI: gu.Ptr("https://example.com"),
},
},
want: &InstanceFeatures{
LoginV2: &feature.LoginV2{
Required: true,
BaseURI: correctlyParsedURI,
},
},
},
{
name: "with login v2 invalid base uri",
fields: fields{
LoginV2: &InstanceSetupFeatureLoginV2{
Required: true,
BaseURI: gu.Ptr("://invalid"),
},
},
wantErr: true,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
f := &InstanceSetupFeatures{
LoginDefaultOrg: tc.fields.LoginDefaultOrg,
UserSchema: tc.fields.UserSchema,
TokenExchange: tc.fields.TokenExchange,
ImprovedPerformance: tc.fields.ImprovedPerformance,
DebugOIDCParentError: tc.fields.DebugOIDCParentError,
OIDCSingleV1SessionTermination: tc.fields.OIDCSingleV1SessionTermination,
EnableBackChannelLogout: tc.fields.EnableBackChannelLogout,
LoginV2: tc.fields.LoginV2,
PermissionCheckV2: tc.fields.PermissionCheckV2,
ConsoleUseV2UserApi: tc.fields.ConsoleUseV2UserApi,
EnableRelationalTables: tc.fields.EnableRelationalTables,
}
got, err := f.ToInstanceFeatures()
require.Equal(t, tc.wantErr, err != nil)
assert.Equal(t, tc.want, got)
})
}
}
+4 -2
View File
@@ -18,10 +18,11 @@ type SystemFeatures struct {
EnableBackChannelLogout *bool
LoginV2 *feature.LoginV2
PermissionCheckV2 *bool
EnableRelationalTables *bool
}
func (m *SystemFeatures) isEmpty() bool {
return m.LoginDefaultOrg == nil &&
return m == nil || (m.LoginDefaultOrg == nil &&
m.UserSchema == nil &&
m.TokenExchange == nil &&
// nil check to allow unset improvements
@@ -29,7 +30,8 @@ func (m *SystemFeatures) isEmpty() bool {
m.OIDCSingleV1SessionTermination == nil &&
m.EnableBackChannelLogout == nil &&
m.LoginV2 == nil &&
m.PermissionCheckV2 == nil
m.PermissionCheckV2 == nil &&
m.EnableRelationalTables == nil)
}
func (c *Commands) SetSystemFeatures(ctx context.Context, f *SystemFeatures) (*domain.ObjectDetails, error) {
+8 -3
View File
@@ -67,6 +67,7 @@ func (m *SystemFeaturesWriteModel) Query() *eventstore.SearchQueryBuilder {
feature_v2.SystemEnableBackChannelLogout,
feature_v2.SystemLoginVersion,
feature_v2.SystemPermissionCheckV2,
feature_v2.SystemEnableRelationalTables,
).
Builder().ResourceOwner(m.ResourceOwner)
}
@@ -101,6 +102,9 @@ func reduceSystemFeature(features *SystemFeatures, key feature.Key, value any) {
case feature.KeyPermissionCheckV2:
v := value.(bool)
features.PermissionCheckV2 = &v
case feature.KeyEnableRelationalTables:
v := value.(bool)
features.EnableRelationalTables = &v
}
}
@@ -115,23 +119,24 @@ func (wm *SystemFeaturesWriteModel) setCommands(ctx context.Context, f *SystemFe
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.EnableBackChannelLogout, f.EnableBackChannelLogout, feature_v2.SystemEnableBackChannelLogout)
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.LoginV2, f.LoginV2, feature_v2.SystemLoginVersion)
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.PermissionCheckV2, f.PermissionCheckV2, feature_v2.SystemPermissionCheckV2)
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.EnableRelationalTables, f.EnableRelationalTables, feature_v2.SystemEnableRelationalTables)
return cmds
}
func appendFeatureUpdate[T comparable](ctx context.Context, cmds []eventstore.Command, aggregate *feature_v2.Aggregate, oldValue, newValue *T, eventType eventstore.EventType) []eventstore.Command {
if newValue != nil && (oldValue == nil || *oldValue != *newValue) {
cmds = append(cmds, feature_v2.NewSetEvent[T](ctx, aggregate, eventType, *newValue))
cmds = append(cmds, feature_v2.NewSetEvent(ctx, aggregate, eventType, *newValue))
}
return cmds
}
func appendFeatureSliceUpdate[T comparable](ctx context.Context, cmds []eventstore.Command, aggregate *feature_v2.Aggregate, oldValues, newValues []T, eventType eventstore.EventType) []eventstore.Command {
if len(newValues) != len(oldValues) {
return append(cmds, feature_v2.NewSetEvent[[]T](ctx, aggregate, eventType, newValues))
return append(cmds, feature_v2.NewSetEvent(ctx, aggregate, eventType, newValues))
}
for i, oldValue := range oldValues {
if oldValue != newValues[i] {
return append(cmds, feature_v2.NewSetEvent[[]T](ctx, aggregate, eventType, newValues))
return append(cmds, feature_v2.NewSetEvent(ctx, aggregate, eventType, newValues))
}
}
return cmds
+53 -14
View File
@@ -11,11 +11,14 @@ import (
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/feature"
"github.com/zitadel/zitadel/internal/repository/feature/feature_v2"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestCommands_SetSystemFeatures(t *testing.T) {
t.Parallel()
aggregate := feature_v2.NewAggregate("SYSTEM", "SYSTEM")
type args struct {
@@ -50,7 +53,7 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
eventstore: expectEventstore(
expectFilter(),
expectPush(
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemLoginDefaultOrgEventType, true,
),
@@ -68,7 +71,7 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
eventstore: expectEventstore(
expectFilter(),
expectPush(
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemUserSchemaEventType, true,
),
@@ -86,7 +89,7 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
eventstore: expectEventstore(
expectFilter(),
expectPushFailed(io.ErrClosedPipe,
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemEnableBackChannelLogout, true,
),
@@ -102,24 +105,29 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
eventstore: expectEventstore(
expectFilter(),
expectPush(
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemLoginDefaultOrgEventType, true,
),
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemUserSchemaEventType, true,
),
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemOIDCSingleV1SessionTerminationEventType, true,
),
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemEnableRelationalTables, true,
),
),
),
args: args{context.Background(), &SystemFeatures{
LoginDefaultOrg: gu.Ptr(true),
UserSchema: gu.Ptr(true),
OIDCSingleV1SessionTermination: gu.Ptr(true),
EnableRelationalTables: gu.Ptr(true),
}},
want: &domain.ObjectDetails{
ResourceOwner: "SYSTEM",
@@ -130,7 +138,7 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
eventstore: expectEventstore(
// throw in some set events, reset and set again.
expectFilter(
eventFromEventPusher(feature_v2.NewSetEvent[bool](
eventFromEventPusher(feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemLoginDefaultOrgEventType, true,
)),
@@ -138,21 +146,21 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
context.Background(), aggregate,
feature_v2.SystemResetEventType,
)),
eventFromEventPusher(feature_v2.NewSetEvent[bool](
eventFromEventPusher(feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemLoginDefaultOrgEventType, false,
)),
),
expectPush(
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemLoginDefaultOrgEventType, true,
),
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemUserSchemaEventType, true,
),
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemOIDCSingleV1SessionTerminationEventType, false,
),
@@ -170,6 +178,8 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
c := &Commands{
eventstore: tt.eventstore(t),
}
@@ -199,7 +209,7 @@ func TestCommands_ResetSystemFeatures(t *testing.T) {
name: "push error",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(feature_v2.NewSetEvent[bool](
eventFromEventPusher(feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemLoginDefaultOrgEventType, true,
)),
@@ -214,7 +224,7 @@ func TestCommands_ResetSystemFeatures(t *testing.T) {
name: "success",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(feature_v2.NewSetEvent[bool](
eventFromEventPusher(feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemLoginDefaultOrgEventType, true,
)),
@@ -231,7 +241,7 @@ func TestCommands_ResetSystemFeatures(t *testing.T) {
name: "no change after previous reset",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(feature_v2.NewSetEvent[bool](
eventFromEventPusher(feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemLoginDefaultOrgEventType, true,
)),
@@ -266,3 +276,32 @@ func TestCommands_ResetSystemFeatures(t *testing.T) {
})
}
}
func TestSystemFeatures_isEmpty(t *testing.T) {
t.Parallel()
tests := []struct {
name string
sysFeatures *SystemFeatures
want bool
}{
{name: "nil features", sysFeatures: nil, want: true},
{name: "empty features", sysFeatures: &SystemFeatures{}, want: true},
{name: "LoginDefaultOrg set", sysFeatures: &SystemFeatures{LoginDefaultOrg: gu.Ptr(true)}, want: false},
{name: "UserSchema set", sysFeatures: &SystemFeatures{UserSchema: gu.Ptr(true)}, want: false},
{name: "TokenExchange set", sysFeatures: &SystemFeatures{TokenExchange: gu.Ptr(true)}, want: false},
{name: "ImprovedPerformance set", sysFeatures: &SystemFeatures{ImprovedPerformance: []feature.ImprovedPerformanceType{}}, want: false},
{name: "OIDCSingleV1SessionTermination set", sysFeatures: &SystemFeatures{OIDCSingleV1SessionTermination: gu.Ptr(true)}, want: false},
{name: "EnableBackChannelLogout set", sysFeatures: &SystemFeatures{EnableBackChannelLogout: gu.Ptr(true)}, want: false},
{name: "LoginV2 set", sysFeatures: &SystemFeatures{LoginV2: &feature.LoginV2{}}, want: false},
{name: "PermissionCheckV2 set", sysFeatures: &SystemFeatures{PermissionCheckV2: gu.Ptr(true)}, want: false},
{name: "EnableRelationalTables set", sysFeatures: &SystemFeatures{EnableRelationalTables: gu.Ptr(true)}, want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := tt.sysFeatures.isEmpty()
assert.Equal(t, tt.want, got)
})
}
}