Link ListOrganizations endpoints (v2 and v2beta) to ListOrgsCommand commander.

The change adds a switch at gRPC endpoint level to call the commander and execute the relational table logic for ListOrganizations. The change also updates the existing ListOrganizations integration tests.
There are also some integration tests logic being refactored and moved into utility functions.
This commit is contained in:
Marco Ardizzone
2025-09-24 10:57:13 +02:00
parent 49c4c0bc4e
commit 7d3ca4b1eb
5 changed files with 84 additions and 59 deletions

View File

@@ -46,6 +46,12 @@ func createOrganizationWithCustomOrgID(ctx context.Context, name string, orgID s
}
}
// 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
func TestServer_ListOrganizations(t *testing.T) {
type args struct {
ctx context.Context

View File

@@ -5,6 +5,7 @@ import (
"connectrpc.com/connect"
orgv2 "github.com/zitadel/zitadel/backend/v3/api/org/v2"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
"github.com/zitadel/zitadel/internal/domain"
@@ -14,6 +15,10 @@ import (
)
func (s *Server) ListOrganizations(ctx context.Context, req *connect.Request[org.ListOrganizationsRequest]) (*connect.Response[org.ListOrganizationsResponse], error) {
if authz.GetFeatures(ctx).EnableRelationalTables {
return orgv2.ListOrganizations(ctx, req)
}
queries, err := listOrgRequestToModel(ctx, req)
if err != nil {
return nil, err

View File

@@ -305,19 +305,7 @@ func TestServer_UpdateOrganization(t *testing.T) {
orgs, orgsName, _ := createOrgs(CTX, t, Client, 2)
relTableState := []struct {
state string
featureSet *feature.SetInstanceFeaturesRequest
}{
{
state: "when relational tables are enabled",
featureSet: &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)},
},
{
state: "when relational tables are disabled",
featureSet: &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(false)},
},
}
relTableState := integration.RelationalTablesEnableMatrix()
tests := []struct {
name string
@@ -359,18 +347,11 @@ func TestServer_UpdateOrganization(t *testing.T) {
}
for _, stateCase := range relTableState {
_, err := Instance.Client.FeatureV2.SetInstanceFeatures(ctx, stateCase.featureSet)
require.NoError(t, err)
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, time.Minute)
// It is necessary to use EventuallyWithT otherwise the test cases are going to fail randomly
// because the flag has not been updated in the projections on time.
require.EventuallyWithT(t, func(collect *assert.CollectT) {
features, err := Instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{})
assert.NoError(collect, err)
assert.Equal(collect, stateCase.featureSet.GetEnableRelationalTables(), features.EnableRelationalTables.GetEnabled())
}, retryDuration, tick)
integration.EnsureInstanceFeature(t, CTX, Instance, stateCase.FeatureSet, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
assert.Equal(tCollect, stateCase.FeatureSet.GetEnableRelationalTables(), got.EnableRelationalTables.GetEnabled())
})
for _, tt := range tests {
t.Run(fmt.Sprintf("%s - %s", stateCase.state, tt.name), func(t1 *testing.T) {
t.Run(fmt.Sprintf("%s - %s", stateCase.State, tt.name), func(t1 *testing.T) {
got, err := Client.UpdateOrganization(ctx, tt.req)
if tt.wantErr {
require.Error(t1, err)
@@ -396,12 +377,14 @@ func TestServer_ListOrganizations(t *testing.T) {
noOfOrgs := 3
orgs, orgsName, orgsDomain := createOrgs(listOrgIAmOwnerCtx, t, listOrgClient, noOfOrgs)
// deactivat org[1]
// deactivate org[1]
_, err := listOrgClient.DeactivateOrganization(listOrgIAmOwnerCtx, &v2beta_org.DeactivateOrganizationRequest{
Id: orgs[1].Id,
})
require.NoError(t, err)
relTableState := integration.RelationalTablesEnableMatrix()
tests := []struct {
name string
ctx context.Context
@@ -639,47 +622,54 @@ func TestServer_ListOrganizations(t *testing.T) {
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, 10*time.Minute)
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
got, err := listOrgClient.ListOrganizations(tt.ctx, &v2beta_org.ListOrganizationsRequest{
Filter: tt.query,
})
if tt.err != nil {
require.ErrorContains(ttt, err, tt.err.Error())
return
}
require.NoError(ttt, err)
require.Equal(ttt, uint64(len(tt.want)), got.Pagination.GetTotalResult())
for _, stateCase := range relTableState {
integration.EnsureInstanceFeature(t, CTX, Instance, stateCase.FeatureSet, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
assert.Equal(tCollect, stateCase.FeatureSet.GetEnableRelationalTables(), got.EnableRelationalTables.GetEnabled())
})
foundOrgs := 0
for _, got := range got.Organizations {
for _, org := range tt.want {
for _, tt := range tests {
t.Run(fmt.Sprintf("%s - %s", stateCase.State, tt.name), func(t *testing.T) {
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, 10*time.Minute)
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
got, err := listOrgClient.ListOrganizations(tt.ctx, &v2beta_org.ListOrganizationsRequest{
Filter: tt.query,
})
if tt.err != nil {
require.ErrorContains(ttt, err, tt.err.Error())
return
}
require.NoError(ttt, err)
// created/chagned date
gotCD := got.GetCreationDate().AsTime()
now := time.Now()
assert.WithinRange(ttt, gotCD, testStartTimestamp, now.Add(time.Minute))
gotCD = got.GetChangedDate().AsTime()
assert.WithinRange(ttt, gotCD, testStartTimestamp, now.Add(time.Minute))
require.Equal(ttt, uint64(len(tt.want)), got.Pagination.GetTotalResult())
// default org
if org.Name == got.Name && got.Name == "testinstance" {
foundOrgs += 1
continue
}
foundOrgs := 0
for _, got := range got.Organizations {
for _, org := range tt.want {
if org.Name == got.Name &&
org.Id == got.Id {
foundOrgs += 1
// created/chagned date
gotCD := got.GetCreationDate().AsTime()
now := time.Now()
assert.WithinRange(ttt, gotCD, testStartTimestamp, now.Add(time.Minute))
gotCD = got.GetChangedDate().AsTime()
assert.WithinRange(ttt, gotCD, testStartTimestamp, now.Add(time.Minute))
// default org
if org.Name == got.Name && got.Name == "testinstance" {
foundOrgs += 1
continue
}
if org.Name == got.Name &&
org.Id == got.Id {
foundOrgs += 1
}
}
}
}
require.Equal(ttt, len(tt.want), foundOrgs)
}, retryDuration, tick, "timeout waiting for expected organizations being created")
})
require.Equal(ttt, len(tt.want), foundOrgs)
}, retryDuration, tick, "timeout waiting for expected organizations being created")
})
}
}
}

View File

@@ -48,6 +48,10 @@ func (s *Server) UpdateOrganization(ctx context.Context, request *connect.Reques
}
func (s *Server) ListOrganizations(ctx context.Context, request *connect.Request[v2beta_org.ListOrganizationsRequest]) (*connect.Response[v2beta_org.ListOrganizationsResponse], error) {
if authz.GetFeatures(ctx).EnableRelationalTables {
return orgv2.ListOrganizationsBeta(ctx, request)
}
queries, err := listOrgRequestToModel(s.systemDefaults, request.Msg)
if err != nil {
return nil, err

View File

@@ -5,6 +5,7 @@ import (
"testing"
"time"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -28,3 +29,22 @@ func EnsureInstanceFeature(t *testing.T, ctx context.Context, instance *Instance
tick,
"timed out waiting for ensuring instance feature")
}
type RelationalTableFeatureMatrix struct {
State string
FeatureSet *feature.SetInstanceFeaturesRequest
}
// TODO(IAM-Marco): Once we have gotten rid of eventstore, this can be removed
func RelationalTablesEnableMatrix() []RelationalTableFeatureMatrix {
return []RelationalTableFeatureMatrix{
{
State: "when relational tables are enabled",
FeatureSet: &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)},
},
{
State: "when relational tables are disabled",
FeatureSet: &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(false)},
},
}
}