diff --git a/backend/v3/domain/organization.go b/backend/v3/domain/organization.go index b37733ec95..f9cda1bd00 100644 --- a/backend/v3/domain/organization.go +++ b/backend/v3/domain/organization.go @@ -17,11 +17,11 @@ const ( type Organization struct { ID string `json:"id,omitempty" db:"id"` Name string `json:"name,omitempty" db:"name"` - InstanceID string `json:"instance_id,omitempty" db:"instance_id"` + InstanceID string `json:"instanceId,omitempty" db:"instance_id"` State State `json:"state,omitempty" db:"state"` - CreatedAt time.Time `json:"created_at,omitempty" db:"created_at"` - UpdatedAt time.Time `json:"updated_at,omitempty" db:"updated_at"` - DeletedAt *time.Time `json:"deleted_at,omitempty" db:"deleted_at"` + CreatedAt time.Time `json:"createdAt,omitempty" db:"created_at"` + UpdatedAt time.Time `json:"updatedAt,omitempty" db:"updated_at"` + DeletedAt *time.Time `json:"deletedAt,omitempty" db:"deleted_at"` } // organizationColumns define all the columns of the instance table. @@ -48,12 +48,15 @@ type organizationConditions interface { IDCondition(instanceID string) database.Condition // NameCondition returns a filter on the name field. NameCondition(op database.TextOperation, name string) database.Condition + // StateCondition returns a filter on the name field. + StateCondition(state State) database.Condition } // organizationChanges define all the changes for the instance table. type organizationChanges interface { // SetName sets the name column. SetName(name string) database.Change + // SetState sets the name column. SetState(state State) database.Change } 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 d66db79e88..7b11f6be48 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 @@ -12,3 +12,9 @@ CREATE TABLE zitadel.organizations( updated_at TIMESTAMPTZ DEFAULT NOW(), deleted_at TIMESTAMPTZ DEFAULT NULL ); + +CREATE TRIGGER trigger_set_updated_at +BEFORE UPDATE ON zitadel.organizations +FOR EACH ROW +WHEN (OLD.updated_at IS NOT DISTINCT FROM NEW.updated_at) +EXECUTE FUNCTION zitadel.set_updated_at(); diff --git a/backend/v3/storage/database/events_testing/organization_test.go b/backend/v3/storage/database/events_testing/organization_test.go index 41eea54531..e4db4b1f33 100644 --- a/backend/v3/storage/database/events_testing/organization_test.go +++ b/backend/v3/storage/database/events_testing/organization_test.go @@ -28,7 +28,7 @@ func TestServer_TestOrganizationAddReduces(t *testing.T) { require.NoError(t, err) afterCreate := time.Now() - orgRepo := repository.OrgRepository(pool) + orgRepo := repository.OrganizationRepository(pool) retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute) assert.EventuallyWithT(t, func(t *assert.CollectT) { @@ -66,7 +66,7 @@ func TestServer_TestOrganizationChangeReduces(t *testing.T) { require.NoError(t, err) afterCreate := time.Now() - orgRepo := repository.OrgRepository(pool) + orgRepo := repository.OrganizationRepository(pool) retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute) assert.EventuallyWithT(t, func(t *assert.CollectT) { @@ -96,7 +96,7 @@ func TestServer_TestOrganizationDeactivateReduces(t *testing.T) { require.NoError(t, err) afterCreate := time.Now() - orgRepo := repository.OrgRepository(pool) + orgRepo := repository.OrganizationRepository(pool) retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute) assert.EventuallyWithT(t, func(t *assert.CollectT) { @@ -131,7 +131,7 @@ func TestServer_TestOrganizationActivateReduces(t *testing.T) { require.NoError(t, err) afterCreate := time.Now() - orgRepo := repository.OrgRepository(pool) + orgRepo := repository.OrganizationRepository(pool) retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute) assert.EventuallyWithT(t, func(t *assert.CollectT) { @@ -157,7 +157,7 @@ func TestServer_TestOrganizationRemoveReduces(t *testing.T) { require.NoError(t, err) // 2. check org retrivable - orgRepo := repository.OrgRepository(pool) + orgRepo := repository.OrganizationRepository(pool) retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute) assert.EventuallyWithT(t, func(t *assert.CollectT) { organization, err := orgRepo.Get(CTX, diff --git a/backend/v3/storage/database/repository/instance.go b/backend/v3/storage/database/repository/instance.go index 8acfb32e80..42504cbd1d 100644 --- a/backend/v3/storage/database/repository/instance.go +++ b/backend/v3/storage/database/repository/instance.go @@ -96,6 +96,9 @@ func (i *instance) Create(ctx context.Context, instance *domain.Instance) error // Update implements [domain.InstanceRepository]. func (i instance) Update(ctx context.Context, condition database.Condition, changes ...database.Change) (int64, error) { + if changes == nil { + return 0, nil + } var builder database.StatementBuilder builder.WriteString(`UPDATE zitadel.instances SET `) diff --git a/backend/v3/storage/database/repository/instance_test.go b/backend/v3/storage/database/repository/instance_test.go index a7f4c9add1..2f01812b12 100644 --- a/backend/v3/storage/database/repository/instance_test.go +++ b/backend/v3/storage/database/repository/instance_test.go @@ -340,7 +340,6 @@ func TestGetInstance(t *testing.T) { require.Nil(t, instance, returnedInstance) return } - require.NoError(t, err) require.Equal(t, returnedInstance.ID, instance.ID) require.Equal(t, returnedInstance.Name, instance.Name) diff --git a/backend/v3/storage/database/repository/org.go b/backend/v3/storage/database/repository/org.go index d0832246a0..158fb0037e 100644 --- a/backend/v3/storage/database/repository/org.go +++ b/backend/v3/storage/database/repository/org.go @@ -20,7 +20,7 @@ type org struct { repository } -func OrgRepository(client database.QueryExecutor) domain.OrganizationRepository { +func OrganizationRepository(client database.QueryExecutor) domain.OrganizationRepository { return &org{ repository: repository{ client: client, @@ -110,10 +110,16 @@ func (o *org) Create(ctx context.Context, organization *domain.Organization) err // Update implements [domain.OrganizationRepository]. func (o org) Update(ctx context.Context, condition database.Condition, changes ...database.Change) (int64, error) { + if changes == nil { + return 0, nil + } builder := database.StatementBuilder{} builder.WriteString(`UPDATE zitadel.organizations SET `) + + // don't update deleted instances + conditions := []database.Condition{condition, database.IsNull(o.DeletedAtColumn())} database.Changes(changes).Write(&builder) - o.writeCondition(&builder, condition) + o.writeCondition(&builder, database.And(conditions...)) stmt := builder.String() @@ -163,6 +169,11 @@ func (o org) NameCondition(op database.TextOperation, name string) database.Cond return database.NewTextCondition(o.NameColumn(), op, name) } +// StateCondition implements [domain.organizationConditions]. +func (o org) StateCondition(state domain.State) database.Condition { + return database.NewTextCondition(o.StateColumn(), database.TextOperationEqual, state) +} + // ------------------------------------------------------------- // columns // ------------------------------------------------------------- diff --git a/backend/v3/storage/database/repository/org_test.go b/backend/v3/storage/database/repository/org_test.go index 11069db03b..d0d24561d2 100644 --- a/backend/v3/storage/database/repository/org_test.go +++ b/backend/v3/storage/database/repository/org_test.go @@ -8,6 +8,7 @@ import ( "github.com/brianvoe/gofakeit/v6" "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" @@ -17,7 +18,7 @@ import ( func TestCreateOrganization(t *testing.T) { tests := []struct { name string - testFunc func() *domain.Organization + testFunc func(ctx context.Context, t *testing.T) *domain.Organization organization domain.Organization err error }{ @@ -54,13 +55,12 @@ func TestCreateOrganization(t *testing.T) { }, { name: "adding same organization twice", - testFunc: func() *domain.Organization { - organizationRepo := repository.OrgRepository(pool) + testFunc: func(ctx context.Context, t *testing.T) *domain.Organization { + organizationRepo := repository.OrganizationRepository(pool) organizationId := gofakeit.Name() organizationName := gofakeit.Name() instanceId := gofakeit.Name() - ctx := context.Background() inst := domain.Organization{ ID: organizationId, Name: organizationName, @@ -112,11 +112,11 @@ func TestCreateOrganization(t *testing.T) { var organization *domain.Organization if tt.testFunc != nil { - organization = tt.testFunc() + organization = tt.testFunc(ctx, t) } else { organization = &tt.organization } - organizationRepo := repository.OrgRepository(pool) + organizationRepo := repository.OrganizationRepository(pool) // create organization beforeCreate := time.Now() @@ -145,21 +145,20 @@ func TestCreateOrganization(t *testing.T) { } func TestUpdateOrganization(t *testing.T) { - organizationRepo := repository.OrgRepository(pool) + organizationRepo := repository.OrganizationRepository(pool) tests := []struct { name string - testFunc func() *domain.Organization + testFunc func(ctx context.Context, t *testing.T) *domain.Organization update []database.Change rowsAffected int64 }{ { name: "happy path update name", - testFunc: func() *domain.Organization { + testFunc: func(ctx context.Context, t *testing.T) *domain.Organization { organizationId := gofakeit.Name() organizationName := gofakeit.Name() instanceId := gofakeit.Name() - ctx := context.Background() org := domain.Organization{ ID: organizationId, Name: organizationName, @@ -179,13 +178,40 @@ func TestUpdateOrganization(t *testing.T) { rowsAffected: 1, }, { - name: "happy path change state", - testFunc: func() *domain.Organization { + name: "update deleted organization", + testFunc: func(ctx context.Context, t *testing.T) *domain.Organization { + organizationId := gofakeit.Name() + organizationName := gofakeit.Name() + instanceId := gofakeit.Name() + + org := domain.Organization{ + ID: organizationId, + Name: organizationName, + InstanceID: instanceId, + State: domain.Active, + } + + // create organization + err := organizationRepo.Create(ctx, &org) + assert.NoError(t, err) + + // delete instance + err = organizationRepo.Delete(ctx, + organizationRepo.IDCondition(org.ID), + ) + require.NoError(t, err) + + return &org + }, + rowsAffected: 0, + }, + { + name: "happy path change state", + testFunc: func(ctx context.Context, t *testing.T) *domain.Organization { organizationId := gofakeit.Name() organizationName := gofakeit.Name() instanceId := gofakeit.Name() - ctx := context.Background() org := domain.Organization{ ID: organizationId, Name: organizationName, @@ -204,27 +230,28 @@ func TestUpdateOrganization(t *testing.T) { update: []database.Change{organizationRepo.SetState(domain.Inactive)}, rowsAffected: 1, }, - // { - // name: "update non existent organization", - // testFunc: func() *domain.Organization { - // organizationId := gofakeit.Name() + { + name: "update non existent organization", + testFunc: func(ctx context.Context, t *testing.T) *domain.Organization { + organizationId := gofakeit.Name() - // org := domain.Organization{ - // ID: organizationId, - // } - // return &org - // }, - // rowsAffected: 0, - // }, + org := domain.Organization{ + ID: organizationId, + } + return &org + }, + update: []database.Change{organizationRepo.SetName("new_name")}, + rowsAffected: 0, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { beforeUpdate := time.Now() ctx := context.Background() - organizationRepo := repository.OrgRepository(pool) + organizationRepo := repository.OrganizationRepository(pool) - createdOrg := tt.testFunc() + createdOrg := tt.testFunc(ctx, t) // update org rowsAffected, err := organizationRepo.Update(ctx, @@ -255,489 +282,438 @@ func TestUpdateOrganization(t *testing.T) { } } -// func TestGetOrganization(t *testing.T) { -// organizationRepo := repository.OrgRepository(pool) -// type test struct { -// name string -// testFunc func() *domain.Organization -// conditionClauses []database.Condition -// noOrganizationReturned bool -// } +func TestGetOrganization(t *testing.T) { + orgRepo := repository.OrganizationRepository(pool) + type test struct { + name string + testFunc func(ctx context.Context, t *testing.T) *domain.Organization + conditionClauses []database.Condition + } -// tests := []test{ -// func() test { -// organizationId := gofakeit.Name() -// return test{ -// name: "happy path get using id", -// testFunc: func() *domain.Organization { -// organizationName := gofakeit.Name() + tests := []test{ + func() test { + organizationId := gofakeit.Name() + return test{ + name: "happy path get using id", + testFunc: func(ctx context.Context, t *testing.T) *domain.Organization { + organizationName := gofakeit.Name() + instanceId := gofakeit.Name() -// ctx := context.Background() -// inst := domain.Organization{ -// ID: organizationId, -// Name: organizationName, -// DefaultOrgID: "defaultOrgId", -// IAMProjectID: "iamProject", -// ConsoleClientID: "consoleCLient", -// ConsoleAppID: "consoleApp", -// DefaultLanguage: "defaultLanguage", -// } + org := domain.Organization{ + ID: organizationId, + Name: organizationName, + InstanceID: instanceId, + State: domain.Active, + } -// // create organization -// err := organizationRepo.Create(ctx, &inst) -// assert.NoError(t, err) -// return &inst -// }, -// conditionClauses: []database.Condition{organizationRepo.IDCondition(organizationId)}, -// } -// }(), -// func() test { -// organizationName := gofakeit.Name() -// return test{ -// name: "happy path get using name", -// testFunc: func() *domain.Organization { -// organizationId := gofakeit.Name() + // create organization + err := orgRepo.Create(ctx, &org) + assert.NoError(t, err) -// ctx := context.Background() -// inst := domain.Organization{ -// ID: organizationId, -// Name: organizationName, -// DefaultOrgID: "defaultOrgId", -// IAMProjectID: "iamProject", -// ConsoleClientID: "consoleCLient", -// ConsoleAppID: "consoleApp", -// DefaultLanguage: "defaultLanguage", -// } + return &org + }, + conditionClauses: []database.Condition{orgRepo.IDCondition(organizationId)}, + } + }(), + func() test { + organizationName := gofakeit.Name() + return test{ + name: "happy path get using name", + testFunc: func(ctx context.Context, t *testing.T) *domain.Organization { + organizationId := gofakeit.Name() + instanceId := gofakeit.Name() -// // create organization -// err := organizationRepo.Create(ctx, &inst) -// assert.NoError(t, err) -// return &inst -// }, -// conditionClauses: []database.Condition{organizationRepo.NameCondition(database.TextOperationEqual, organizationName)}, -// } -// }(), -// { -// name: "get non existent organization", -// testFunc: func() *domain.Organization { -// organizationId := gofakeit.Name() + org := domain.Organization{ + ID: organizationId, + Name: organizationName, + InstanceID: instanceId, + State: domain.Active, + } -// inst := domain.Organization{ -// ID: organizationId, -// } -// return &inst -// }, -// conditionClauses: []database.Condition{organizationRepo.NameCondition(database.TextOperationEqual, "non-existent-organization-name")}, -// noOrganizationReturned: true, -// }, -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// ctx := context.Background() -// organizationRepo := repository.OrgRepository(pool) + // create organization + err := orgRepo.Create(ctx, &org) + assert.NoError(t, err) -// var organization *domain.Organization -// if tt.testFunc != nil { -// organization = tt.testFunc() -// } + return &org + }, + conditionClauses: []database.Condition{orgRepo.NameCondition(database.TextOperationEqual, organizationName)}, + } + }(), + { + name: "get non existent organization", + testFunc: func(ctx context.Context, t *testing.T) *domain.Organization { + instanceId := gofakeit.Name() -// // check organization values -// returnedOrganization, err := organizationRepo.Get(ctx, -// tt.conditionClauses..., -// ) -// assert.NoError(t, err) -// if tt.noOrganizationReturned { -// assert.Nil(t, returnedOrganization) -// return -// } + _ = domain.Instance{ + ID: instanceId, + } + return nil + }, + conditionClauses: []database.Condition{orgRepo.NameCondition(database.TextOperationEqual, "non-existent-instance-name")}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + orgRepo := repository.OrganizationRepository(pool) -// assert.Equal(t, returnedOrganization.ID, organization.ID) -// assert.Equal(t, returnedOrganization.Name, organization.Name) -// assert.Equal(t, returnedOrganization.DefaultOrgID, organization.DefaultOrgID) -// assert.Equal(t, returnedOrganization.IAMProjectID, organization.IAMProjectID) -// assert.Equal(t, returnedOrganization.ConsoleClientID, organization.ConsoleClientID) -// assert.Equal(t, returnedOrganization.ConsoleAppID, organization.ConsoleAppID) -// assert.Equal(t, returnedOrganization.DefaultLanguage, organization.DefaultLanguage) -// assert.NoError(t, err) -// }) -// } -// } + var org *domain.Organization + if tt.testFunc != nil { + org = tt.testFunc(ctx, t) + } -// func TestListOrganization(t *testing.T) { -// type test struct { -// name string -// testFunc func() ([]*domain.Organization, database.PoolTest, func()) -// conditionClauses []database.Condition -// noOrganizationReturned bool -// } -// tests := []test{ -// { -// name: "happy path single organization no filter", -// testFunc: func() ([]*domain.Organization, database.PoolTest, func()) { -// ctx := context.Background() -// // create new db to make sure no organizations exist -// pool, stop, err := newEmbeededDB() -// assert.NoError(t, err) + // get org values + returnedOrg, err := orgRepo.Get(ctx, + tt.conditionClauses..., + ) + require.NoError(t, err) + if org == nil { + require.Nil(t, org, returnedOrg) + return + } -// organizationRepo := repository.OrgRepository(pool) -// noOfOrganizations := 1 -// organizations := make([]*domain.Organization, noOfOrganizations) -// for i := range noOfOrganizations { + require.Equal(t, returnedOrg.ID, org.ID) + require.Equal(t, returnedOrg.Name, org.Name) + require.Equal(t, returnedOrg.InstanceID, org.InstanceID) + require.Equal(t, returnedOrg.State, org.State) + }) + } +} -// organizationId := gofakeit.Name() -// organizationName := gofakeit.Name() +func TestListOrganization(t *testing.T) { + ctx := context.Background() + pool, stop, err := newEmbeddedDB(ctx) + require.NoError(t, err) + defer stop() + organizationRepo := repository.OrganizationRepository(pool) -// inst := domain.Organization{ -// ID: organizationId, -// Name: organizationName, -// DefaultOrgID: "defaultOrgId", -// IAMProjectID: "iamProject", -// ConsoleClientID: "consoleCLient", -// ConsoleAppID: "consoleApp", -// DefaultLanguage: "defaultLanguage", -// } + type test struct { + name string + testFunc func(ctx context.Context, t *testing.T) []*domain.Organization + conditionClauses []database.Condition + noOrganizationReturned bool + } + tests := []test{ + { + name: "happy path single organization no filter", + testFunc: func(ctx context.Context, t *testing.T) []*domain.Organization { + noOfOrganizations := 1 + organizations := make([]*domain.Organization, noOfOrganizations) + instanceId := gofakeit.Name() + for i := range noOfOrganizations { -// // create organization -// err := organizationRepo.Create(ctx, &inst) -// assert.NoError(t, err) + inst := domain.Organization{ + ID: gofakeit.Name(), + Name: gofakeit.Name(), + InstanceID: instanceId, + State: domain.Active, + } -// organizations[i] = &inst -// } + // create organization + err := organizationRepo.Create(ctx, &inst) + require.NoError(t, err) -// return organizations, pool, stop -// }, -// }, -// { -// name: "happy path multiple organization no filter", -// testFunc: func() ([]*domain.Organization, database.PoolTest, func()) { -// ctx := context.Background() -// // create new db to make sure no organizations exist -// pool, stop, err := newEmbeededDB() -// assert.NoError(t, err) + organizations[i] = &inst + } -// organizationRepo := repository.OrgRepository(pool) -// noOfOrganizations := 5 -// organizations := make([]*domain.Organization, noOfOrganizations) -// for i := range noOfOrganizations { + return organizations + }, + }, + { + name: "happy path multiple organization no filter", + testFunc: func(ctx context.Context, t *testing.T) []*domain.Organization { + noOfOrganizations := 5 + organizations := make([]*domain.Organization, noOfOrganizations) + instanceId := gofakeit.Name() + for i := range noOfOrganizations { -// organizationId := gofakeit.Name() -// organizationName := gofakeit.Name() + inst := domain.Organization{ + ID: gofakeit.Name(), + Name: gofakeit.Name(), + InstanceID: instanceId, + State: domain.Active, + } -// inst := domain.Organization{ -// ID: organizationId, -// Name: organizationName, -// DefaultOrgID: "defaultOrgId", -// IAMProjectID: "iamProject", -// ConsoleClientID: "consoleCLient", -// ConsoleAppID: "consoleApp", -// DefaultLanguage: "defaultLanguage", -// } + // create organization + err := organizationRepo.Create(ctx, &inst) + require.NoError(t, err) -// // create organization -// err := organizationRepo.Create(ctx, &inst) -// assert.NoError(t, err) + organizations[i] = &inst + } -// organizations[i] = &inst -// } + return organizations + }, + }, + func() test { + organizationId := gofakeit.Name() + return test{ + name: "organization filter on id", + testFunc: func(ctx context.Context, t *testing.T) []*domain.Organization { + noOfOrganizations := 1 + organizations := make([]*domain.Organization, noOfOrganizations) + instanceId := gofakeit.Name() + for i := range noOfOrganizations { -// return organizations, pool, stop -// }, -// }, -// func() test { -// organizationRepo := repository.OrgRepository(pool) -// organizationId := gofakeit.Name() -// return test{ -// name: "organization filter on id", -// testFunc: func() ([]*domain.Organization, database.PoolTest, func()) { -// ctx := context.Background() + inst := domain.Organization{ + ID: organizationId, + Name: gofakeit.Name(), + InstanceID: instanceId, + State: domain.Active, + } -// noOfOrganizations := 1 -// organizations := make([]*domain.Organization, noOfOrganizations) -// for i := range noOfOrganizations { + // create organization + err := organizationRepo.Create(ctx, &inst) + require.NoError(t, err) -// organizationName := gofakeit.Name() + organizations[i] = &inst + } -// inst := domain.Organization{ -// ID: organizationId, -// Name: organizationName, -// DefaultOrgID: "defaultOrgId", -// IAMProjectID: "iamProject", -// ConsoleClientID: "consoleCLient", -// ConsoleAppID: "consoleApp", -// DefaultLanguage: "defaultLanguage", -// } + return organizations + }, + conditionClauses: []database.Condition{organizationRepo.IDCondition(organizationId)}, + } + }(), + { + name: "multiple organization filter on state", + testFunc: func(ctx context.Context, t *testing.T) []*domain.Organization { + noOfOrganizations := 5 + organizations := make([]*domain.Organization, noOfOrganizations) + instanceId := gofakeit.Name() + for i := range noOfOrganizations { -// // create organization -// err := organizationRepo.Create(ctx, &inst) -// assert.NoError(t, err) + inst := domain.Organization{ + ID: gofakeit.Name(), + Name: gofakeit.Name(), + InstanceID: instanceId, + State: domain.Inactive, + } -// organizations[i] = &inst -// } + // create organization + err := organizationRepo.Create(ctx, &inst) + require.NoError(t, err) -// return organizations, nil, nil -// }, -// conditionClauses: []database.Condition{organizationRepo.IDCondition(organizationId)}, -// } -// }(), -// func() test { -// organizationRepo := repository.OrgRepository(pool) -// organizationName := gofakeit.Name() -// return test{ -// name: "multiple organization filter on name", -// testFunc: func() ([]*domain.Organization, database.PoolTest, func()) { -// ctx := context.Background() + organizations[i] = &inst + } -// noOfOrganizations := 5 -// organizations := make([]*domain.Organization, noOfOrganizations) -// for i := range noOfOrganizations { + return organizations + }, + conditionClauses: []database.Condition{organizationRepo.StateCondition(domain.Inactive)}, + }, + func() test { + organizationName := gofakeit.Name() + return test{ + name: "multiple organization filter on name", + testFunc: func(ctx context.Context, t *testing.T) []*domain.Organization { + noOfOrganizations := 5 + organizations := make([]*domain.Organization, noOfOrganizations) + instanceId := gofakeit.Name() + for i := range noOfOrganizations { -// organizationId := gofakeit.Name() + inst := domain.Organization{ + ID: gofakeit.Name(), + Name: organizationName, + InstanceID: instanceId, + State: domain.Active, + } -// inst := domain.Organization{ -// ID: organizationId, -// Name: organizationName, -// DefaultOrgID: "defaultOrgId", -// IAMProjectID: "iamProject", -// ConsoleClientID: "consoleCLient", -// ConsoleAppID: "consoleApp", -// DefaultLanguage: "defaultLanguage", -// } + // create organization + err := organizationRepo.Create(ctx, &inst) + require.NoError(t, err) -// // create organization -// err := organizationRepo.Create(ctx, &inst) -// assert.NoError(t, err) + organizations[i] = &inst + } -// organizations[i] = &inst -// } + return organizations + }, + conditionClauses: []database.Condition{organizationRepo.NameCondition(database.TextOperationEqual, organizationName)}, + } + }(), + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Cleanup(func() { + _, err := pool.Exec(ctx, "DELETE FROM zitadel.organizations") + require.NoError(t, err) + }) -// return organizations, nil, nil -// }, -// conditionClauses: []database.Condition{organizationRepo.NameCondition(database.TextOperationEqual, organizationName)}, -// } -// }(), -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// ctx := context.Background() + organizations := tt.testFunc(ctx, t) -// var organizations []*domain.Organization + // check organization values + returnedOrgs, err := organizationRepo.List(ctx, + tt.conditionClauses..., + ) + require.NoError(t, err) + if tt.noOrganizationReturned { + require.Nil(t, returnedOrgs) + return + } -// pool := pool -// if tt.testFunc != nil { -// var stop func() -// var pool_ database.PoolTest -// organizations, pool_, stop = tt.testFunc() -// if pool_ != nil { -// pool = pool_ -// defer stop() -// } -// } -// organizationRepo := repository.OrgRepository(pool) + require.Equal(t, len(organizations), len(returnedOrgs)) + for i, org := range organizations { + require.Equal(t, returnedOrgs[i].ID, org.ID) + require.Equal(t, returnedOrgs[i].Name, org.Name) + require.Equal(t, returnedOrgs[i].InstanceID, org.InstanceID) + require.Equal(t, returnedOrgs[i].State, org.State) + } + }) + } +} -// // check organization values -// returnedOrganizations, err := organizationRepo.List(ctx, -// tt.conditionClauses..., -// ) -// assert.NoError(t, err) -// if tt.noOrganizationReturned { -// assert.Nil(t, returnedOrganizations) -// return -// } +func TestDeleteOrganization(t *testing.T) { + type test struct { + name string + testFunc func(ctx context.Context, t *testing.T) + conditionClauses database.Condition + } + tests := []test{ + func() test { + organizationRepo := repository.OrganizationRepository(pool) + organizationId := gofakeit.Name() + instanceId := gofakeit.Name() + return test{ + name: "happy path delete single organization filter id", + testFunc: func(ctx context.Context, t *testing.T) { + noOfOrganizations := 1 + organizations := make([]*domain.Organization, noOfOrganizations) + for i := range noOfOrganizations { -// assert.Equal(t, len(organizations), len(returnedOrganizations)) -// for i, organization := range organizations { -// assert.Equal(t, returnedOrganizations[i].ID, organization.ID) -// assert.Equal(t, returnedOrganizations[i].Name, organization.Name) -// assert.Equal(t, returnedOrganizations[i].DefaultOrgID, organization.DefaultOrgID) -// assert.Equal(t, returnedOrganizations[i].IAMProjectID, organization.IAMProjectID) -// assert.Equal(t, returnedOrganizations[i].ConsoleClientID, organization.ConsoleClientID) -// assert.Equal(t, returnedOrganizations[i].ConsoleAppID, organization.ConsoleAppID) -// assert.Equal(t, returnedOrganizations[i].DefaultLanguage, organization.DefaultLanguage) -// assert.NoError(t, err) -// } -// }) -// } -// } + inst := domain.Organization{ + ID: organizationId, + Name: gofakeit.Name(), + InstanceID: instanceId, + State: domain.Active, + } -// func TestDeleteOrganization(t *testing.T) { -// type test struct { -// name string -// testFunc func() -// conditionClauses database.Condition -// } -// tests := []test{ -// func() test { -// organizationRepo := repository.OrgRepository(pool) -// organizationId := gofakeit.Name() -// return test{ -// name: "happy path delete single organization filter id", -// testFunc: func() { -// ctx := context.Background() + // create organization + err := organizationRepo.Create(ctx, &inst) + require.NoError(t, err) -// noOfOrganizations := 1 -// organizations := make([]*domain.Organization, noOfOrganizations) -// for i := range noOfOrganizations { + organizations[i] = &inst + } + }, + conditionClauses: organizationRepo.IDCondition(organizationId), + } + }(), + func() test { + organizationRepo := repository.OrganizationRepository(pool) + organizationName := gofakeit.Name() + instanceId := gofakeit.Name() + return test{ + name: "happy path delete single organization filter name", + testFunc: func(ctx context.Context, t *testing.T) { + noOfOrganizations := 1 + organizations := make([]*domain.Organization, noOfOrganizations) + for i := range noOfOrganizations { -// organizationName := gofakeit.Name() + inst := domain.Organization{ + ID: gofakeit.Name(), + Name: organizationName, + InstanceID: instanceId, + State: domain.Active, + } -// inst := domain.Organization{ -// ID: organizationId, -// Name: organizationName, -// DefaultOrgID: "defaultOrgId", -// IAMProjectID: "iamProject", -// ConsoleClientID: "consoleCLient", -// ConsoleAppID: "consoleApp", -// DefaultLanguage: "defaultLanguage", -// } + // create organization + err := organizationRepo.Create(ctx, &inst) + require.NoError(t, err) -// // create organization -// err := organizationRepo.Create(ctx, &inst) -// assert.NoError(t, err) + organizations[i] = &inst + } + }, + conditionClauses: organizationRepo.NameCondition(database.TextOperationEqual, organizationName), + } + }(), + func() test { + organizationRepo := repository.OrganizationRepository(pool) + non_existent_organization_name := gofakeit.Name() + return test{ + name: "delete non existent organization", + conditionClauses: organizationRepo.NameCondition(database.TextOperationEqual, non_existent_organization_name), + } + }(), + func() test { + organizationRepo := repository.OrganizationRepository(pool) + organizationName := gofakeit.Name() + instanceId := gofakeit.Name() + return test{ + name: "multiple organization filter on name", + testFunc: func(ctx context.Context, t *testing.T) { + noOfOrganizations := 5 + organizations := make([]*domain.Organization, noOfOrganizations) + for i := range noOfOrganizations { -// organizations[i] = &inst -// } -// }, -// conditionClauses: organizationRepo.IDCondition(organizationId), -// } -// }(), -// func() test { -// organizationRepo := repository.OrgRepository(pool) -// organizationName := gofakeit.Name() -// return test{ -// name: "happy path delete single organization filter name", -// testFunc: func() { -// ctx := context.Background() + inst := domain.Organization{ + ID: gofakeit.Name(), + Name: organizationName, + InstanceID: instanceId, + State: domain.Active, + } -// noOfOrganizations := 1 -// organizations := make([]*domain.Organization, noOfOrganizations) -// for i := range noOfOrganizations { + // create organization + err := organizationRepo.Create(ctx, &inst) + require.NoError(t, err) -// organizationId := gofakeit.Name() + organizations[i] = &inst + } + }, + conditionClauses: organizationRepo.NameCondition(database.TextOperationEqual, organizationName), + } + }(), + func() test { + organizationRepo := repository.OrganizationRepository(pool) + organizationName := gofakeit.Name() + instanceId := gofakeit.Name() + return test{ + name: "deleted already deleted organization", + testFunc: func(ctx context.Context, t *testing.T) { + noOfOrganizations := 1 + organizations := make([]*domain.Organization, noOfOrganizations) + for i := range noOfOrganizations { -// inst := domain.Organization{ -// ID: organizationId, -// Name: organizationName, -// DefaultOrgID: "defaultOrgId", -// IAMProjectID: "iamProject", -// ConsoleClientID: "consoleCLient", -// ConsoleAppID: "consoleApp", -// DefaultLanguage: "defaultLanguage", -// } + inst := domain.Organization{ + ID: gofakeit.Name(), + Name: organizationName, + InstanceID: instanceId, + State: domain.Active, + } -// // create organization -// err := organizationRepo.Create(ctx, &inst) -// assert.NoError(t, err) + // create organization + err := organizationRepo.Create(ctx, &inst) + require.NoError(t, err) -// organizations[i] = &inst -// } -// }, -// conditionClauses: organizationRepo.NameCondition(database.TextOperationEqual, organizationName), -// } -// }(), -// func() test { -// organizationRepo := repository.OrgRepository(pool) -// non_existent_organization_name := gofakeit.Name() -// return test{ -// name: "delete non existent organization", -// conditionClauses: organizationRepo.NameCondition(database.TextOperationEqual, non_existent_organization_name), -// } -// }(), -// func() test { -// organizationRepo := repository.OrgRepository(pool) -// organizationName := gofakeit.Name() -// return test{ -// name: "multiple organization filter on name", -// testFunc: func() { -// ctx := context.Background() + organizations[i] = &inst + } -// noOfOrganizations := 5 -// organizations := make([]*domain.Organization, noOfOrganizations) -// for i := range noOfOrganizations { + // delete organization + err := organizationRepo.Delete(ctx, + organizationRepo.NameCondition(database.TextOperationEqual, organizationName), + ) + require.NoError(t, err) + }, + conditionClauses: organizationRepo.NameCondition(database.TextOperationEqual, organizationName), + } + }(), + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + organizationRepo := repository.OrganizationRepository(pool) -// organizationId := gofakeit.Name() + if tt.testFunc != nil { + tt.testFunc(ctx, t) + } -// inst := domain.Organization{ -// ID: organizationId, -// Name: organizationName, -// DefaultOrgID: "defaultOrgId", -// IAMProjectID: "iamProject", -// ConsoleClientID: "consoleCLient", -// ConsoleAppID: "consoleApp", -// DefaultLanguage: "defaultLanguage", -// } + // delete organization + err := organizationRepo.Delete(ctx, + tt.conditionClauses, + ) + require.NoError(t, err) -// // create organization -// err := organizationRepo.Create(ctx, &inst) -// assert.NoError(t, err) - -// organizations[i] = &inst -// } -// }, -// conditionClauses: organizationRepo.NameCondition(database.TextOperationEqual, organizationName), -// } -// }(), -// func() test { -// organizationRepo := repository.OrgRepository(pool) -// organizationName := gofakeit.Name() -// return test{ -// name: "deleted already deleted organization", -// testFunc: func() { -// ctx := context.Background() - -// noOfOrganizations := 1 -// organizations := make([]*domain.Organization, noOfOrganizations) -// for i := range noOfOrganizations { - -// organizationId := gofakeit.Name() - -// inst := domain.Organization{ -// ID: organizationId, -// Name: organizationName, -// DefaultOrgID: "defaultOrgId", -// IAMProjectID: "iamProject", -// ConsoleClientID: "consoleCLient", -// ConsoleAppID: "consoleApp", -// DefaultLanguage: "defaultLanguage", -// } - -// // create organization -// err := organizationRepo.Create(ctx, &inst) -// assert.NoError(t, err) - -// organizations[i] = &inst -// } - -// // delete organization -// err := organizationRepo.Delete(ctx, -// organizationRepo.NameCondition(database.TextOperationEqual, organizationName), -// ) -// assert.NoError(t, err) -// }, -// conditionClauses: organizationRepo.NameCondition(database.TextOperationEqual, organizationName), -// } -// }(), -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// ctx := context.Background() -// organizationRepo := repository.OrgRepository(pool) - -// if tt.testFunc != nil { -// tt.testFunc() -// } - -// // delete organization -// err := organizationRepo.Delete(ctx, -// tt.conditionClauses, -// ) -// assert.NoError(t, err) - -// // check organization was deleted -// organization, err := organizationRepo.Get(ctx, -// tt.conditionClauses, -// ) -// assert.NoError(t, err) -// assert.Nil(t, organization) -// }) -// } -// } + // check organization was deleted + organization, err := organizationRepo.Get(ctx, + tt.conditionClauses, + ) + require.NoError(t, err) + require.Nil(t, organization) + }) + } +} diff --git a/go.mod b/go.mod index 221b55b204..a5917e1f72 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( github.com/fatih/color v1.18.0 github.com/fergusstrange/embedded-postgres v1.30.0 github.com/gabriel-vasile/mimetype v1.4.9 + github.com/georgysavva/scany/v2 v2.1.4 github.com/go-chi/chi/v5 v5.2.1 github.com/go-jose/go-jose/v4 v4.1.0 github.com/go-ldap/ldap/v3 v3.4.11 @@ -125,7 +126,6 @@ require ( github.com/crewjam/httperr v0.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect - github.com/georgysavva/scany/v2 v2.1.4 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect diff --git a/go.sum b/go.sum index 3afde66193..0b4cbef44e 100644 --- a/go.sum +++ b/go.sum @@ -233,6 +233,8 @@ github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= +github.com/georgysavva/scany/v2 v2.1.4 h1:nrzHEJ4oQVRoiKmocRqA1IyGOmM/GQOEsg9UjMR5Ip4= +github.com/georgysavva/scany/v2 v2.1.4/go.mod h1:fqp9yHZzM/PFVa3/rYEC57VmDx+KDch0LoqrJzkvtos= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= @@ -308,8 +310,9 @@ github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXe github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= @@ -764,6 +767,8 @@ github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3 github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -1122,4 +1127,3 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= - diff --git a/internal/query/projection/org_relational.go b/internal/query/projection/org_relational.go index 49faef5577..ce8393ca3a 100644 --- a/internal/query/projection/org_relational.go +++ b/internal/query/projection/org_relational.go @@ -6,7 +6,6 @@ import ( repoDomain "github.com/zitadel/zitadel/backend/v3/domain" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" - old_handler "github.com/zitadel/zitadel/internal/eventstore/handler" "github.com/zitadel/zitadel/internal/eventstore/handler/v2" "github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/org" @@ -27,25 +26,6 @@ func newOrgRelationalProjection(ctx context.Context, config handler.Config) *han return handler.NewHandler(ctx, &config, new(orgRelationalProjection)) } -// Init implements [handler.initializer] -func (p *orgRelationalProjection) Init() *old_handler.Check { - return handler.NewTableCheck( - handler.NewTable([]*handler.InitColumn{ - handler.NewColumn(OrgColumnID, handler.ColumnTypeText), - handler.NewColumn(OrgColumnName, handler.ColumnTypeText), - handler.NewColumn(OrgColumnInstanceID, handler.ColumnTypeText), - handler.NewColumn(State, handler.ColumnTypeEnum), - handler.NewColumn(CreatedAt, handler.ColumnTypeTimestamp), - handler.NewColumn(UpdatedAt, handler.ColumnTypeTimestamp), - handler.NewColumn(DeletedAt, handler.ColumnTypeTimestamp), - }, - handler.NewPrimaryKey(OrgColumnInstanceID, OrgColumnID), - // handler.WithIndex(handler.NewIndex("domain", []string{OrgColumnDomain})), - handler.WithIndex(handler.NewIndex("name", []string{OrgColumnName})), - ), - ) -} - func (p *orgRelationalProjection) Reducers() []handler.AggregateReducer { return []handler.AggregateReducer{ {