mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 15:07:32 +00:00
started adding tests
This commit is contained in:
@@ -2,6 +2,7 @@ package domain
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/backend/v3/storage/cache"
|
"github.com/zitadel/zitadel/backend/v3/storage/cache"
|
||||||
@@ -9,11 +10,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Instance struct {
|
type Instance struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
CreatedAt time.Time `json:"-"`
|
CreatedAt sql.Null[time.Time] `json:"-"`
|
||||||
UpdatedAt time.Time `json:"-"`
|
UpdatedAt sql.Null[time.Time] `json:"-"`
|
||||||
DeletedAt time.Time `json:"-"`
|
DeletedAt sql.Null[time.Time] `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type instanceCacheIndex uint8
|
type instanceCacheIndex uint8
|
||||||
@@ -67,8 +68,9 @@ type InstanceRepository interface {
|
|||||||
instanceConditions
|
instanceConditions
|
||||||
instanceChanges
|
instanceChanges
|
||||||
|
|
||||||
|
// TODO
|
||||||
// Member returns the member repository which is a sub repository of the instance repository.
|
// 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)
|
Get(ctx context.Context, opts ...database.QueryOption) (*Instance, error)
|
||||||
|
|
||||||
|
@@ -13,9 +13,13 @@ type pgxPool struct {
|
|||||||
*pgxpool.Pool
|
*pgxpool.Pool
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var _ database.Pool = (*pgxPool)(nil)
|
||||||
_ database.Pool = (*pgxPool)(nil)
|
|
||||||
)
|
func PGxPool(pool *pgxpool.Pool) *pgxPool {
|
||||||
|
return &pgxPool{
|
||||||
|
Pool: pool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Acquire implements [database.Pool].
|
// Acquire implements [database.Pool].
|
||||||
func (c *pgxPool) Acquire(ctx context.Context) (database.Client, error) {
|
func (c *pgxPool) Acquire(ctx context.Context) (database.Client, error) {
|
||||||
|
117
backend/v3/storage/database/events_testing/instance_test.go
Normal file
117
backend/v3/storage/database/events_testing/instance_test.go
Normal 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)
|
||||||
|
}
|
159
backend/v3/storage/database/repository/instance.go
Normal file
159
backend/v3/storage/database/repository/instance.go
Normal 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
|
||||||
|
}
|
101
backend/v3/storage/database/repository/instance_test.go
Normal file
101
backend/v3/storage/database/repository/instance_test.go
Normal 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"))
|
||||||
|
// })
|
||||||
|
// }
|
@@ -1,4 +1,4 @@
|
|||||||
package repository
|
package repository_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@@ -3,6 +3,7 @@ package repository
|
|||||||
import "github.com/zitadel/zitadel/backend/v3/storage/database"
|
import "github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||||
|
|
||||||
type repository struct {
|
type repository struct {
|
||||||
|
// we can't reuse builder after it's been used already, I think we should remove it
|
||||||
builder database.StatementBuilder
|
builder database.StatementBuilder
|
||||||
client database.QueryExecutor
|
client database.QueryExecutor
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
package repository
|
package repository_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@@ -33,6 +33,7 @@ func (*instanceRelationalProjection) Init() *old_handler.Check {
|
|||||||
handler.NewColumn(InstanceColumnProjectID, handler.ColumnTypeText, handler.Default("")),
|
handler.NewColumn(InstanceColumnProjectID, handler.ColumnTypeText, handler.Default("")),
|
||||||
handler.NewColumn(InstanceColumnConsoleID, handler.ColumnTypeText, handler.Default("")),
|
handler.NewColumn(InstanceColumnConsoleID, handler.ColumnTypeText, handler.Default("")),
|
||||||
handler.NewColumn(InstanceColumnConsoleAppID, handler.ColumnTypeText, handler.Default("")),
|
handler.NewColumn(InstanceColumnConsoleAppID, handler.ColumnTypeText, handler.Default("")),
|
||||||
|
handler.NewColumn(InstanceColumnSequence, handler.ColumnTypeInt64),
|
||||||
handler.NewColumn(InstanceColumnDefaultLanguage, handler.ColumnTypeText, handler.Default("")),
|
handler.NewColumn(InstanceColumnDefaultLanguage, handler.ColumnTypeText, handler.Default("")),
|
||||||
},
|
},
|
||||||
handler.NewPrimaryKey(InstanceColumnID),
|
handler.NewPrimaryKey(InstanceColumnID),
|
||||||
|
Reference in New Issue
Block a user