fixup! fixup! fixup! Merge branch 'clean-transactional-propsal' into instance_table

This commit is contained in:
Iraq Jaber
2025-06-16 17:37:38 +02:00
parent 3378a98a49
commit 939e12bc24
4 changed files with 106 additions and 88 deletions

View File

@@ -11,14 +11,14 @@ import (
type Instance struct { type Instance struct {
ID string `json:"id,omitempty" db:"id"` ID string `json:"id,omitempty" db:"id"`
Name string `json:"name,omitempty" db:"name"` Name string `json:"name,omitempty" db:"name"`
DefaultOrgID string `json:"default_org_id,omitempty" db:"default_org_id"` DefaultOrgID string `json:"defaultOrgId,omitempty" db:"default_org_id"`
IAMProjectID string `json:"iam_project_id,omitempty" db:"iam_project_id"` IAMProjectID string `json:"iamProjectId,omitempty" db:"iam_project_id"`
ConsoleClientID string `json:"console_client_id,omitempty" db:"console_client_id"` ConsoleClientID string `json:"consoleClientId,omitempty" db:"console_client_id"`
ConsoleAppID string `json:"console_app_id,omitempty" db:"console_app_id"` ConsoleAppID string `json:"consoleAppId,omitempty" db:"console_app_id"`
DefaultLanguage string `json:"default_language,omitempty" db:"default_language"` DefaultLanguage string `json:"defaultLanguage,omitempty" db:"default_language"`
CreatedAt time.Time `json:"created_at" db:"created_at"` CreatedAt time.Time `json:"createdAt" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"` UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
DeletedAt *time.Time `json:"deleted_at" db:"deleted_at"` DeletedAt *time.Time `json:"deletedAt" db:"deleted_at"`
} }
type instanceCacheIndex uint8 type instanceCacheIndex uint8

View File

@@ -10,3 +10,17 @@ CREATE TABLE IF NOT EXISTS zitadel.instances(
updated_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(),
deleted_at TIMESTAMPTZ DEFAULT NULL deleted_at TIMESTAMPTZ DEFAULT NULL
); );
CREATE OR REPLACE FUNCTION zitadel.set_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at := NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_set_updated_at
BEFORE UPDATE ON zitadel.instances
FOR EACH ROW
WHEN (OLD.* IS DISTINCT FROM NEW.*)
EXECUTE FUNCTION zitadel.set_updated_at();

View File

