From 400fb97d6d49c7317836f4c7a8c89624b7f84776 Mon Sep 17 00:00:00 2001 From: Iraq Jaber Date: Fri, 6 Jun 2025 12:31:18 +0200 Subject: [PATCH] fixup! fixup! fixup! Merge branch 'main' into import_export_merge --- backend/v3/storage/database/database.go | 5 + .../storage/database/dialect/postgres/pool.go | 17 + .../storage/database/repository/instance.go | 4 +- .../database/repository/instance_test.go | 390 ++++++++++++++---- .../database/repository/repository_test.go | 46 ++- go.mod | 4 - go.sum | 9 - 7 files changed, 359 insertions(+), 116 deletions(-) diff --git a/backend/v3/storage/database/database.go b/backend/v3/storage/database/database.go index 7a50703a25..f962132fa0 100644 --- a/backend/v3/storage/database/database.go +++ b/backend/v3/storage/database/database.go @@ -14,6 +14,11 @@ type Pool interface { Close(ctx context.Context) error } +type PoolTest interface { + Pool + MigrateTest(ctx context.Context) error +} + // Client is a single database connection which can be released back to the pool. type Client interface { Beginner diff --git a/backend/v3/storage/database/dialect/postgres/pool.go b/backend/v3/storage/database/dialect/postgres/pool.go index 971285d86b..74927e0956 100644 --- a/backend/v3/storage/database/dialect/postgres/pool.go +++ b/backend/v3/storage/database/dialect/postgres/pool.go @@ -80,3 +80,20 @@ func (c *pgxPool) Migrate(ctx context.Context) error { isMigrated = err == nil return err } + +// Migrate implements [database.PoolTest]. +func (c *pgxPool) MigrateTest(ctx context.Context) error { + // allow multiple migrations + // if isMigrated { + // return nil + // } + + client, err := c.Pool.Acquire(ctx) + if err != nil { + return err + } + + err = migration.Migrate(ctx, client.Conn()) + isMigrated = err == nil + return err +} diff --git a/backend/v3/storage/database/repository/instance.go b/backend/v3/storage/database/repository/instance.go index a41b347d4b..f3dd185cfb 100644 --- a/backend/v3/storage/database/repository/instance.go +++ b/backend/v3/storage/database/repository/instance.go @@ -32,12 +32,12 @@ const queryInstanceStmt = `SELECT id, name, default_org_id, iam_project_id, cons ` FROM zitadel.instances` // Get implements [domain.InstanceRepository]. -// func (i *instance) Get(ctx context.Context, opts ...database.QueryOption) (*domain.Instance, error) { func (i *instance) Get(ctx context.Context, opts ...database.Condition) (*domain.Instance, error) { i.builder = database.StatementBuilder{} i.builder.WriteString(queryInstanceStmt) + // return only non deleted isntances opts = append(opts, database.IsNull(i.DeletedAtColumn())) andCondition := database.And(opts...) andCondition.Write(&i.builder) @@ -46,12 +46,12 @@ func (i *instance) Get(ctx context.Context, opts ...database.Condition) (*domain } // List implements [domain.InstanceRepository]. -// func (i *instance) List(ctx context.Context, opts ...database.QueryOption) (*domain.Instance, error) { func (i *instance) List(ctx context.Context, opts ...database.Condition) ([]*domain.Instance, error) { i.builder = database.StatementBuilder{} i.builder.WriteString(queryInstanceStmt) + // return only non deleted isntances opts = append(opts, database.IsNull(i.DeletedAtColumn())) andCondition := database.And(opts...) andCondition.Write(&i.builder) diff --git a/backend/v3/storage/database/repository/instance_test.go b/backend/v3/storage/database/repository/instance_test.go index bfc603ac65..1555b0aed7 100644 --- a/backend/v3/storage/database/repository/instance_test.go +++ b/backend/v3/storage/database/repository/instance_test.go @@ -296,115 +296,337 @@ func TestGetInstance(t *testing.T) { } func TestListInstance(t *testing.T) { - tests := []struct { - name string - testFunc func() *domain.Instance - + type test struct { + name string + testFunc func() ([]*domain.Instance, database.PoolTest, func()) + conditionClauses []database.Condition noInstanceReturned bool - }{ + } + tests := []test{ { - name: "happy path", - testFunc: func() *domain.Instance { - instanceRepo := repository.InstanceRepository(pool) - instanceId := gofakeit.Name() - instanceName := gofakeit.Name() - + name: "happy path single instance", + testFunc: func() ([]*domain.Instance, database.PoolTest, func()) { ctx := context.Background() - inst := domain.Instance{ - ID: instanceId, - Name: instanceName, - DefaultOrgID: "defaultOrgId", - IAMProjectID: "iamProject", - ConsoleClientID: "consoleCLient", - ConsoleAppID: "consoleApp", - DefaultLanguage: "defaultLanguage", + pool, stop, err := newEmbeededDB() + assert.NoError(t, err) + + instanceRepo := repository.InstanceRepository(pool) + noOfInstances := 1 + instances := make([]*domain.Instance, noOfInstances) + for i := range noOfInstances { + + 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) + assert.NoError(t, err) + + instances[i] = &inst } - // create instance - err := instanceRepo.Create(ctx, &inst) - assert.NoError(t, err) - return &inst + return instances, pool, stop }, }, { - name: "get non existent instance", - testFunc: func() *domain.Instance { - instanceId := gofakeit.Name() + name: "happy path multiple instance", + testFunc: func() ([]*domain.Instance, database.PoolTest, func()) { + ctx := context.Background() + pool, stop, err := newEmbeededDB() + assert.NoError(t, err) - inst := domain.Instance{ - ID: instanceId, + instanceRepo := repository.InstanceRepository(pool) + noOfInstances := 5 + instances := make([]*domain.Instance, noOfInstances) + for i := range noOfInstances { + + 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) + assert.NoError(t, err) + + instances[i] = &inst } - return &inst + + return instances, pool, stop }, - noInstanceReturned: true, }, + func() test { + instanceRepo := repository.InstanceRepository(pool) + instanceId := gofakeit.Name() + return test{ + name: "instance filter on id", + testFunc: func() ([]*domain.Instance, database.PoolTest, func()) { + ctx := context.Background() + + noOfInstances := 1 + instances := make([]*domain.Instance, noOfInstances) + for i := range noOfInstances { + + 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) + assert.NoError(t, err) + + instances[i] = &inst + } + + return instances, nil, nil + }, + conditionClauses: []database.Condition{instanceRepo.IDCondition(instanceId)}, + } + }(), + func() test { + instanceRepo := repository.InstanceRepository(pool) + instanceName := gofakeit.Name() + return test{ + name: "multiple instance filter on name", + testFunc: func() ([]*domain.Instance, database.PoolTest, func()) { + ctx := context.Background() + + noOfInstances := 5 + instances := make([]*domain.Instance, noOfInstances) + for i := range noOfInstances { + + instanceId := 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) + assert.NoError(t, err) + + instances[i] = &inst + } + + return instances, nil, nil + }, + conditionClauses: []database.Condition{instanceRepo.NameCondition(database.TextOperationEqual, instanceName)}, + } + }(), + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + + var instances []*domain.Instance + + 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) + + // check instance values + returnedInstances, err := instanceRepo.List(ctx, + tt.conditionClauses..., + ) + assert.NoError(t, err) + if tt.noInstanceReturned { + assert.Nil(t, returnedInstances) + return + } + + assert.Equal(t, len(instances), len(returnedInstances)) + for i, instance := range instances { + assert.Equal(t, returnedInstances[i].ID, instance.ID) + assert.Equal(t, returnedInstances[i].Name, instance.Name) + assert.Equal(t, returnedInstances[i].DefaultOrgID, instance.DefaultOrgID) + assert.Equal(t, returnedInstances[i].IAMProjectID, instance.IAMProjectID) + assert.Equal(t, returnedInstances[i].ConsoleClientID, instance.ConsoleClientID) + assert.Equal(t, returnedInstances[i].ConsoleAppID, instance.ConsoleAppID) + assert.Equal(t, returnedInstances[i].DefaultLanguage, instance.DefaultLanguage) + assert.NoError(t, err) + } + }) + } +} + +func TestDeleteInstance(t *testing.T) { + type test struct { + name string + testFunc func() + conditionClauses database.Condition + noInstanceReturned bool + } + tests := []test{ + func() test { + instanceRepo := repository.InstanceRepository(pool) + instanceName := gofakeit.Name() + return test{ + name: "happy path delete single instance", + testFunc: func() { + ctx := context.Background() + + noOfInstances := 1 + instances := make([]*domain.Instance, noOfInstances) + for i := range noOfInstances { + + instanceId := 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) + assert.NoError(t, err) + + instances[i] = &inst + } + }, + conditionClauses: instanceRepo.NameCondition(database.TextOperationEqual, instanceName), + } + }(), + func() test { + instanceRepo := repository.InstanceRepository(pool) + instanceName := gofakeit.Name() + return test{ + name: "multiple instance filter on name", + testFunc: func() { + ctx := context.Background() + + noOfInstances := 5 + instances := make([]*domain.Instance, noOfInstances) + for i := range noOfInstances { + + instanceId := 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) + assert.NoError(t, err) + + instances[i] = &inst + } + }, + conditionClauses: instanceRepo.NameCondition(database.TextOperationEqual, instanceName), + } + }(), + func() test { + instanceRepo := repository.InstanceRepository(pool) + instanceName := gofakeit.Name() + return test{ + name: "deleted already deleted instance", + testFunc: func() { + ctx := context.Background() + + noOfInstances := 1 + instances := make([]*domain.Instance, noOfInstances) + for i := range noOfInstances { + + instanceId := 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) + assert.NoError(t, err) + + instances[i] = &inst + } + + // delete instance + err := instanceRepo.Delete(ctx, + instanceRepo.NameCondition(database.TextOperationEqual, instanceName), + ) + assert.NoError(t, err) + }, + conditionClauses: instanceRepo.NameCondition(database.TextOperationEqual, instanceName), + } + }(), } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := context.Background() instanceRepo := repository.InstanceRepository(pool) - var instance *domain.Instance if tt.testFunc != nil { - instance = tt.testFunc() + tt.testFunc() } - // check instance values - returnedInstance, err := instanceRepo.List(ctx, - instanceRepo.IDCondition(instance.ID), + // delete instance + err := instanceRepo.Delete(ctx, + tt.conditionClauses, ) assert.NoError(t, err) - if tt.noInstanceReturned { - assert.Nil(t, returnedInstance) - return - } - assert.Equal(t, returnedInstance.ID, instance.ID) - assert.Equal(t, returnedInstance.Name, instance.Name) - assert.Equal(t, returnedInstance.DefaultOrgID, instance.DefaultOrgID) - assert.Equal(t, returnedInstance.IAMProjectID, instance.IAMProjectID) - assert.Equal(t, returnedInstance.ConsoleClientID, instance.ConsoleClientID) - assert.Equal(t, returnedInstance.ConsoleAppID, instance.ConsoleAppID) - assert.Equal(t, returnedInstance.DefaultLanguage, instance.DefaultLanguage) + // check instance was deleted + instance, err := instanceRepo.Get(ctx, + tt.conditionClauses, + ) assert.NoError(t, err) + assert.Nil(t, instance) }) } } - -func TestUpdateDeleteInstance(t *testing.T) { - instanceRepo := repository.InstanceRepository(pool) - instanceId := gofakeit.Name() - instanceName := gofakeit.Name() - - ctx := context.Background() - inst := domain.Instance{ - ID: instanceId, - Name: instanceName, - DefaultOrgID: "defaultOrgId", - IAMProjectID: "iamProject", - ConsoleClientID: "consoleCLient", - ConsoleAppID: "consoleApp", - DefaultLanguage: "defaultLanguage", - } - - err := instanceRepo.Create(ctx, &inst) - assert.NoError(t, err) - - instance, err := instanceRepo.Get(ctx, - instanceRepo.NameCondition(database.TextOperationEqual, instanceName), - ) - assert.NotNil(t, instance) - assert.NoError(t, err) - - // delete instance - err = instanceRepo.Delete(ctx, - instanceRepo.IDCondition(instanceId), - ) - assert.NoError(t, err) - - instance, err = instanceRepo.Get(ctx, - instanceRepo.NameCondition(database.TextOperationEqual, instanceName), - ) - assert.NoError(t, err) - assert.Nil(t, instance) -} diff --git a/backend/v3/storage/database/repository/repository_test.go b/backend/v3/storage/database/repository/repository_test.go index 05ec45b930..28db52365f 100644 --- a/backend/v3/storage/database/repository/repository_test.go +++ b/backend/v3/storage/database/repository/repository_test.go @@ -2,6 +2,7 @@ package repository_test import ( "context" + "fmt" "log" "os" "testing" @@ -14,28 +15,39 @@ func TestMain(m *testing.M) { os.Exit(runTests(m)) } -var pool database.Pool +var pool database.PoolTest func runTests(m *testing.M) int { - connector, stop, err := embedded.StartEmbedded() + var stop func() + var err error + pool, stop, err = newEmbeededDB() if err != nil { - log.Fatalf("unable to start embedded postgres: %v", err) + log.Print(err) + return 1 } defer stop() - ctx := context.Background() - - pool, err = connector.Connect(ctx) - if err != nil { - log.Printf("unable to connect to embedded postgres: %v", err) - return 1 - } - - err = pool.Migrate(ctx) - if err != nil { - log.Printf("unable to migrate database: %v", err) - return 1 - } - return m.Run() } + +func newEmbeededDB() (pool database.PoolTest, stop func(), err error) { + var connector database.Connector + connector, stop, err = embedded.StartEmbedded() + if err != nil { + return nil, nil, fmt.Errorf("unable to start embedded postgres: %v", err) + } + + ctx := context.Background() + + pool_, err := connector.Connect(ctx) + if err != nil { + return nil, nil, fmt.Errorf("unable to connect to embedded postgres: %v", err) + } + pool = pool_.(database.PoolTest) + + err = pool.MigrateTest(ctx) + if err != nil { + return nil, nil, fmt.Errorf("unable to migrate database: %v", err) + } + return pool, stop, err +} diff --git a/go.mod b/go.mod index b82fce9863..de0e8aa314 100644 --- a/go.mod +++ b/go.mod @@ -43,7 +43,6 @@ require ( github.com/h2non/gock v1.2.0 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/improbable-eng/grpc-web v0.15.0 - github.com/jackc/pgconn v1.14.3 github.com/jackc/pgx/v5 v5.7.3 github.com/jackc/tern/v2 v2.3.3 github.com/jarcoal/jpath v0.0.0-20140328210829-f76b8b2dbf52 @@ -136,9 +135,6 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/huandu/xstrings v1.5.0 // indirect - github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgio v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.3 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/lib/pq v1.10.9 // indirect diff --git a/go.sum b/go.sum index b2313dec22..ff10718898 100644 --- a/go.sum +++ b/go.sum @@ -449,19 +449,10 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= -github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= -github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= -github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.7.3 h1:PO1wNKj/bTAwxSJnO1Z4Ai8j4magtqg2SLNjEDzcXQo=