From 40de3d462adc3f5ab6adcb3905f36cdb39d11e14 Mon Sep 17 00:00:00 2001 From: Iraq Jaber Date: Wed, 30 Apr 2025 09:43:36 +0200 Subject: [PATCH] fixup! fixup! fixup! fixup! fixup! fixup! refactor(api): moving organization API resourced based added DeleteOrganization() --- .../org/v2beta/integration_test/org_test.go | 91 +++++++++++++++---- internal/api/grpc/org/v2beta/org.go | 10 ++ proto/zitadel/org/v2beta/org_service.proto | 59 ++++++------ 3 files changed, 113 insertions(+), 47 deletions(-) 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 11438fcdb1..e48151bb7c 100644 --- a/internal/api/grpc/org/v2beta/integration_test/org_test.go +++ b/internal/api/grpc/org/v2beta/integration_test/org_test.go @@ -35,6 +35,7 @@ func TestMain(m *testing.M) { Instance = integration.NewInstance(ctx) Client = Instance.Client.OrgV2beta + CTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner) CTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner) User = Instance.CreateHumanUser(CTX) return m.Run() @@ -58,14 +59,14 @@ func TestServer_GetOrganizationByID(t *testing.T) { }{ { name: "get organization happy path", - ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner), + ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeIAMOwner), req: &org.GetOrganizationByIDRequest{ Id: orgId, }, }, { name: "get organization that doesn't exist", - ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner), + ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeIAMOwner), req: &org.GetOrganizationByIDRequest{ Id: "non existing organization", }, @@ -249,25 +250,25 @@ func TestServer_UpdateOrganization(t *testing.T) { want *org.UpdateOrganizationResponse wantErr bool }{ - { - name: "update org with same name", - ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner), - req: &org.UpdateOrganizationRequest{ - Id: orgId, - Name: orgName, - }, - }, { name: "update org with new name", - ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner), + ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeIAMOwner), req: &org.UpdateOrganizationRequest{ Id: orgId, Name: "new org name", }, }, + { + name: "update org with same name", + ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeIAMOwner), + req: &org.UpdateOrganizationRequest{ + Id: orgId, + Name: orgName, + }, + }, { name: "update org with no id", - ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner), + ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeIAMOwner), req: &org.UpdateOrganizationRequest{ Id: orgId, // Name: "", @@ -299,7 +300,7 @@ func TestServer_ListOrganization(t *testing.T) { noOfOrgs := 3 orgs, orgsName, err := createOrgs(noOfOrgs) if err != nil { - assert.Fail(t, "unable to create org") + assert.Fail(t, "unable to create orgs") } tests := []struct { @@ -310,8 +311,8 @@ func TestServer_ListOrganization(t *testing.T) { wantErr bool }{ { - name: "update org with same name", - ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner), + name: "list organizations happy path", + ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeIAMOwner), req: &org.ListOrganizationsRequest{}, want: []*org.Organization{ { @@ -358,17 +359,75 @@ func TestServer_ListOrganization(t *testing.T) { // require.Equal(t, org.do, got.Result[i].Name) } } - fmt.Printf("@@ >>>>>>>>>>>>>>>>>>>>>>>>>>>> foundOrgs = %+v\n", foundOrgs) require.Equal(t, len(tt.want), foundOrgs) }, retryDuration, tick, "timeout waiting for expected organizations being created") }) } } +func TestServer_DeleteOrganization(t *testing.T) { + tests := []struct { + name string + ctx context.Context + createOrgFunc func() string + req *org.DeleteOrganizationRequest + want *org.DeleteOrganizationResponse + err error + }{ + { + name: "delete org happy path", + ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeIAMOwner), + createOrgFunc: func() string { + orgs, _, err := createOrgs(1) + if err != nil { + assert.Fail(t, "unable to create org") + } + return orgs[0].OrganizationId + }, + req: &org.DeleteOrganizationRequest{}, + }, + { + name: "delete non existent org", + ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeIAMOwner), + req: &org.DeleteOrganizationRequest{ + Id: "non existent org id", + }, + err: fmt.Errorf("Organisation not found"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.createOrgFunc != nil { + tt.req.Id = tt.createOrgFunc() + } + + got, err := Client.DeleteOrganization(tt.ctx, tt.req) + if tt.err != nil { + require.Contains(t, err.Error(), tt.err.Error()) + return + } + require.NoError(t, err) + + // check details + assert.NotZero(t, got.GetDetails().GetSequence()) + gotCD := got.GetDetails().GetChangeDate().AsTime() + now := time.Now() + assert.WithinRange(t, gotCD, now.Add(-time.Minute), now.Add(time.Minute)) + assert.NotEmpty(t, got.GetDetails().GetResourceOwner()) + + _, err = Client.GetOrganizationByID(tt.ctx, &org.GetOrganizationByIDRequest{ + Id: tt.req.Id, + }) + require.Contains(t, err.Error(), "Organisation not found") + }) + } +} + func createOrgs(noOfOrgs int) ([]*org.CreateOrganizationResponse, []string, error) { var err error orgs := make([]*org.CreateOrganizationResponse, noOfOrgs) orgsName := make([]string, noOfOrgs) + for i := range noOfOrgs { orgName := gofakeit.Name() orgsName[i] = orgName diff --git a/internal/api/grpc/org/v2beta/org.go b/internal/api/grpc/org/v2beta/org.go index a2ffb35a3d..adccac9427 100644 --- a/internal/api/grpc/org/v2beta/org.go +++ b/internal/api/grpc/org/v2beta/org.go @@ -58,6 +58,16 @@ func (s *Server) ListOrganizations(ctx context.Context, request *v2beta_org.List }, nil } +func (s *Server) DeleteOrganization(ctx context.Context, request *v2beta_org.DeleteOrganizationRequest) (*v2beta_org.DeleteOrganizationResponse, error) { + details, err := s.command.RemoveOrg(ctx, request.Id) + if err != nil { + return nil, err + } + return &v2beta_org.DeleteOrganizationResponse{ + Details: object.DomainToDetailsPb(details), + }, nil +} + func createOrganizationRequestToCommand(request *v2beta_org.CreateOrganizationRequest) (*command.OrgSetup, error) { admins, err := createOrganizationRequestAdminsToCommand(request.GetAdmins()) if err != nil { diff --git a/proto/zitadel/org/v2beta/org_service.proto b/proto/zitadel/org/v2beta/org_service.proto index cd745c6dca..a51bb51b7b 100644 --- a/proto/zitadel/org/v2beta/org_service.proto +++ b/proto/zitadel/org/v2beta/org_service.proto @@ -244,7 +244,7 @@ service OrganizationService { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { tags: "Organizations"; - summary: "Remove Organization"; + summary: "Deletes Organization"; description: "Deletes the organization and all its resources (Users, Projects, Grants to and from the org). Users of this organization will not be able to log in." responses: { key: "200"; @@ -346,45 +346,42 @@ message GetOrganizationByIDResponse { } message ListOrganizationsRequest { - option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - description: "Search query for lists"; - required: ["query"] - }; - }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + description: "Search query for lists"; + required: ["query"] + }; + }; - //list limitations and ordering - // zitadel.v1.ListQuery query = 1; - zitadel.object.v2beta.ListQuery query = 1; - // the field the result is sorted - // zitadel.org.v1.OrgFieldName sorting_column = 2; - zitadel.org.v2beta.OrgFieldName sorting_column = 2; - //criteria the client is looking for - repeated zitadel.org.v2beta.OrgQuery queries = 3; + // list limitations and ordering + zitadel.object.v2beta.ListQuery query = 1; + // the field the result is sorted + zitadel.org.v2beta.OrgFieldName sorting_column = 2; + //criteria the client is looking for + repeated zitadel.org.v2beta.OrgQuery queries = 3; } message ListOrganizationsResponse { - zitadel.object.v2beta.ListDetails details = 1; - // zitadel.org.v2beta.Organization organization = 1; - zitadel.org.v2beta.OrgFieldName sorting_column = 2; - repeated zitadel.org.v2beta.Organization result = 3; + zitadel.object.v2beta.ListDetails details = 1; + zitadel.org.v2beta.OrgFieldName sorting_column = 2; + repeated zitadel.org.v2beta.Organization result = 3; } message DeleteOrganizationRequest { - option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { - json_schema: { - required: ["org_id"] - }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + required: ["id"] }; + }; - string org_id = 1 [ - (validate.rules).string = {min_len: 1, max_len: 200}, - (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { - example: "\"69629023906488334\""; - min_length: 1; - max_length: 200; - } - ]; + string id = 1 [ + (validate.rules).string = {min_len: 1, max_len: 200}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"69629023906488334\""; + min_length: 1; + max_length: 200; + } + ]; } message DeleteOrganizationResponse {