@@ -53,8 +53,8 @@ func (i *instance) List(ctx context.Context, opts ...database.Condition) ([]*dom
// return only non deleted instances // return only non deleted instances
opts = append(opts, database.IsNull(i.DeletedAtColumn())) opts = append(opts, database.IsNull(i.DeletedAtColumn()))
andCondition := database.And(opts...) notDeletedCondition := database.And(opts...)
i.writeCondition(&builder, andCondition) i.writeCondition(&builder, notDeletedCondition)
rows, err := i.client.Query(ctx, builder.String(), builder.Args()...) rows, err := i.client.Query(ctx, builder.String(), builder.Args()...)
if err != nil { if err != nil {
@@ -105,8 +105,11 @@ func (i instance) Update(ctx context.Context, condition database.Condition, chan
var builder database.StatementBuilder var builder database.StatementBuilder
builder.WriteString(`UPDATE zitadel.instances SET `) builder.WriteString(`UPDATE zitadel.instances SET `)
// don't update deleted instances
conditions := []database.Condition{condition, database.IsNull(i.DeletedAtColumn())}
database.Changes(changes).Write(&builder) database.Changes(changes).Write(&builder)
i.writeCondition(&builder, condition) i.writeCondition(&builder, database.And(conditions...))
stmt := builder.String() stmt := builder.String()

View File

@@ -17,7 +17,7 @@ import (
func TestCreateInstance(t *testing.T) { func TestCreateInstance(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
testFunc func() *domain.Instance testFunc func(ctx context.Context, t *testing.T) *domain.Instance
instance domain.Instance instance domain.Instance
err error err error
}{ }{
@@ -39,7 +39,7 @@ func TestCreateInstance(t *testing.T) {
}(), }(),
}, },
{ {
name: "create instance wihtout name", name: "create instance without name",
instance: func() domain.Instance { instance: func() domain.Instance {
instanceId := gofakeit.Name() instanceId := gofakeit.Name()
// instanceName := gofakeit.Name() // instanceName := gofakeit.Name()
@@ -58,12 +58,11 @@ func TestCreateInstance(t *testing.T) {
}, },
{ {
name: "adding same instance twice", name: "adding same instance twice",
testFunc: func() *domain.Instance { testFunc: func(ctx context.Context, t *testing.T) *domain.Instance {
instanceRepo := repository.InstanceRepository(pool) instanceRepo := repository.InstanceRepository(pool)
instanceId := gofakeit.Name() instanceId := gofakeit.Name()
instanceName := gofakeit.Name() instanceName := gofakeit.Name()
ctx := context.Background()
inst := domain.Instance{ inst := domain.Instance{
ID: instanceId, ID: instanceId,
Name: instanceName, Name: instanceName,
@@ -105,7 +104,7 @@ func TestCreateInstance(t *testing.T) {
var instance *domain.Instance var instance *domain.Instance
if tt.testFunc != nil { if tt.testFunc != nil {
instance = tt.testFunc() instance = tt.testFunc(ctx, t)
} else { } else {
instance = &tt.instance instance = &tt.instance
} }
@@ -142,17 +141,16 @@ func TestCreateInstance(t *testing.T) {
func TestUpdateInstance(t *testing.T) { func TestUpdateInstance(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
testFunc func() *domain.Instance testFunc func(ctx context.Context, t *testing.T) *domain.Instance
rowsAffected int64 rowsAffected int64
}{ }{
{ {
name: "happy path", name: "happy path",
testFunc: func() *domain.Instance { testFunc: func(ctx context.Context, t *testing.T) *domain.Instance {
instanceRepo := repository.InstanceRepository(pool) instanceRepo := repository.InstanceRepository(pool)
instanceId := gofakeit.Name() instanceId := gofakeit.Name()
instanceName := gofakeit.Name() instanceName := gofakeit.Name()
ctx := context.Background()
inst := domain.Instance{ inst := domain.Instance{
ID: instanceId, ID: instanceId,
Name: instanceName, Name: instanceName,
@@ -170,9 +168,40 @@ func TestUpdateInstance(t *testing.T) {
}, },
rowsAffected: 1, rowsAffected: 1,
}, },
{
name: "update deleted instance",
testFunc: func(ctx context.Context, t *testing.T) *domain.Instance {
instanceRepo := repository.InstanceRepository(pool)
instanceId := gofakeit.Name()
instanceName := gofakeit.Name()
inst := domain.Instance{
ID: instanceId,
Name: instanceName,
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
ConsoleClientID: "consoleCLient",
ConsoleAppID: "consoleApp",
DefaultLanguage: "defaultLanguage",
}
// create instance
err := instanceRepo.Create(ctx, &inst)
require.NoError(t, err)
// delete instance
err = instanceRepo.Delete(ctx,
instanceRepo.IDCondition(inst.ID),
)
require.NoError(t, err)
return &inst
},
rowsAffected: 0,
},
{ {
name: "update non existent instance", name: "update non existent instance",
testFunc: func() *domain.Instance { testFunc: func(ctx context.Context, t *testing.T) *domain.Instance {
instanceId := gofakeit.Name() instanceId := gofakeit.Name()
inst := domain.Instance{ inst := domain.Instance{
@@ -185,13 +214,13 @@ func TestUpdateInstance(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
beforeUpdate := time.Now()
ctx := context.Background() ctx := context.Background()
instanceRepo := repository.InstanceRepository(pool) instanceRepo := repository.InstanceRepository(pool)
instance := tt.testFunc() instance := tt.testFunc(ctx, t)
// had to minus 1 second due to a race conditon
beforeUpdate := time.Now().Add(-time.Second)
// update name // update name
newName := "new_" + instance.Name newName := "new_" + instance.Name
rowsAffected, err := instanceRepo.Update(ctx, rowsAffected, err := instanceRepo.Update(ctx,
@@ -223,10 +252,9 @@ func TestUpdateInstance(t *testing.T) {
func TestGetInstance(t *testing.T) { func TestGetInstance(t *testing.T) {
instanceRepo := repository.InstanceRepository(pool) instanceRepo := repository.InstanceRepository(pool)
type test struct { type test struct {
name string name string
testFunc func() *domain.Instance testFunc func(ctx context.Context, t *testing.T) *domain.Instance
conditionClauses []database.Condition conditionClauses []database.Condition
noInstanceReturned bool
} }
tests := []test{ tests := []test{
@@ -234,10 +262,9 @@ func TestGetInstance(t *testing.T) {
instanceId := gofakeit.Name() instanceId := gofakeit.Name()
return test{ return test{
name: "happy path get using id", name: "happy path get using id",
testFunc: func() *domain.Instance { testFunc: func(ctx context.Context, t *testing.T) *domain.Instance {
instanceName := gofakeit.Name() instanceName := gofakeit.Name()
ctx := context.Background()
inst := domain.Instance{ inst := domain.Instance{
ID: instanceId, ID: instanceId,
Name: instanceName, Name: instanceName,
@@ -260,10 +287,9 @@ func TestGetInstance(t *testing.T) {
instanceName := gofakeit.Name() instanceName := gofakeit.Name()
return test{ return test{
name: "happy path get using name", name: "happy path get using name",
testFunc: func() *domain.Instance { testFunc: func(ctx context.Context, t *testing.T) *domain.Instance {
instanceId := gofakeit.Name() instanceId := gofakeit.Name()
ctx := context.Background()
inst := domain.Instance{ inst := domain.Instance{
ID: instanceId, ID: instanceId,
Name: instanceName, Name: instanceName,
@@ -284,16 +310,15 @@ func TestGetInstance(t *testing.T) {
}(), }(),
{ {
name: "get non existent instance", name: "get non existent instance",
testFunc: func() *domain.Instance { testFunc: func(ctx context.Context, t *testing.T) *domain.Instance {
instanceId := gofakeit.Name() instanceId := gofakeit.Name()
inst := domain.Instance{ _ = domain.Instance{
ID: instanceId, ID: instanceId,
} }
return &inst return nil
}, },
conditionClauses: []database.Condition{instanceRepo.NameCondition(database.TextOperationEqual, "non-existent-instance-name")}, conditionClauses: []database.Condition{instanceRepo.NameCondition(database.TextOperationEqual, "non-existent-instance-name")},
noInstanceReturned: true,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
@@ -303,7 +328,7 @@ func TestGetInstance(t *testing.T) {
var instance *domain.Instance var instance *domain.Instance
if tt.testFunc != nil { if tt.testFunc != nil {
instance = tt.testFunc() instance = tt.testFunc(ctx, t)
} }
// check instance values // check instance values
@@ -311,8 +336,8 @@ func TestGetInstance(t *testing.T) {
tt.conditionClauses..., tt.conditionClauses...,
) )
require.NoError(t, err) require.NoError(t, err)
if tt.noInstanceReturned { if instance == nil {
require.Nil(t, returnedInstance) require.Nil(t, instance, returnedInstance)
return return
} }
@@ -329,21 +354,21 @@ func TestGetInstance(t *testing.T) {
} }
func TestListInstance(t *testing.T) { func TestListInstance(t *testing.T) {
ctx := context.Background()
pool, stop, err := newEmbeededDB(ctx)
require.NoError(t, err)
defer stop()
type test struct { type test struct {
name string name string
testFunc func() ([]*domain.Instance, database.PoolTest, func()) testFunc func(ctx context.Context, t *testing.T) []*domain.Instance
conditionClauses []database.Condition conditionClauses []database.Condition
noInstanceReturned bool noInstanceReturned bool
} }
tests := []test{ tests := []test{
{ {
name: "happy path single instance no filter", name: "happy path single instance no filter",
testFunc: func() ([]*domain.Instance, database.PoolTest, func()) { testFunc: func(ctx context.Context, t *testing.T) []*domain.Instance {
ctx := context.Background()
// create new db to make sure no instances exist
pool, stop, err := newEmbeededDB(ctx)
require.NoError(t, err)
instanceRepo := repository.InstanceRepository(pool) instanceRepo := repository.InstanceRepository(pool)
noOfInstances := 1 noOfInstances := 1
instances := make([]*domain.Instance, noOfInstances) instances := make([]*domain.Instance, noOfInstances)
@@ -366,17 +391,12 @@ func TestListInstance(t *testing.T) {
instances[i] = &inst instances[i] = &inst
} }
return instances, pool, stop return instances
}, },
}, },
{ {
name: "happy path multiple instance no filter", name: "happy path multiple instance no filter",
testFunc: func() ([]*domain.Instance, database.PoolTest, func()) { testFunc: func(ctx context.Context, t *testing.T) []*domain.Instance {
ctx := context.Background()
// create new db to make sure no instances exist
pool, stop, err := newEmbeededDB(ctx)
require.NoError(t, err)
instanceRepo := repository.InstanceRepository(pool) instanceRepo := repository.InstanceRepository(pool)
noOfInstances := 5 noOfInstances := 5
instances := make([]*domain.Instance, noOfInstances) instances := make([]*domain.Instance, noOfInstances)
@@ -399,7 +419,7 @@ func TestListInstance(t *testing.T) {
instances[i] = &inst instances[i] = &inst
} }
return instances, pool, stop return instances
}, },
}, },
func() test { func() test {
@@ -407,9 +427,7 @@ func TestListInstance(t *testing.T) {
instanceId := gofakeit.Name() instanceId := gofakeit.Name()
return test{ return test{
name: "instance filter on id", name: "instance filter on id",
testFunc: func() ([]*domain.Instance, database.PoolTest, func()) { testFunc: func(ctx context.Context, t *testing.T) []*domain.Instance {
ctx := context.Background()
noOfInstances := 1 noOfInstances := 1
instances := make([]*domain.Instance, noOfInstances) instances := make([]*domain.Instance, noOfInstances)
for i := range noOfInstances { for i := range noOfInstances {
@@ -431,7 +449,7 @@ func TestListInstance(t *testing.T) {
instances[i] = &inst instances[i] = &inst
} }
return instances, nil, nil return instances
}, },
conditionClauses: []database.Condition{instanceRepo.IDCondition(instanceId)}, conditionClauses: []database.Condition{instanceRepo.IDCondition(instanceId)},
} }
@@ -441,9 +459,7 @@ func TestListInstance(t *testing.T) {
instanceName := gofakeit.Name() instanceName := gofakeit.Name()
return test{ return test{
name: "multiple instance filter on name", name: "multiple instance filter on name",
testFunc: func() ([]*domain.Instance, database.PoolTest, func()) { testFunc: func(ctx context.Context, t *testing.T) []*domain.Instance {
ctx := context.Background()
noOfInstances := 5 noOfInstances := 5
instances := make([]*domain.Instance, noOfInstances) instances := make([]*domain.Instance, noOfInstances)
for i := range noOfInstances { for i := range noOfInstances {
@@ -465,7 +481,7 @@ func TestListInstance(t *testing.T) {
instances[i] = &inst instances[i] = &inst
} }
return instances, nil, nil return instances
}, },
conditionClauses: []database.Condition{instanceRepo.NameCondition(database.TextOperationEqual, instanceName)}, conditionClauses: []database.Condition{instanceRepo.NameCondition(database.TextOperationEqual, instanceName)},
} }
@@ -473,20 +489,13 @@ func TestListInstance(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
ctx := context.Background() // ctx := context.Background()
t.Cleanup(func() {
pool.Exec(ctx, "DELETE FROM zitadel.instances")
})
var instances []*domain.Instance instances := tt.testFunc(ctx, t)
pool := pool
if tt.testFunc != nil {
var stop func()
var pool_ database.PoolTest
instances, pool_, stop = tt.testFunc()
if pool_ != nil {
pool = pool_
defer stop()
}
}
instanceRepo := repository.InstanceRepository(pool) instanceRepo := repository.InstanceRepository(pool)
// check instance values // check instance values
@@ -517,7 +526,7 @@ func TestListInstance(t *testing.T) {
func TestDeleteInstance(t *testing.T) { func TestDeleteInstance(t *testing.T) {
type test struct { type test struct {
name string name string
testFunc func() testFunc func(ctx context.Context, t *testing.T)
conditionClauses database.Condition conditionClauses database.Condition
} }
tests := []test{ tests := []test{
@@ -526,9 +535,7 @@ func TestDeleteInstance(t *testing.T) {
instanceId := gofakeit.Name() instanceId := gofakeit.Name()
return test{ return test{
name: "happy path delete single instance filter id", name: "happy path delete single instance filter id",
testFunc: func() { testFunc: func(ctx context.Context, t *testing.T) {
ctx := context.Background()
noOfInstances := 1 noOfInstances := 1
instances := make([]*domain.Instance, noOfInstances) instances := make([]*domain.Instance, noOfInstances)
for i := range noOfInstances { for i := range noOfInstances {
@@ -558,9 +565,7 @@ func TestDeleteInstance(t *testing.T) {
instanceName := gofakeit.Name() instanceName := gofakeit.Name()
return test{ return test{
name: "happy path delete single instance filter name", name: "happy path delete single instance filter name",
testFunc: func() { testFunc: func(ctx context.Context, t *testing.T) {
ctx := context.Background()
noOfInstances := 1 noOfInstances := 1
instances := make([]*domain.Instance, noOfInstances) instances := make([]*domain.Instance, noOfInstances)
for i := range noOfInstances { for i := range noOfInstances {
@@ -598,9 +603,7 @@ func TestDeleteInstance(t *testing.T) {
instanceName := gofakeit.Name() instanceName := gofakeit.Name()
return test{ return test{
name: "multiple instance filter on name", name: "multiple instance filter on name",
testFunc: func() { testFunc: func(ctx context.Context, t *testing.T) {
ctx := context.Background()
noOfInstances := 5 noOfInstances := 5
instances := make([]*domain.Instance, noOfInstances) instances := make([]*domain.Instance, noOfInstances)
for i := range noOfInstances { for i := range noOfInstances {
@@ -630,9 +633,7 @@ func TestDeleteInstance(t *testing.T) {
instanceName := gofakeit.Name() instanceName := gofakeit.Name()
return test{ return test{
name: "deleted already deleted instance", name: "deleted already deleted instance",
testFunc: func() { testFunc: func(ctx context.Context, t *testing.T) {
ctx := context.Background()
noOfInstances := 1 noOfInstances := 1
instances := make([]*domain.Instance, noOfInstances) instances := make([]*domain.Instance, noOfInstances)
for i := range noOfInstances { for i := range noOfInstances {
@@ -670,7 +671,7 @@ func TestDeleteInstance(t *testing.T) {
instanceRepo := repository.InstanceRepository(pool) instanceRepo := repository.InstanceRepository(pool)
if tt.testFunc != nil { if tt.testFunc != nil {
tt.testFunc() tt.testFunc(ctx, t)
} }
// delete instance // delete instance