From 7d3ca4b1eba0a2c17c6c40008444c600efa6cb2a Mon Sep 17 00:00:00 2001 From: Marco Ardizzone Date: Wed, 24 Sep 2025 10:57:13 +0200 Subject: [PATCH] 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. --- .../org/v2/integration_test/query_test.go | 6 + internal/api/grpc/org/v2/query.go | 5 + .../org/v2beta/integration_test/org_test.go | 108 ++++++++---------- internal/api/grpc/org/v2beta/org.go | 4 + internal/integration/feature.go | 20 ++++ 5 files changed, 84 insertions(+), 59 deletions(-) diff --git a/internal/api/grpc/org/v2/integration_test/query_test.go b/internal/api/grpc/org/v2/integration_test/query_test.go index d7acf45121f..a393d9a1f66 100644 --- a/internal/api/grpc/org/v2/integration_test/query_test.go +++ b/internal/api/grpc/org/v2/integration_test/query_test.go @@ -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 diff --git a/internal/api/grpc/org/v2/query.go b/internal/api/grpc/org/v2/query.go index 09e2534e8dc..1fff47f490c 100644 --- a/internal/api/grpc/org/v2/query.go +++ b/internal/api/grpc/org/v2/query.go @@ -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 diff --git a/internal/api/grpc/org/v2beta/integration_test/org_test.go b/internal/api/grpc/org/v2beta/integration_test/org_test.go index 269ff9eef75..6fd97556fd7 100644 --- a/internal/api/grpc/org/v2beta/integration_test/org_test.go +++ b/internal/api/grpc/org/v2beta/integration_test/org_test.go @@ -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") + }) + } } } diff --git a/internal/api/grpc/org/v2beta/org.go b/internal/api/grpc/org/v2beta/org.go index 60e6018fbf2..ec59970b9ba 100644 --- a/internal/api/grpc/org/v2beta/org.go +++ b/internal/api/grpc/org/v2beta/org.go @@ -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 diff --git a/internal/integration/feature.go b/internal/integration/feature.go index 07942fcdcd3..41b9eaba880 100644 --- a/internal/integration/feature.go +++ b/internal/integration/feature.go @@ -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)}, + }, + } +}