diff --git a/backend/v3/storage/database/dialect/postgres/migration/002_organization_table/up.sql b/backend/v3/storage/database/dialect/postgres/migration/002_organization_table/up.sql index 16024775b7..502734f43c 100644 --- a/backend/v3/storage/database/dialect/postgres/migration/002_organization_table/up.sql +++ b/backend/v3/storage/database/dialect/postgres/migration/002_organization_table/up.sql @@ -8,7 +8,7 @@ CREATE TABLE zitadel.organizations( name TEXT NOT NULL, instance_id TEXT NOT NULL, state zitadel.organization_state NOT NULL, - created_at TIMESTAMP DEFAULT NOW(), - updated_at TIMESTAMP DEFAULT NOW(), - deleted_at TIMESTAMP DEFAULT NULL + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + deleted_at TIMESTAMPTZ DEFAULT NULL ); diff --git a/backend/v3/storage/database/events_testing/instance_test.go b/backend/v3/storage/database/events_testing/instance_test.go index ea0c2f5519..607dc3991d 100644 --- a/backend/v3/storage/database/events_testing/instance_test.go +++ b/backend/v3/storage/database/events_testing/instance_test.go @@ -19,6 +19,7 @@ import ( "github.com/zitadel/zitadel/backend/v3/storage/database/dialect/postgres" "github.com/zitadel/zitadel/backend/v3/storage/database/repository" "github.com/zitadel/zitadel/internal/integration" + mgmt "github.com/zitadel/zitadel/pkg/grpc/management" "github.com/zitadel/zitadel/pkg/grpc/system" ) @@ -30,6 +31,7 @@ var ( Instance *integration.Instance SystemClient system.SystemServiceClient OrgClient org.OrganizationServiceClient + MgmtClient mgmt.ManagementServiceClient ) var pool database.Pool @@ -44,6 +46,7 @@ func TestMain(m *testing.M) { CTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner) SystemClient = integration.SystemClient() OrgClient = Instance.Client.OrgV2 + MgmtClient = Instance.Client.Mgmt var err error dbConfig, err := pgxpool.ParseConfig(ConnString) diff --git a/backend/v3/storage/database/events_testing/organization_test.go b/backend/v3/storage/database/events_testing/organization_test.go index 6b1c75cc76..ee86c26b40 100644 --- a/backend/v3/storage/database/events_testing/organization_test.go +++ b/backend/v3/storage/database/events_testing/organization_test.go @@ -3,7 +3,6 @@ package events_test import ( - "fmt" "testing" "time" @@ -11,147 +10,182 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/zitadel/zitadel/backend/v3/domain" "github.com/zitadel/zitadel/backend/v3/storage/database" "github.com/zitadel/zitadel/backend/v3/storage/database/repository" "github.com/zitadel/zitadel/internal/integration" + "github.com/zitadel/zitadel/pkg/grpc/management" "github.com/zitadel/zitadel/pkg/grpc/org/v2" ) -// const ConnString = "host=localhost port=5432 user=zitadel dbname=zitadel sslmode=disable" - -// var ( -// dbPool *pgxpool.Pool -// CTX context.Context -// Organization *integration.Organization -// SystemClient system.SystemServiceClient -// ) - -// var pool database.Pool - -// func TestMain(m *testing.M) { -// os.Exit(func() int { -// ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute) -// defer cancel() - -// Organization = integration.NewOrganization(ctx) - -// CTX = Organization.WithAuthorization(ctx, integration.UserTypeIAMOwner) -// SystemClient = integration.SystemClient() - -// var err error -// dbPool, err = pgxpool.New(context.Background(), ConnString) -// if err != nil { -// panic(err) -// } - -// pool = postgres.PGxPool(dbPool) - -// return m.Run() -// }()) -// } - func TestServer_TestOrganizationAddReduces(t *testing.T) { + beforeCreate := time.Now() orgName := gofakeit.Name() - // beforeCreate := time.Now() _, err := OrgClient.AddOrganization(CTX, &org.AddOrganizationRequest{ Name: orgName, }) require.NoError(t, err) - // afterCreate := time.Now() + afterCreate := time.Now() orgRepo := repository.OrgRepository(pool) - organization, err := orgRepo.Get(CTX, - orgRepo.NameCondition(database.TextOperationEqual, orgName), - ) - require.NoError(t, err) - fmt.Printf("@@ >>>>>>>>>>>>>>>>>>>>>>>>>>>> organization = %+v\n", organization) retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute) - assert.EventuallyWithT(t, func(ttt *assert.CollectT) { + assert.EventuallyWithT(t, func(t *assert.CollectT) { organization, err := orgRepo.Get(CTX, orgRepo.NameCondition(database.TextOperationEqual, orgName), ) - require.NoError(ttt, err) - fmt.Printf("@@ >>>>>>>>>>>>>>>>>>>>>>>>>>>> organization = %+v\n", organization) - // // event instance.added - // require.Equal(ttt, instanceName, organization.Name) - // // event instance.default.org.set - // require.NotNil(t, organization.DefaultOrgID) - // // event instance.iam.project.set - // require.NotNil(t, organization.IAMProjectID) - // // event instance.iam.console.set - // require.NotNil(t, organization.ConsoleAppID) - // // event instance.default.language.set - // require.NotNil(t, organization.DefaultLanguage) - // // event instance.added - // assert.WithinRange(t, organization.CreatedAt, beforeCreate, afterCreate) - // // event instance.added - // assert.WithinRange(t, organization.UpdatedAt, beforeCreate, afterCreate) - // require.Nil(t, organization.DeletedAt) + require.NoError(t, err) + + activeState := domain.State[0] + + // event org.added + require.NotNil(t, organization.ID) + require.Equal(t, orgName, organization.Name) + require.NotNil(t, organization.InstanceID) + require.Equal(t, activeState, organization.State) + assert.WithinRange(t, organization.CreatedAt, beforeCreate, afterCreate) + assert.WithinRange(t, organization.UpdatedAt, beforeCreate, afterCreate) + require.Nil(t, organization.DeletedAt) }, retryDuration, tick) } -// func TestServer_TestOrganizationUpdateNameReduces(t *testing.T) { -// instanceName := gofakeit.Name() -// res, err := SystemClient.CreateOrganization(CTX, &system.CreateOrganizationRequest{ -// OrganizationName: instanceName, -// Owner: &system.CreateOrganizationRequest_Machine_{ -// Machine: &system.CreateOrganizationRequest_Machine{ -// UserName: "owner", -// Name: "owner", -// PersonalAccessToken: &system.CreateOrganizationRequest_PersonalAccessToken{}, -// }, -// }, -// }) -// require.NoError(t, err) +func TestServer_TestOrganizationChangeReduces(t *testing.T) { + beforeCreate := time.Now() + orgName := gofakeit.Name() -// instanceName += "new" -// _, err = SystemClient.UpdateOrganization(CTX, &system.UpdateOrganizationRequest{ -// OrganizationId: res.OrganizationId, -// OrganizationName: instanceName, -// }) -// require.NoError(t, err) + // 1. create org + _, err := OrgClient.AddOrganization(CTX, &org.AddOrganizationRequest{ + Name: orgName, + }) + require.NoError(t, err) -// instanceRepo := repository.OrganizationRepository(pool) -// retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute) -// assert.EventuallyWithT(t, func(ttt *assert.CollectT) { -// instance, err := instanceRepo.Get(CTX, -// instanceRepo.NameCondition(database.TextOperationEqual, instanceName), -// ) -// require.NoError(ttt, err) -// // event instance.changed -// require.Equal(ttt, instanceName, instance.Name) -// }, retryDuration, tick) -// } + // 2. update org name + orgName = orgName + "_new" + _, err = MgmtClient.UpdateOrg(CTX, &management.UpdateOrgRequest{ + Name: orgName, + }) + require.NoError(t, err) + afterCreate := time.Now() -// func TestServer_TestOrganizationDeleteReduces(t *testing.T) { -// instanceName := gofakeit.Name() -// res, err := SystemClient.CreateOrganization(CTX, &system.CreateOrganizationRequest{ -// OrganizationName: instanceName, -// Owner: &system.CreateOrganizationRequest_Machine_{ -// Machine: &system.CreateOrganizationRequest_Machine{ -// UserName: "owner", -// Name: "owner", -// PersonalAccessToken: &system.CreateOrganizationRequest_PersonalAccessToken{}, -// }, -// }, -// }) -// require.NoError(t, err) + orgRepo := repository.OrgRepository(pool) -// _, err = SystemClient.RemoveOrganization(CTX, &system.RemoveOrganizationRequest{ -// OrganizationId: res.OrganizationId, -// }) -// require.NoError(t, err) + retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute) + assert.EventuallyWithT(t, func(t *assert.CollectT) { + organization, err := orgRepo.Get(CTX, + orgRepo.NameCondition(database.TextOperationEqual, orgName), + ) + require.NoError(t, err) -// instanceRepo := repository.OrganizationRepository(pool) -// retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute) -// assert.EventuallyWithT(t, func(ttt *assert.CollectT) { -// instance, err := instanceRepo.Get(CTX, -// instanceRepo.NameCondition(database.TextOperationEqual, instanceName), -// ) -// // event instance.removed -// require.Nil(t, instance) -// require.NoError(ttt, err) -// }, retryDuration, tick) -// } + // event org.changed + require.Equal(t, orgName, organization.Name) + assert.WithinRange(t, organization.UpdatedAt, beforeCreate, afterCreate) + }, retryDuration, tick) +} + +func TestServer_TestOrganizationDeactivateReduces(t *testing.T) { + beforeCreate := time.Now() + orgName := gofakeit.Name() + + // 1. create org + organization, err := OrgClient.AddOrganization(CTX, &org.AddOrganizationRequest{ + Name: orgName, + }) + require.NoError(t, err) + + // 2. deactivate org name + _ = Instance.DeactivateOrganization(CTX, organization.OrganizationId) + require.NoError(t, err) + afterCreate := time.Now() + + orgRepo := repository.OrgRepository(pool) + + retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute) + assert.EventuallyWithT(t, func(t *assert.CollectT) { + organization, err := orgRepo.Get(CTX, + orgRepo.NameCondition(database.TextOperationEqual, orgName), + ) + require.NoError(t, err) + + deactiveState := domain.State[1] + + // event org.deactivate + require.Equal(t, orgName, organization.Name) + require.Equal(t, deactiveState, organization.State) + assert.WithinRange(t, organization.UpdatedAt, beforeCreate, afterCreate) + }, retryDuration, tick) +} + +func TestServer_TestOrganizationActivateReduces(t *testing.T) { + beforeCreate := time.Now() + orgName := gofakeit.Name() + + // 1. create org + organization, err := OrgClient.AddOrganization(CTX, &org.AddOrganizationRequest{ + Name: orgName, + }) + require.NoError(t, err) + + // 2. deactivate org name + _ = Instance.DeactivateOrganization(CTX, organization.OrganizationId) + require.NoError(t, err) + + // 2. activate org name + _ = Instance.ReactivateOrganization(CTX, organization.OrganizationId) + require.NoError(t, err) + afterCreate := time.Now() + + orgRepo := repository.OrgRepository(pool) + + retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute) + assert.EventuallyWithT(t, func(t *assert.CollectT) { + organization, err := orgRepo.Get(CTX, + orgRepo.NameCondition(database.TextOperationEqual, orgName), + ) + require.NoError(t, err) + + deactiveState := domain.State[1] + + // event org.reactivate + require.Equal(t, orgName, organization.Name) + require.Equal(t, deactiveState, organization.State) + assert.WithinRange(t, organization.UpdatedAt, beforeCreate, afterCreate) + }, retryDuration, tick) +} + +func TestServer_TestOrganizationRemoveReduces(t *testing.T) { + orgName := gofakeit.Name() + + // 1. create org + organization, err := OrgClient.AddOrganization(CTX, &org.AddOrganizationRequest{ + Name: orgName, + }) + require.NoError(t, err) + + // 2. check org retrivable + orgRepo := repository.OrgRepository(pool) + retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute) + assert.EventuallyWithT(t, func(t *assert.CollectT) { + organization, err := orgRepo.Get(CTX, + orgRepo.NameCondition(database.TextOperationEqual, orgName), + ) + require.NoError(t, err) + + require.Equal(t, orgName, organization.Name) + }, retryDuration, tick) + + // 3. delete org + _ = Instance.RemoveOrganization(CTX, organization.OrganizationId) + require.NoError(t, err) + + retryDuration, tick = integration.WaitForAndTickWithMaxDuration(CTX, time.Minute) + assert.EventuallyWithT(t, func(t *assert.CollectT) { + organization, err := orgRepo.Get(CTX, + orgRepo.NameCondition(database.TextOperationEqual, orgName), + ) + require.NoError(t, err) + + // event org.remove + require.Nil(t, organization) + }, retryDuration, tick) +} diff --git a/internal/integration/client.go b/internal/integration/client.go index e82a6bec55..f98dd2b9e1 100644 --- a/internal/integration/client.go +++ b/internal/integration/client.go @@ -277,6 +277,24 @@ func (i *Instance) DeactivateOrganization(ctx context.Context, orgID string) *mg return resp } +func (i *Instance) ReactivateOrganization(ctx context.Context, orgID string) *mgmt.ReactivateOrgResponse { + resp, err := i.Client.Mgmt.ReactivateOrg( + SetOrgID(ctx, orgID), + &mgmt.ReactivateOrgRequest{}, + ) + logging.OnError(err).Fatal("reactivate org") + return resp +} + +func (i *Instance) RemoveOrganization(ctx context.Context, orgID string) *mgmt.RemoveOrgResponse { + resp, err := i.Client.Mgmt.RemoveOrg( + SetOrgID(ctx, orgID), + &mgmt.RemoveOrgRequest{}, + ) + logging.OnError(err).Fatal("reactivate org") + return resp +} + func SetOrgID(ctx context.Context, orgID string) context.Context { md, ok := metadata.FromOutgoingContext(ctx) if !ok { diff --git a/internal/query/projection/org_relational.go b/internal/query/projection/org_relational.go index 81d9ac69ed..dca44e8622 100644 --- a/internal/query/projection/org_relational.go +++ b/internal/query/projection/org_relational.go @@ -138,10 +138,13 @@ func (p *orgRelationalProjection) reduceOrgRelationalDeactivated(event eventstor if !ok { return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-BApK5", "reduce.wrong.event.type %s", org.OrgDeactivatedEventType) } + + // need to convert old state (int) to new state (enum) + state := repoDomain.State[domain.OrgStateInactive-1] return handler.NewUpdateStatement( e, []handler.Column{ - handler.NewCol(State, domain.OrgStateInactive), + handler.NewCol(State, state), handler.NewCol(UpdatedAt, e.CreationDate()), }, []handler.Condition{ @@ -197,6 +200,7 @@ func (p *orgRelationalProjection) reduceOrgRelationalRemoved(event eventstore.Ev return handler.NewUpdateStatement( e, []handler.Column{ + handler.NewCol(UpdatedAt, e.CreationDate()), handler.NewCol(DeletedAt, e.CreationDate()), }, []handler.Condition{