started adding tests

This commit is contained in:
Iraq Jaber
2025-05-28 18:18:50 +02:00
parent e8fccf28b8
commit 8ea414e335
9 changed files with 396 additions and 11 deletions

View File

@@ -2,6 +2,7 @@ package domain
import (
"context"
"database/sql"
"time"
"github.com/zitadel/zitadel/backend/v3/storage/cache"
@@ -11,9 +12,9 @@ import (
type Instance struct {
ID string `json:"id"`
Name string `json:"name"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
DeletedAt time.Time `json:"-"`
CreatedAt sql.Null[time.Time] `json:"-"`
UpdatedAt sql.Null[time.Time] `json:"-"`
DeletedAt sql.Null[time.Time] `json:"-"`
}
type instanceCacheIndex uint8
@@ -67,8 +68,9 @@ type InstanceRepository interface {
instanceConditions
instanceChanges
// TODO
// Member returns the member repository which is a sub repository of the instance repository.
Member() MemberRepository
// Member() MemberRepository
Get(ctx context.Context, opts ...database.QueryOption) (*Instance, error)

View File

@@ -13,9 +13,13 @@ type pgxPool struct {
*pgxpool.Pool
}
var (
_ database.Pool = (*pgxPool)(nil)
)
var _ database.Pool = (*pgxPool)(nil)
func PGxPool(pool *pgxpool.Pool) *pgxPool {
return &pgxPool{
Pool: pool,
}
}
// Acquire implements [database.Pool].
func (c *pgxPool) Acquire(ctx context.Context) (database.Client, error) {

View File

@@ -0,0 +1,117 @@
//go:build integration
package instance_test
import (
"context"
"os"
"testing"
"time"
"github.com/brianvoe/gofakeit/v6"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/backend/v3/storage/database"
"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"
"github.com/zitadel/zitadel/pkg/grpc/system"
)
const ConnString = "host=localhost port=5432 user=zitadel dbname=zitadel sslmode=disable"
var (
dbPool *pgxpool.Pool
CTX context.Context
SystemCTX context.Context
Instance *integration.Instance
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()
Instance = integration.NewInstance(ctx)
CTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
// SystemCTX = integration.WithSystemAuthorization(ctx)
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_TestInstanceAddReduces(t *testing.T) {
instanceName := "newInstance"
_, err := SystemClient.CreateInstance(CTX, &system.CreateInstanceRequest{
InstanceName: instanceName,
Owner: &system.CreateInstanceRequest_Machine_{
Machine: &system.CreateInstanceRequest_Machine{
UserName: "owner",
Name: "owner",
PersonalAccessToken: &system.CreateInstanceRequest_PersonalAccessToken{},
},
},
})
require.NoError(t, err)
instanceRepo := repository.InstanceRepository(pool)
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(ttt *assert.CollectT) {
instance, err := instanceRepo.Get(CTX,
database.WithCondition(
instanceRepo.NameCondition(database.TextOperationEqual, instanceName),
),
)
require.NoError(ttt, err)
require.Equal(ttt, instanceName, instance.Name)
}, retryDuration, tick)
}
func TestServer_TestInstanceUpdateNameReduces(t *testing.T) {
instanceName := gofakeit.Name()
res, err := SystemClient.CreateInstance(CTX, &system.CreateInstanceRequest{
InstanceName: instanceName,
Owner: &system.CreateInstanceRequest_Machine_{
Machine: &system.CreateInstanceRequest_Machine{
UserName: "owner",
Name: "owner",
PersonalAccessToken: &system.CreateInstanceRequest_PersonalAccessToken{},
},
},
})
require.NoError(t, err)
instanceName += "new"
_, err = SystemClient.UpdateInstance(CTX, &system.UpdateInstanceRequest{
InstanceId: res.InstanceId,
InstanceName: instanceName,
})
require.NoError(t, err)
instanceRepo := repository.InstanceRepository(pool)
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(ttt *assert.CollectT) {
instance, err := instanceRepo.Get(CTX,
database.WithCondition(
instanceRepo.NameCondition(database.TextOperationEqual, instanceName),
),
)
require.NoError(ttt, err)
require.Equal(ttt, instanceName, instance.Name)
}, retryDuration, tick)
}

View File

@@ -0,0 +1,159 @@
package repository
import (
"context"
"errors"
"github.com/zitadel/zitadel/backend/v3/domain"
"github.com/zitadel/zitadel/backend/v3/storage/database"
)
var _ domain.InstanceRepository = (*instance)(nil)
type instance struct {
repository
}
func InstanceRepository(client database.QueryExecutor) domain.InstanceRepository {
return &instance{
repository: repository{
client: client,
},
}
}
// -------------------------------------------------------------
// repository
// -------------------------------------------------------------
const queryInstanceStmt = `SELECT id, name, created_at, updated_at, deleted_at` +
` FROM zitadel.instances`
// Get implements [domain.InstanceRepository].
func (i *instance) Get(ctx context.Context, opts ...database.QueryOption) (*domain.Instance, error) {
i.builder = database.StatementBuilder{}
options := new(database.QueryOpts)
for _, opt := range opts {
opt(options)
}
i.builder.WriteString(queryInstanceStmt)
options.WriteCondition(&i.builder)
options.WriteOrderBy(&i.builder)
options.WriteLimit(&i.builder)
options.WriteOffset(&i.builder)
return scanInstance(i.client.QueryRow(ctx, i.builder.String(), i.builder.Args()...))
}
const createInstanceStmt = `INSERT INTO zitadel.instances (id, name)` +
` VALUES ($1, $2)` +
` RETURNING created_at, updated_at`
// Create implements [domain.InstanceRepository].
func (i *instance) Create(ctx context.Context, instance *domain.Instance) error {
i.builder = database.StatementBuilder{}
i.builder.AppendArgs(instance.ID, instance.Name)
i.builder.WriteString(createInstanceStmt)
return i.client.QueryRow(ctx, i.builder.String(), i.builder.Args()...).Scan(&instance.CreatedAt, &instance.UpdatedAt)
}
// Update implements [domain.InstanceRepository].
func (i instance) Update(ctx context.Context, condition database.Condition, changes ...database.Change) error {
i.builder = database.StatementBuilder{}
i.builder.WriteString(`UPDATE human_users SET `)
database.Changes(changes).Write(&i.builder)
i.writeCondition(condition)
stmt := i.builder.String()
return i.client.Exec(ctx, stmt, i.builder.Args()...)
}
// Delete implements [domain.InstanceRepository].
func (i instance) Delete(ctx context.Context, condition database.Condition) error {
i.builder.WriteString("DELETE FROM instance")
if condition == nil {
return errors.New("Delete must contain a condition") // (otherwise ALL instances will be deleted)
}
i.writeCondition(condition)
return i.client.Exec(ctx, i.builder.String(), i.builder.Args()...)
}
// -------------------------------------------------------------
// changes
// -------------------------------------------------------------
// SetName implements [domain.instanceChanges].
func (i instance) SetName(name string) database.Change {
return database.NewChange(i.NameColumn(), name)
}
// -------------------------------------------------------------
// conditions
// -------------------------------------------------------------
// IDCondition implements [domain.instanceConditions].
func (i instance) IDCondition(id string) database.Condition {
return database.NewTextCondition(i.IDColumn(), database.TextOperationEqual, id)
}
// NameCondition implements [domain.instanceConditions].
func (i instance) NameCondition(op database.TextOperation, name string) database.Condition {
return database.NewTextCondition(i.NameColumn(), op, name)
}
// -------------------------------------------------------------
// columns
// -------------------------------------------------------------
// IDColumn implements [domain.instanceColumns].
func (instance) IDColumn() database.Column {
return database.NewColumn("id")
}
// NameColumn implements [domain.instanceColumns].
func (instance) NameColumn() database.Column {
return database.NewColumn("name")
}
// CreatedAtColumn implements [domain.instanceColumns].
func (instance) CreatedAtColumn() database.Column {
return database.NewColumn("created_at")
}
// UpdatedAtColumn implements [domain.instanceColumns].
func (instance) UpdatedAtColumn() database.Column {
return database.NewColumn("updated_at")
}
// DeletedAtColumn implements [domain.instanceColumns].
func (instance) DeletedAtColumn() database.Column {
return database.NewColumn("deleted_at")
}
func (i *instance) writeCondition(condition database.Condition) {
if condition == nil {
return
}
i.builder.WriteString(" WHERE ")
condition.Write(&i.builder)
}
func scanInstance(scanner database.Scanner) (*domain.Instance, error) {
var instance domain.Instance
err := scanner.Scan(
&instance.ID,
&instance.Name,
&instance.CreatedAt,
&instance.UpdatedAt,
&instance.DeletedAt,
)
if err != nil {
return nil, err
}
return &instance, nil
}

View File

@@ -0,0 +1,101 @@
package repository_test
import (
"context"
"fmt"
"testing"
"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/dbmock"
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
"go.uber.org/mock/gomock"
)
func TestCreateInstance(t *testing.T) {
instanceRepo := repository.InstanceRepository(pool)
ctx := context.Background()
inst := domain.Instance{
ID: "id",
Name: "name",
}
err := instanceRepo.Create(ctx, &inst)
fmt.Printf("@@ >>>>>>>>>>>>>>>>>>>>>>>>>>>> err = %+v\n", err)
fmt.Printf("@@ >>>>>>>>>>>>>>>>>>>>>>>>>>>> inst = %+v\n", inst)
require.NoError(t, err)
instt, err := instanceRepo.Get(ctx,
database.WithCondition(
instanceRepo.NameCondition(database.TextOperationEqual, "name"),
),
)
// require.NoError(t, err)
fmt.Printf("@@ >>>>>>>>>>>>>>>>>>>>>>>>>>>> err = %+v\n", err)
fmt.Printf("@@ >>>>>>>>>>>>>>>>>>>>>>>>>>>> inst = %+v\n", instt)
t.Skip("tests are meant as examples and are not real tests")
t.Run("User filters", func(t *testing.T) {
client := dbmock.NewMockClient(gomock.NewController(t))
user := repository.UserRepository(client)
u, err := user.Get(context.Background(),
database.WithCondition(
database.And(
database.Or(
user.IDCondition("test"),
user.IDCondition("2"),
),
user.UsernameCondition(database.TextOperationStartsWithIgnoreCase, "test"),
),
),
database.WithOrderBy(user.CreatedAtColumn()),
)
assert.NoError(t, err)
assert.NotNil(t, u)
})
t.Run("machine and human filters", func(t *testing.T) {
client := dbmock.NewMockClient(gomock.NewController(t))
user := repository.UserRepository(client)
machine := user.Machine()
human := user.Human()
email, err := human.GetEmail(context.Background(), database.And(
user.UsernameCondition(database.TextOperationStartsWithIgnoreCase, "test"),
database.Or(
machine.DescriptionCondition(database.TextOperationStartsWithIgnoreCase, "test"),
human.EmailVerifiedCondition(true),
database.IsNotNull(machine.DescriptionColumn()),
),
))
assert.NoError(t, err)
assert.NotNil(t, email)
})
}
// type dbInstruction string
// func TestArg(t *testing.T) {
// var bla any = "asdf"
// instr, ok := bla.(dbInstruction)
// assert.False(t, ok)
// assert.Empty(t, instr)
// bla = dbInstruction("asdf")
// instr, ok = bla.(dbInstruction)
// assert.True(t, ok)
// assert.Equal(t, instr, dbInstruction("asdf"))
// }
// func TestWriteUser(t *testing.T) {
// t.Skip("tests are meant as examples and are not real tests")
// t.Run("update user", func(t *testing.T) {
// user := repository.UserRepository(nil)
// user.Human().Update(context.Background(), user.IDCondition("test"), user.SetUsername("test"))
// })
// }

View File

@@ -1,4 +1,4 @@
package repository
package repository_test
import (
"context"

View File

@@ -3,6 +3,7 @@ package repository
import "github.com/zitadel/zitadel/backend/v3/storage/database"
type repository struct {
// we can't reuse builder after it's been used already, I think we should remove it
builder database.StatementBuilder
client database.QueryExecutor
}

View File

@@ -1,4 +1,4 @@
package repository
package repository_test
import (
"context"

View File

@@ -33,6 +33,7 @@ func (*instanceRelationalProjection) Init() *old_handler.Check {
handler.NewColumn(InstanceColumnProjectID, handler.ColumnTypeText, handler.Default("")),
handler.NewColumn(InstanceColumnConsoleID, handler.ColumnTypeText, handler.Default("")),
handler.NewColumn(InstanceColumnConsoleAppID, handler.ColumnTypeText, handler.Default("")),
handler.NewColumn(InstanceColumnSequence, handler.ColumnTypeInt64),
handler.NewColumn(InstanceColumnDefaultLanguage, handler.ColumnTypeText, handler.Default("")),
},
handler.NewPrimaryKey(InstanceColumnID),