refactor: database interaction and error handling (#10762)

This pull request introduces a significant refactoring of the database
interaction layer, focusing on improving explicitness, transactional
control, and error handling. The core change is the removal of the
stateful `QueryExecutor` from repository instances. Instead, it is now
passed as an argument to each method that interacts with the database.

This change makes transaction management more explicit and flexible, as
the same repository instance can be used with a database pool or a
specific transaction without needing to be re-instantiated.

### Key Changes

- **Explicit `QueryExecutor` Passing:**
- All repository methods (`Get`, `List`, `Create`, `Update`, `Delete`,
etc.) in `InstanceRepository`, `OrganizationRepository`,
`UserRepository`, and their sub-repositories now require a
`database.QueryExecutor` (e.g., a `*pgxpool.Pool` or `pgx.Tx`) as the
first argument.
- Repository constructors no longer accept a `QueryExecutor`. For
example, `repository.InstanceRepository(pool)` is now
`repository.InstanceRepository()`.

- **Enhanced Error Handling:**
- A new `database.MissingConditionError` is introduced to enforce
required query conditions, such as ensuring an `instance_id` is always
present in `UPDATE` and `DELETE` operations.
- The database error wrapper in the `postgres` package now correctly
identifies and wraps `pgx.ErrTooManyRows` and similar errors from the
`scany` library into a `database.MultipleRowsFoundError`.

- **Improved Database Conditions:**
- The `database.Condition` interface now includes a
`ContainsColumn(Column) bool` method. This allows for runtime checks to
ensure that critical filters (like `instance_id`) are included in a
query, preventing accidental cross-tenant data modification.
- A new `database.Exists()` condition has been added to support `EXISTS`
subqueries, enabling more complex filtering logic, such as finding an
organization that has a specific domain.

- **Repository and Interface Refactoring:**
- The method for loading related entities (e.g., domains for an
organization) has been changed from a boolean flag (`Domains(true)`) to
a more explicit, chainable method (`LoadDomains()`). This returns a new
repository instance configured to load the sub-resource, promoting
immutability.
- The custom `OrgIdentifierCondition` has been removed in favor of using
the standard `database.Condition` interface, simplifying the API.

- **Code Cleanup and Test Updates:**
  - Unnecessary struct embeddings and metadata have been removed.
- All integration and repository tests have been updated to reflect the
new method signatures, passing the database pool or transaction object
explicitly.
- New tests have been added to cover the new `ExistsDomain`
functionality and other enhancements.

These changes make the data access layer more robust, predictable, and
easier to work with, especially in the context of database transactions.
This commit is contained in:
Silvan
2025-09-24 10:12:31 +00:00
committed by GitHub
parent 09d09ab337
commit cccfc816f6
53 changed files with 3893 additions and 1423 deletions
@@ -3,19 +3,35 @@ package repository
import (
"encoding/json"
"errors"
"github.com/zitadel/zitadel/backend/v3/storage/database"
)
type JSONArray[T any] []*T
func (a JSONArray[T]) Scan(src any) error {
var ErrScanSource = errors.New("unsupported scan source")
func (a *JSONArray[T]) Scan(src any) (err error) {
var rawJSON []byte
switch s := src.(type) {
case string:
return json.Unmarshal([]byte(s), &a)
if len(s) == 0 {
return nil
}
rawJSON = []byte(s)
case []byte:
return json.Unmarshal(s, &a)
if len(s) == 0 {
return nil
}
rawJSON = s
case nil:
return nil
default:
return errors.New("unsupported scan source")
return ErrScanSource
}
err = json.Unmarshal(rawJSON, a)
if err != nil {
return database.NewScanError(err)
}
return nil
}
@@ -0,0 +1,117 @@
package repository_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/backend/v3/storage/database"
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
)
type testObject struct {
ID string `json:"id"`
Number int `json:"number"`
Active bool `json:"active"`
}
func TestJSONArray_Scan(t *testing.T) {
type want struct {
err error
res []*testObject
}
tests := []struct {
name string
src any
want want
}{
{
name: "nil",
src: nil,
want: want{
err: nil,
res: nil,
},
},
{
name: "[]byte with data",
src: []byte(`[{"id":"1","number":1,"active":true}]`),
want: want{
err: nil,
res: []*testObject{{ID: "1", Number: 1, Active: true}},
},
},
{
name: "[]byte without data",
src: []byte(`[]`),
want: want{
err: nil,
res: nil,
},
},
{
name: "nil []byte",
src: []byte(nil),
want: want{
err: nil,
res: nil,
},
},
{
name: "empty []byte",
src: []byte{},
want: want{
err: nil,
res: nil,
},
},
{
name: "string with data",
src: `[{"id":"1","number":1,"active":true}]`,
want: want{
err: nil,
res: []*testObject{{ID: "1", Number: 1, Active: true}},
},
},
{
name: "string without data",
src: string(`[]`),
want: want{
err: nil,
res: nil,
},
},
{
name: "empty string",
src: "",
want: want{
err: nil,
res: nil,
},
},
{
name: "wrong type",
src: []int{1, 2, 3},
want: want{
err: repository.ErrScanSource,
res: nil,
},
},
{
name: "badly formatted JSON",
src: []byte(`this is not a JSON`),
want: want{
err: new(database.ScanError),
res: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var a repository.JSONArray[testObject]
gotErr := a.Scan(tt.src)
require.ErrorIs(t, gotErr, tt.want.err)
require.Len(t, a, len(tt.want.res))
})
}
}
@@ -13,17 +13,20 @@ import (
var _ domain.InstanceRepository = (*instance)(nil)
type instance struct {
repository
shouldLoadDomains bool
domainRepo *instanceDomain
domainRepo instanceDomain
}
func InstanceRepository(client database.QueryExecutor) domain.InstanceRepository {
return &instance{
repository: repository{
client: client,
},
}
func InstanceRepository() domain.InstanceRepository {
return new(instance)
}
func (instance) qualifiedTableName() string {
return "zitadel.instances"
}
func (instance) unqualifiedTableName() string {
return "instances"
}
// -------------------------------------------------------------
@@ -37,7 +40,7 @@ const (
)
// Get implements [domain.InstanceRepository].
func (i *instance) Get(ctx context.Context, opts ...database.QueryOption) (*domain.Instance, error) {
func (i instance) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.Instance, error) {
opts = append(opts,
i.joinDomains(),
database.WithGroupBy(i.IDColumn()),
@@ -52,11 +55,11 @@ func (i *instance) Get(ctx context.Context, opts ...database.QueryOption) (*doma
builder.WriteString(queryInstanceStmt)
options.Write(&builder)
return scanInstance(ctx, i.client, &builder)
return scanInstance(ctx, client, &builder)
}
// List implements [domain.InstanceRepository].
func (i *instance) List(ctx context.Context, opts ...database.QueryOption) ([]*domain.Instance, error) {
func (i instance) List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*domain.Instance, error) {
opts = append(opts,
i.joinDomains(),
database.WithGroupBy(i.IDColumn()),
@@ -71,27 +74,11 @@ func (i *instance) List(ctx context.Context, opts ...database.QueryOption) ([]*d
builder.WriteString(queryInstanceStmt)
options.Write(&builder)
return scanInstances(ctx, i.client, &builder)
}
func (i *instance) joinDomains() database.QueryOption {
columns := make([]database.Condition, 0, 2)
columns = append(columns, database.NewColumnCondition(i.IDColumn(), i.Domains(false).InstanceIDColumn()))
// If domains should not be joined, we make sure to return null for the domain columns
// the query optimizer of the dialect should optimize this away if no domains are requested
if !i.shouldLoadDomains {
columns = append(columns, database.IsNull(i.Domains(false).InstanceIDColumn()))
}
return database.WithLeftJoin(
"zitadel.instance_domains",
database.And(columns...),
)
return scanInstances(ctx, client, &builder)
}
// Create implements [domain.InstanceRepository].
func (i *instance) Create(ctx context.Context, instance *domain.Instance) error {
func (i instance) Create(ctx context.Context, client database.QueryExecutor, instance *domain.Instance) error {
var (
builder database.StatementBuilder
createdAt, updatedAt any = database.DefaultInstruction, database.DefaultInstruction
@@ -103,42 +90,46 @@ func (i *instance) Create(ctx context.Context, instance *domain.Instance) error
updatedAt = instance.UpdatedAt
}
builder.WriteString(`INSERT INTO zitadel.instances (id, name, default_org_id, iam_project_id, console_client_id, console_app_id, default_language, created_at, updated_at) VALUES (`)
builder.WriteString(`INSERT INTO `)
builder.WriteString(i.qualifiedTableName())
builder.WriteString(` (id, name, default_org_id, iam_project_id, console_client_id, console_app_id, default_language, created_at, updated_at) VALUES (`)
builder.WriteArgs(instance.ID, instance.Name, instance.DefaultOrgID, instance.IAMProjectID, instance.ConsoleClientID, instance.ConsoleAppID, instance.DefaultLanguage, createdAt, updatedAt)
builder.WriteString(`) RETURNING created_at, updated_at`)
return i.client.QueryRow(ctx, builder.String(), builder.Args()...).Scan(&instance.CreatedAt, &instance.UpdatedAt)
return client.QueryRow(ctx, builder.String(), builder.Args()...).Scan(&instance.CreatedAt, &instance.UpdatedAt)
}
// Update implements [domain.InstanceRepository].
func (i instance) Update(ctx context.Context, id string, changes ...database.Change) (int64, error) {
func (i instance) Update(ctx context.Context, client database.QueryExecutor, id string, changes ...database.Change) (int64, error) {
if len(changes) == 0 {
return 0, database.ErrNoChanges
}
if !database.Changes(changes).IsOnColumn(i.UpdatedAtColumn()) {
changes = append(changes, database.NewChange(i.UpdatedAtColumn(), database.NullInstruction))
}
var builder database.StatementBuilder
builder.WriteString(`UPDATE zitadel.instances SET `)
database.Changes(changes).Write(&builder)
idCondition := i.IDCondition(id)
writeCondition(&builder, idCondition)
stmt := builder.String()
return i.client.Exec(ctx, stmt, builder.Args()...)
return client.Exec(ctx, stmt, builder.Args()...)
}
// Delete implements [domain.InstanceRepository].
func (i instance) Delete(ctx context.Context, id string) (int64, error) {
func (i instance) Delete(ctx context.Context, client database.QueryExecutor, id string) (int64, error) {
var builder database.StatementBuilder
builder.WriteString(`DELETE FROM zitadel.instances`)
builder.WriteString(`DELETE FROM `)
builder.WriteString(i.qualifiedTableName())
idCondition := i.IDCondition(id)
writeCondition(&builder, idCondition)
return i.client.Exec(ctx, builder.String(), builder.Args()...)
return client.Exec(ctx, builder.String(), builder.Args()...)
}
// -------------------------------------------------------------
@@ -185,53 +176,77 @@ func (i instance) NameCondition(op database.TextOperation, name string) database
return database.NewTextCondition(i.NameColumn(), op, name)
}
// ExistsDomain creates a correlated [database.Exists] condition on instance_domains.
// Use this filter to make sure the Instance returned contains a specific domain.
// of the instance in the aggregated result.
// Example usage:
//
// domainRepo := instanceRepo.Domains(true) // ensure domains are loaded/aggregated
// instance, _ := instanceRepo.Get(ctx,
// database.WithCondition(
// database.And(
// instanceRepo.InstanceIDCondition(instanceID),
// instanceRepo.ExistsDomain(domainRepo.DomainCondition(database.TextOperationEqual, "example.com")),
// ),
// ),
// )
func (i instance) ExistsDomain(cond database.Condition) database.Condition {
return database.Exists(
i.domainRepo.qualifiedTableName(),
database.And(
database.NewColumnCondition(i.IDColumn(), i.domainRepo.InstanceIDColumn()),
cond,
),
)
}
// -------------------------------------------------------------
// columns
// -------------------------------------------------------------
// IDColumn implements [domain.instanceColumns].
func (instance) IDColumn() database.Column {
return database.NewColumn("instances", "id")
func (i instance) IDColumn() database.Column {
return database.NewColumn(i.unqualifiedTableName(), "id")
}
// NameColumn implements [domain.instanceColumns].
func (instance) NameColumn() database.Column {
return database.NewColumn("instances", "name")
func (i instance) NameColumn() database.Column {
return database.NewColumn(i.unqualifiedTableName(), "name")
}
// CreatedAtColumn implements [domain.instanceColumns].
func (instance) CreatedAtColumn() database.Column {
return database.NewColumn("instances", "created_at")
func (i instance) CreatedAtColumn() database.Column {
return database.NewColumn(i.unqualifiedTableName(), "created_at")
}
// DefaultOrgIdColumn implements [domain.instanceColumns].
func (instance) DefaultOrgIDColumn() database.Column {
return database.NewColumn("instances", "default_org_id")
func (i instance) DefaultOrgIDColumn() database.Column {
return database.NewColumn(i.unqualifiedTableName(), "default_org_id")
}
// IAMProjectIDColumn implements [domain.instanceColumns].
func (instance) IAMProjectIDColumn() database.Column {
return database.NewColumn("instances", "iam_project_id")
func (i instance) IAMProjectIDColumn() database.Column {
return database.NewColumn(i.unqualifiedTableName(), "iam_project_id")
}
// ConsoleClientIDColumn implements [domain.instanceColumns].
func (instance) ConsoleClientIDColumn() database.Column {
return database.NewColumn("instances", "console_client_id")
func (i instance) ConsoleClientIDColumn() database.Column {
return database.NewColumn(i.unqualifiedTableName(), "console_client_id")
}
// ConsoleAppIDColumn implements [domain.instanceColumns].
func (instance) ConsoleAppIDColumn() database.Column {
return database.NewColumn("instances", "console_app_id")
func (i instance) ConsoleAppIDColumn() database.Column {
return database.NewColumn(i.unqualifiedTableName(), "console_app_id")
}
// DefaultLanguageColumn implements [domain.instanceColumns].
func (instance) DefaultLanguageColumn() database.Column {
return database.NewColumn("instances", "default_language")
func (i instance) DefaultLanguageColumn() database.Column {
return database.NewColumn(i.unqualifiedTableName(), "default_language")
}
// UpdatedAtColumn implements [domain.instanceColumns].
func (instance) UpdatedAtColumn() database.Column {
return database.NewColumn("instances", "updated_at")
func (i instance) UpdatedAtColumn() database.Column {
return database.NewColumn(i.unqualifiedTableName(), "updated_at")
}
// -------------------------------------------------------------
@@ -282,19 +297,24 @@ func scanInstances(ctx context.Context, querier database.Querier, builder *datab
// sub repositories
// -------------------------------------------------------------
// Domains implements [domain.InstanceRepository].
func (i *instance) Domains(shouldLoad bool) domain.InstanceDomainRepository {
if !i.shouldLoadDomains {
i.shouldLoadDomains = shouldLoad
func (i *instance) LoadDomains() domain.InstanceRepository {
return &instance{
shouldLoadDomains: true,
}
}
if i.domainRepo != nil {
return i.domainRepo
func (i *instance) joinDomains() database.QueryOption {
columns := make([]database.Condition, 0, 2)
columns = append(columns, database.NewColumnCondition(i.IDColumn(), i.domainRepo.InstanceIDColumn()))
// If domains should not be joined, we make sure to return null for the domain columns
// the query optimizer of the dialect should optimize this away if no domains are requested
if !i.shouldLoadDomains {
columns = append(columns, database.IsNull(i.domainRepo.InstanceIDColumn()))
}
i.domainRepo = &instanceDomain{
repository: i.repository,
instance: i,
}
return i.domainRepo
return database.WithLeftJoin(
i.domainRepo.qualifiedTableName(),
database.And(columns...),
)
}
@@ -10,9 +10,18 @@ import (
var _ domain.InstanceDomainRepository = (*instanceDomain)(nil)
type instanceDomain struct {
repository
*instance
type instanceDomain struct{}
func InstanceDomainRepository() domain.InstanceDomainRepository {
return new(instanceDomain)
}
func (instanceDomain) qualifiedTableName() string {
return "zitadel.instance_domains"
}
func (instanceDomain) unqualifiedTableName() string {
return "instance_domains"
}
// -------------------------------------------------------------
@@ -23,8 +32,7 @@ const queryInstanceDomainStmt = `SELECT instance_domains.instance_id, instance_d
`FROM zitadel.instance_domains`
// Get implements [domain.InstanceDomainRepository].
// Subtle: this method shadows the method ([domain.InstanceRepository]).Get of instanceDomain.instance.
func (i *instanceDomain) Get(ctx context.Context, opts ...database.QueryOption) (*domain.InstanceDomain, error) {
func (i instanceDomain) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.InstanceDomain, error) {
options := new(database.QueryOpts)
for _, opt := range opts {
opt(options)
@@ -34,12 +42,11 @@ func (i *instanceDomain) Get(ctx context.Context, opts ...database.QueryOption)
builder.WriteString(queryInstanceDomainStmt)
options.Write(&builder)
return scanInstanceDomain(ctx, i.client, &builder)
return scanInstanceDomain(ctx, client, &builder)
}
// List implements [domain.InstanceDomainRepository].
// Subtle: this method shadows the method ([domain.InstanceRepository]).List of instanceDomain.instance.
func (i *instanceDomain) List(ctx context.Context, opts ...database.QueryOption) ([]*domain.InstanceDomain, error) {
func (i instanceDomain) List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*domain.InstanceDomain, error) {
options := new(database.QueryOpts)
for _, opt := range opts {
opt(options)
@@ -49,11 +56,11 @@ func (i *instanceDomain) List(ctx context.Context, opts ...database.QueryOption)
builder.WriteString(queryInstanceDomainStmt)
options.Write(&builder)
return scanInstanceDomains(ctx, i.client, &builder)
return scanInstanceDomains(ctx, client, &builder)
}
// Add implements [domain.InstanceDomainRepository].
func (i *instanceDomain) Add(ctx context.Context, domain *domain.AddInstanceDomain) error {
func (i instanceDomain) Add(ctx context.Context, client database.QueryExecutor, domain *domain.AddInstanceDomain) error {
var (
builder database.StatementBuilder
createdAt, updatedAt any = database.DefaultInstruction, database.DefaultInstruction
@@ -69,33 +76,41 @@ func (i *instanceDomain) Add(ctx context.Context, domain *domain.AddInstanceDoma
builder.WriteArgs(domain.InstanceID, domain.Domain, domain.IsPrimary, domain.IsGenerated, domain.Type, createdAt, updatedAt)
builder.WriteString(`) RETURNING created_at, updated_at`)
return i.client.QueryRow(ctx, builder.String(), builder.Args()...).Scan(&domain.CreatedAt, &domain.UpdatedAt)
return client.QueryRow(ctx, builder.String(), builder.Args()...).Scan(&domain.CreatedAt, &domain.UpdatedAt)
}
// Update implements [domain.InstanceDomainRepository].
// Subtle: this method shadows the method ([domain.InstanceRepository]).Update of instanceDomain.instance.
func (i *instanceDomain) Update(ctx context.Context, condition database.Condition, changes ...database.Change) (int64, error) {
func (i instanceDomain) Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, changes ...database.Change) (int64, error) {
if !condition.IsRestrictingColumn(i.InstanceIDColumn()) {
return 0, database.NewMissingConditionError(i.InstanceIDColumn())
}
if len(changes) == 0 {
return 0, database.ErrNoChanges
}
var builder database.StatementBuilder
if !database.Changes(changes).IsOnColumn(i.UpdatedAtColumn()) {
changes = append(changes, database.NewChange(i.UpdatedAtColumn(), database.NullInstruction))
}
var builder database.StatementBuilder
builder.WriteString(`UPDATE zitadel.instance_domains SET `)
database.Changes(changes).Write(&builder)
writeCondition(&builder, condition)
return i.client.Exec(ctx, builder.String(), builder.Args()...)
return client.Exec(ctx, builder.String(), builder.Args()...)
}
// Remove implements [domain.InstanceDomainRepository].
func (i *instanceDomain) Remove(ctx context.Context, condition database.Condition) (int64, error) {
var builder database.StatementBuilder
func (i instanceDomain) Remove(ctx context.Context, client database.QueryExecutor, condition database.Condition) (int64, error) {
if !condition.IsRestrictingColumn(i.InstanceIDColumn()) {
return 0, database.NewMissingConditionError(i.InstanceIDColumn())
}
var builder database.StatementBuilder
builder.WriteString(`DELETE FROM zitadel.instance_domains WHERE `)
condition.Write(&builder)
return i.client.Exec(ctx, builder.String(), builder.Args()...)
return client.Exec(ctx, builder.String(), builder.Args()...)
}
// -------------------------------------------------------------
@@ -146,40 +161,38 @@ func (i instanceDomain) TypeCondition(typ domain.DomainType) database.Condition
// -------------------------------------------------------------
// CreatedAtColumn implements [domain.InstanceDomainRepository].
// Subtle: this method shadows the method ([domain.InstanceRepository]).CreatedAtColumn of instanceDomain.instance.
func (instanceDomain) CreatedAtColumn() database.Column {
return database.NewColumn("instance_domains", "created_at")
func (i instanceDomain) CreatedAtColumn() database.Column {
return database.NewColumn(i.unqualifiedTableName(), "created_at")
}
// DomainColumn implements [domain.InstanceDomainRepository].
func (instanceDomain) DomainColumn() database.Column {
return database.NewColumn("instance_domains", "domain")
func (i instanceDomain) DomainColumn() database.Column {
return database.NewColumn(i.unqualifiedTableName(), "domain")
}
// InstanceIDColumn implements [domain.InstanceDomainRepository].
func (instanceDomain) InstanceIDColumn() database.Column {
return database.NewColumn("instance_domains", "instance_id")
func (i instanceDomain) InstanceIDColumn() database.Column {
return database.NewColumn(i.unqualifiedTableName(), "instance_id")
}
// IsPrimaryColumn implements [domain.InstanceDomainRepository].
func (instanceDomain) IsPrimaryColumn() database.Column {
return database.NewColumn("instance_domains", "is_primary")
func (i instanceDomain) IsPrimaryColumn() database.Column {
return database.NewColumn(i.unqualifiedTableName(), "is_primary")
}
// UpdatedAtColumn implements [domain.InstanceDomainRepository].
// Subtle: this method shadows the method ([domain.InstanceRepository]).UpdatedAtColumn of instanceDomain.instance.
func (instanceDomain) UpdatedAtColumn() database.Column {
return database.NewColumn("instance_domains", "updated_at")
func (i instanceDomain) UpdatedAtColumn() database.Column {
return database.NewColumn(i.unqualifiedTableName(), "updated_at")
}
// IsGeneratedColumn implements [domain.InstanceDomainRepository].
func (instanceDomain) IsGeneratedColumn() database.Column {
return database.NewColumn("instance_domains", "is_generated")
func (i instanceDomain) IsGeneratedColumn() database.Column {
return database.NewColumn(i.unqualifiedTableName(), "is_generated")
}
// TypeColumn implements [domain.InstanceDomainRepository].
func (instanceDomain) TypeColumn() database.Column {
return database.NewColumn("instance_domains", "type")
func (i instanceDomain) TypeColumn() database.Column {
return database.NewColumn(i.unqualifiedTableName(), "type")
}
// -------------------------------------------------------------
@@ -1,7 +1,6 @@
package repository_test
import (
"context"
"testing"
"time"
@@ -16,31 +15,43 @@ import (
)
func TestAddInstanceDomain(t *testing.T) {
// we take now here because the timestamp of the transaction is used to set the createdAt and updatedAt fields
beforeAdd := time.Now()
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer func() {
err = tx.Rollback(t.Context())
if err != nil {
t.Log("error during rollback:", err)
}
}()
instanceRepo := repository.InstanceRepository()
domainRepo := repository.InstanceDomainRepository()
// create instance
instanceID := gofakeit.UUID()
instance := domain.Instance{
ID: instanceID,
Name: gofakeit.Name(),
ID: gofakeit.NewCrypto().UUID(),
Name: gofakeit.BeerName(),
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
ConsoleClientID: "consoleClient",
ConsoleAppID: "consoleApp",
DefaultLanguage: "defaultLanguage",
}
instanceRepo := repository.InstanceRepository(pool)
err := instanceRepo.Create(t.Context(), &instance)
err = instanceRepo.Create(t.Context(), tx, &instance)
require.NoError(t, err)
tests := []struct {
name string
testFunc func(ctx context.Context, t *testing.T, domainRepo domain.InstanceDomainRepository) *domain.AddInstanceDomain
testFunc func(t *testing.T, tx database.QueryExecutor) *domain.AddInstanceDomain
instanceDomain domain.AddInstanceDomain
err error
}{
{
name: "happy path custom domain",
instanceDomain: domain.AddInstanceDomain{
InstanceID: instanceID,
InstanceID: instance.ID,
Domain: gofakeit.DomainName(),
Type: domain.DomainTypeCustom,
IsPrimary: gu.Ptr(false),
@@ -50,7 +61,7 @@ func TestAddInstanceDomain(t *testing.T) {
{
name: "happy path trusted domain",
instanceDomain: domain.AddInstanceDomain{
InstanceID: instanceID,
InstanceID: instance.ID,
Domain: gofakeit.DomainName(),
Type: domain.DomainTypeTrusted,
},
@@ -58,7 +69,7 @@ func TestAddInstanceDomain(t *testing.T) {
{
name: "add primary domain",
instanceDomain: domain.AddInstanceDomain{
InstanceID: instanceID,
InstanceID: instance.ID,
Domain: gofakeit.DomainName(),
Type: domain.DomainTypeCustom,
IsPrimary: gu.Ptr(true),
@@ -68,7 +79,7 @@ func TestAddInstanceDomain(t *testing.T) {
{
name: "add custom domain without domain name",
instanceDomain: domain.AddInstanceDomain{
InstanceID: instanceID,
InstanceID: instance.ID,
Domain: "",
Type: domain.DomainTypeCustom,
IsPrimary: gu.Ptr(false),
@@ -79,7 +90,7 @@ func TestAddInstanceDomain(t *testing.T) {
{
name: "add trusted domain without domain name",
instanceDomain: domain.AddInstanceDomain{
InstanceID: instanceID,
InstanceID: instance.ID,
Domain: "",
Type: domain.DomainTypeTrusted,
},
@@ -87,23 +98,23 @@ func TestAddInstanceDomain(t *testing.T) {
},
{
name: "add custom domain with same domain twice",
testFunc: func(ctx context.Context, t *testing.T, domainRepo domain.InstanceDomainRepository) *domain.AddInstanceDomain {
testFunc: func(t *testing.T, tx database.QueryExecutor) *domain.AddInstanceDomain {
domainName := gofakeit.DomainName()
instanceDomain := &domain.AddInstanceDomain{
InstanceID: instanceID,
InstanceID: instance.ID,
Domain: domainName,
Type: domain.DomainTypeCustom,
IsPrimary: gu.Ptr(false),
IsGenerated: gu.Ptr(false),
}
err := domainRepo.Add(ctx, instanceDomain)
err := domainRepo.Add(t.Context(), tx, instanceDomain)
require.NoError(t, err)
// return same domain again
return &domain.AddInstanceDomain{
InstanceID: instanceID,
InstanceID: instance.ID,
Domain: domainName,
Type: domain.DomainTypeCustom,
IsPrimary: gu.Ptr(false),
@@ -114,22 +125,20 @@ func TestAddInstanceDomain(t *testing.T) {
},
{
name: "add trusted domain with same domain twice",
testFunc: func(ctx context.Context, t *testing.T, domainRepo domain.InstanceDomainRepository) *domain.AddInstanceDomain {
domainName := gofakeit.DomainName()
testFunc: func(t *testing.T, tx database.QueryExecutor) *domain.AddInstanceDomain {
instanceDomain := &domain.AddInstanceDomain{
InstanceID: instanceID,
Domain: domainName,
InstanceID: instance.ID,
Domain: gofakeit.DomainName(),
Type: domain.DomainTypeTrusted,
}
err := domainRepo.Add(ctx, instanceDomain)
err := domainRepo.Add(t.Context(), tx, instanceDomain)
require.NoError(t, err)
// return same domain again
return &domain.AddInstanceDomain{
InstanceID: instanceID,
Domain: domainName,
InstanceID: instance.ID,
Domain: instanceDomain.Domain,
Type: domain.DomainTypeTrusted,
}
},
@@ -196,26 +205,23 @@ func TestAddInstanceDomain(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := t.Context()
// we take now here because the timestamp of the transaction is used to set the createdAt and updatedAt fields
beforeAdd := time.Now()
tx, err := pool.Begin(t.Context(), nil)
savepoint, err := tx.Begin(t.Context())
require.NoError(t, err)
defer func() {
require.NoError(t, tx.Rollback(t.Context()))
err := savepoint.Rollback(t.Context())
if err != nil {
t.Log("error during rollback:", err)
}
}()
instanceRepo := repository.InstanceRepository(tx)
domainRepo := instanceRepo.Domains(false)
var instanceDomain *domain.AddInstanceDomain
if test.testFunc != nil {
instanceDomain = test.testFunc(ctx, t, domainRepo)
instanceDomain = test.testFunc(t, savepoint)
} else {
instanceDomain = &test.instanceDomain
}
err = domainRepo.Add(ctx, instanceDomain)
err = domainRepo.Add(t.Context(), savepoint, instanceDomain)
afterAdd := time.Now()
if test.err != nil {
assert.ErrorIs(t, err, test.err)
@@ -232,10 +238,21 @@ func TestAddInstanceDomain(t *testing.T) {
}
func TestGetInstanceDomain(t *testing.T) {
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer func() {
err = tx.Rollback(t.Context())
if err != nil {
t.Log("error during rollback:", err)
}
}()
instanceRepo := repository.InstanceRepository()
domainRepo := repository.InstanceDomainRepository()
// create instance
instanceID := gofakeit.UUID()
instance := domain.Instance{
ID: instanceID,
ID: gofakeit.NewCrypto().UUID(),
Name: gofakeit.Name(),
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
@@ -243,38 +260,29 @@ func TestGetInstanceDomain(t *testing.T) {
ConsoleAppID: "consoleApp",
DefaultLanguage: "defaultLanguage",
}
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer func() {
require.NoError(t, tx.Rollback(t.Context()))
}()
instanceRepo := repository.InstanceRepository(tx)
err = instanceRepo.Create(t.Context(), &instance)
err = instanceRepo.Create(t.Context(), tx, &instance)
require.NoError(t, err)
// add domains
domainRepo := instanceRepo.Domains(false)
domainName1 := gofakeit.DomainName()
domainName2 := gofakeit.DomainName()
domain1 := &domain.AddInstanceDomain{
InstanceID: instanceID,
Domain: domainName1,
InstanceID: instance.ID,
Domain: gofakeit.DomainName(),
IsPrimary: gu.Ptr(true),
IsGenerated: gu.Ptr(false),
Type: domain.DomainTypeCustom,
}
domain2 := &domain.AddInstanceDomain{
InstanceID: instanceID,
Domain: domainName2,
InstanceID: instance.ID,
Domain: gofakeit.DomainName(),
IsPrimary: gu.Ptr(false),
IsGenerated: gu.Ptr(false),
Type: domain.DomainTypeCustom,
}
err = domainRepo.Add(t.Context(), domain1)
err = domainRepo.Add(t.Context(), tx, domain1)
require.NoError(t, err)
err = domainRepo.Add(t.Context(), domain2)
err = domainRepo.Add(t.Context(), tx, domain2)
require.NoError(t, err)
tests := []struct {
@@ -289,19 +297,19 @@ func TestGetInstanceDomain(t *testing.T) {
database.WithCondition(domainRepo.IsPrimaryCondition(true)),
},
expected: &domain.InstanceDomain{
InstanceID: instanceID,
Domain: domainName1,
InstanceID: instance.ID,
Domain: domain1.Domain,
IsPrimary: gu.Ptr(true),
},
},
{
name: "get by domain name",
opts: []database.QueryOption{
database.WithCondition(domainRepo.DomainCondition(database.TextOperationEqual, domainName2)),
database.WithCondition(domainRepo.DomainCondition(database.TextOperationEqual, domain2.Domain)),
},
expected: &domain.InstanceDomain{
InstanceID: instanceID,
Domain: domainName2,
InstanceID: instance.ID,
Domain: domain2.Domain,
IsPrimary: gu.Ptr(false),
},
},
@@ -318,7 +326,7 @@ func TestGetInstanceDomain(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
ctx := t.Context()
result, err := domainRepo.Get(ctx, test.opts...)
result, err := domainRepo.Get(ctx, tx, test.opts...)
if test.err != nil {
assert.ErrorIs(t, err, test.err)
return
@@ -335,10 +343,21 @@ func TestGetInstanceDomain(t *testing.T) {
}
func TestListInstanceDomains(t *testing.T) {
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer func() {
err = tx.Rollback(t.Context())
if err != nil {
t.Log("error during rollback:", err)
}
}()
instanceRepo := repository.InstanceRepository()
domainRepo := repository.InstanceDomainRepository()
// create instance
instanceID := gofakeit.UUID()
instance := domain.Instance{
ID: instanceID,
ID: gofakeit.NewCrypto().UUID(),
Name: gofakeit.Name(),
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
@@ -346,42 +365,35 @@ func TestListInstanceDomains(t *testing.T) {
ConsoleAppID: "consoleApp",
DefaultLanguage: "defaultLanguage",
}
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer func() {
require.NoError(t, tx.Rollback(t.Context()))
}()
instanceRepo := repository.InstanceRepository(tx)
err = instanceRepo.Create(t.Context(), &instance)
err = instanceRepo.Create(t.Context(), tx, &instance)
require.NoError(t, err)
// add multiple domains
domainRepo := instanceRepo.Domains(false)
domains := []domain.AddInstanceDomain{
{
InstanceID: instanceID,
InstanceID: instance.ID,
Domain: gofakeit.DomainName(),
IsPrimary: gu.Ptr(true),
IsGenerated: gu.Ptr(false),
Type: domain.DomainTypeCustom,
},
{
InstanceID: instanceID,
InstanceID: instance.ID,
Domain: gofakeit.DomainName(),
IsPrimary: gu.Ptr(false),
IsGenerated: gu.Ptr(false),
Type: domain.DomainTypeCustom,
},
{
InstanceID: instanceID,
InstanceID: instance.ID,
Domain: gofakeit.DomainName(),
Type: domain.DomainTypeTrusted,
},
}
for i := range domains {
err = domainRepo.Add(t.Context(), &domains[i])
err = domainRepo.Add(t.Context(), tx, &domains[i])
require.NoError(t, err)
}
@@ -405,7 +417,7 @@ func TestListInstanceDomains(t *testing.T) {
{
name: "list by instance",
opts: []database.QueryOption{
database.WithCondition(domainRepo.InstanceIDCondition(instanceID)),
database.WithCondition(domainRepo.InstanceIDCondition(instance.ID)),
},
expectedCount: 3,
},
@@ -422,12 +434,12 @@ func TestListInstanceDomains(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
ctx := t.Context()
results, err := domainRepo.List(ctx, test.opts...)
results, err := domainRepo.List(ctx, tx, test.opts...)
require.NoError(t, err)
assert.Len(t, results, test.expectedCount)
for _, result := range results {
assert.Equal(t, instanceID, result.InstanceID)
assert.Equal(t, instance.ID, result.InstanceID)
assert.NotEmpty(t, result.Domain)
assert.NotEmpty(t, result.CreatedAt)
assert.NotEmpty(t, result.UpdatedAt)
@@ -437,10 +449,21 @@ func TestListInstanceDomains(t *testing.T) {
}
func TestUpdateInstanceDomain(t *testing.T) {
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer func() {
err = tx.Rollback(t.Context())
if err != nil {
t.Log("error during rollback:", err)
}
}()
instanceRepo := repository.InstanceRepository()
domainRepo := repository.InstanceDomainRepository()
// create instance
instanceID := gofakeit.UUID()
instance := domain.Instance{
ID: instanceID,
ID: gofakeit.UUID(),
Name: gofakeit.Name(),
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
@@ -448,29 +471,19 @@ func TestUpdateInstanceDomain(t *testing.T) {
ConsoleAppID: "consoleApp",
DefaultLanguage: "defaultLanguage",
}
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer func() {
require.NoError(t, tx.Rollback(t.Context()))
}()
instanceRepo := repository.InstanceRepository(tx)
err = instanceRepo.Create(t.Context(), &instance)
err = instanceRepo.Create(t.Context(), tx, &instance)
require.NoError(t, err)
// add domain
domainRepo := instanceRepo.Domains(false)
domainName := gofakeit.DomainName()
instanceDomain := &domain.AddInstanceDomain{
InstanceID: instanceID,
Domain: domainName,
InstanceID: instance.ID,
Domain: gofakeit.DomainName(),
IsPrimary: gu.Ptr(false),
IsGenerated: gu.Ptr(false),
Type: domain.DomainTypeCustom,
}
err = domainRepo.Add(t.Context(), instanceDomain)
err = domainRepo.Add(t.Context(), tx, instanceDomain)
require.NoError(t, err)
tests := []struct {
@@ -481,31 +494,38 @@ func TestUpdateInstanceDomain(t *testing.T) {
err error
}{
{
name: "set primary",
condition: domainRepo.DomainCondition(database.TextOperationEqual, domainName),
changes: []database.Change{domainRepo.SetPrimary()},
expected: 1,
name: "set primary",
condition: database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.DomainCondition(database.TextOperationEqual, instanceDomain.Domain),
),
changes: []database.Change{domainRepo.SetPrimary()},
expected: 1,
},
{
name: "update non-existent domain",
condition: domainRepo.DomainCondition(database.TextOperationEqual, "non-existent.com"),
changes: []database.Change{domainRepo.SetPrimary()},
expected: 0,
name: "update non-existent domain",
condition: database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.DomainCondition(database.TextOperationEqual, "non-existent.com"),
),
changes: []database.Change{domainRepo.SetPrimary()},
expected: 0,
},
{
name: "no changes",
condition: domainRepo.DomainCondition(database.TextOperationEqual, domainName),
changes: []database.Change{},
expected: 0,
err: database.ErrNoChanges,
name: "no changes",
condition: database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.DomainCondition(database.TextOperationEqual, instanceDomain.Domain),
),
changes: []database.Change{},
expected: 0,
err: database.ErrNoChanges,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := t.Context()
rowsAffected, err := domainRepo.Update(ctx, test.condition, test.changes...)
rowsAffected, err := domainRepo.Update(t.Context(), tx, test.condition, test.changes...)
if test.err != nil {
assert.ErrorIs(t, err, test.err)
return
@@ -516,7 +536,7 @@ func TestUpdateInstanceDomain(t *testing.T) {
// verify changes were applied if rows were affected
if rowsAffected > 0 && len(test.changes) > 0 {
result, err := domainRepo.Get(ctx, database.WithCondition(test.condition))
result, err := domainRepo.Get(t.Context(), tx, database.WithCondition(test.condition))
require.NoError(t, err)
// We know changes were applied since rowsAffected > 0
@@ -529,10 +549,21 @@ func TestUpdateInstanceDomain(t *testing.T) {
}
func TestRemoveInstanceDomain(t *testing.T) {
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer func() {
err = tx.Rollback(t.Context())
if err != nil {
t.Log("error during rollback:", err)
}
}()
instanceRepo := repository.InstanceRepository()
domainRepo := repository.InstanceDomainRepository()
// create instance
instanceID := gofakeit.UUID()
instance := domain.Instance{
ID: instanceID,
ID: gofakeit.UUID(),
Name: gofakeit.Name(),
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
@@ -540,37 +571,28 @@ func TestRemoveInstanceDomain(t *testing.T) {
ConsoleAppID: "consoleApp",
DefaultLanguage: "defaultLanguage",
}
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer func() {
require.NoError(t, tx.Rollback(t.Context()))
}()
instanceRepo := repository.InstanceRepository(tx)
err = instanceRepo.Create(t.Context(), &instance)
err = instanceRepo.Create(t.Context(), tx, &instance)
require.NoError(t, err)
// add domains
domainRepo := instanceRepo.Domains(false)
domainName1 := gofakeit.DomainName()
domain1 := &domain.AddInstanceDomain{
InstanceID: instanceID,
Domain: domainName1,
InstanceID: instance.ID,
Domain: gofakeit.DomainName(),
IsPrimary: gu.Ptr(true),
IsGenerated: gu.Ptr(false),
Type: domain.DomainTypeCustom,
}
domain2 := &domain.AddInstanceDomain{
InstanceID: instanceID,
InstanceID: instance.ID,
Domain: gofakeit.DomainName(),
IsPrimary: gu.Ptr(false),
IsGenerated: gu.Ptr(false),
Type: domain.DomainTypeCustom,
}
err = domainRepo.Add(t.Context(), domain1)
err = domainRepo.Add(t.Context(), tx, domain1)
require.NoError(t, err)
err = domainRepo.Add(t.Context(), domain2)
err = domainRepo.Add(t.Context(), tx, domain2)
require.NoError(t, err)
tests := []struct {
@@ -579,36 +601,43 @@ func TestRemoveInstanceDomain(t *testing.T) {
expected int64
}{
{
name: "remove by domain name",
condition: domainRepo.DomainCondition(database.TextOperationEqual, domainName1),
expected: 1,
name: "remove by domain name",
condition: database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.DomainCondition(database.TextOperationEqual, domain1.Domain),
),
expected: 1,
},
{
name: "remove by primary condition",
condition: domainRepo.IsPrimaryCondition(false),
expected: 1, // domain2 should still exist and be non-primary
name: "remove by primary condition",
condition: database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.IsPrimaryCondition(false),
),
expected: 1, // domain2 should still exist and be non-primary
},
{
name: "remove non-existent domain",
condition: domainRepo.DomainCondition(database.TextOperationEqual, "non-existent.com"),
expected: 0,
name: "remove non-existent domain",
condition: database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.DomainCondition(database.TextOperationEqual, "non-existent.com"),
),
expected: 0,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := t.Context()
// count before removal
beforeCount, err := domainRepo.List(ctx)
beforeCount, err := domainRepo.List(t.Context(), tx)
require.NoError(t, err)
rowsAffected, err := domainRepo.Remove(ctx, test.condition)
rowsAffected, err := domainRepo.Remove(t.Context(), tx, test.condition)
require.NoError(t, err)
assert.Equal(t, test.expected, rowsAffected)
// verify removal
afterCount, err := domainRepo.List(ctx)
afterCount, err := domainRepo.List(t.Context(), tx)
require.NoError(t, err)
assert.Equal(t, len(beforeCount)-int(test.expected), len(afterCount))
})
@@ -616,8 +645,7 @@ func TestRemoveInstanceDomain(t *testing.T) {
}
func TestInstanceDomainConditions(t *testing.T) {
instanceRepo := repository.InstanceRepository(pool)
domainRepo := instanceRepo.Domains(false)
domainRepo := repository.InstanceDomainRepository()
tests := []struct {
name string
@@ -671,8 +699,7 @@ func TestInstanceDomainConditions(t *testing.T) {
}
func TestInstanceDomainChanges(t *testing.T) {
instanceRepo := repository.InstanceRepository(pool)
domainRepo := instanceRepo.Domains(false)
domainRepo := repository.InstanceDomainRepository()
tests := []struct {
name string
@@ -2,6 +2,7 @@ package repository_test
import (
"context"
"strconv"
"testing"
"time"
@@ -16,9 +17,20 @@ import (
)
func TestCreateInstance(t *testing.T) {
beforeCreate := time.Now()
tx, err := pool.Begin(context.Background(), nil)
require.NoError(t, err)
defer func() {
err := tx.Rollback(context.Background())
if err != nil {
t.Log("error during rollback:", err)
}
}()
instanceRepo := repository.InstanceRepository()
tests := []struct {
name string
testFunc func(ctx context.Context, t *testing.T) *domain.Instance
testFunc func(t *testing.T, tx database.QueryExecutor) *domain.Instance
instance domain.Instance
err error
}{
@@ -59,14 +71,10 @@ func TestCreateInstance(t *testing.T) {
},
{
name: "adding same instance twice",
testFunc: func(ctx context.Context, t *testing.T) *domain.Instance {
instanceRepo := repository.InstanceRepository(pool)
instanceId := gofakeit.Name()
instanceName := gofakeit.Name()
testFunc: func(t *testing.T, tx database.QueryExecutor) *domain.Instance {
inst := domain.Instance{
ID: instanceId,
Name: instanceName,
ID: gofakeit.UUID(),
Name: gofakeit.Name(),
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
ConsoleClientID: "consoleCLient",
@@ -74,7 +82,9 @@ func TestCreateInstance(t *testing.T) {
DefaultLanguage: "defaultLanguage",
}
err := instanceRepo.Create(ctx, &inst)
err := instanceRepo.Create(t.Context(), tx, &inst)
require.NoError(t, err)
// change the name to make sure same only the id clashes
inst.Name = gofakeit.Name()
require.NoError(t, err)
@@ -84,7 +94,7 @@ func TestCreateInstance(t *testing.T) {
},
func() struct {
name string
testFunc func(ctx context.Context, t *testing.T) *domain.Instance
testFunc func(t *testing.T, tx database.QueryExecutor) *domain.Instance
instance domain.Instance
err error
} {
@@ -92,14 +102,12 @@ func TestCreateInstance(t *testing.T) {
instanceName := gofakeit.Name()
return struct {
name string
testFunc func(ctx context.Context, t *testing.T) *domain.Instance
testFunc func(t *testing.T, tx database.QueryExecutor) *domain.Instance
instance domain.Instance
err error
}{
name: "adding instance with same name twice",
testFunc: func(ctx context.Context, t *testing.T) *domain.Instance {
instanceRepo := repository.InstanceRepository(pool)
testFunc: func(t *testing.T, tx database.QueryExecutor) *domain.Instance {
inst := domain.Instance{
ID: gofakeit.Name(),
Name: instanceName,
@@ -110,7 +118,7 @@ func TestCreateInstance(t *testing.T) {
DefaultLanguage: "defaultLanguage",
}
err := instanceRepo.Create(ctx, &inst)
err := instanceRepo.Create(t.Context(), tx, &inst)
require.NoError(t, err)
// change the id
@@ -135,11 +143,8 @@ func TestCreateInstance(t *testing.T) {
{
name: "adding instance with no id",
instance: func() domain.Instance {
// instanceId := gofakeit.Name()
instanceName := gofakeit.Name()
instance := domain.Instance{
// ID: instanceId,
Name: instanceName,
Name: gofakeit.Name(),
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
ConsoleClientID: "consoleCLient",
@@ -153,19 +158,25 @@ func TestCreateInstance(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
savepoint, err := tx.Begin(t.Context())
require.NoError(t, err)
defer func() {
err = savepoint.Rollback(t.Context())
if err != nil {
t.Log("error during rollback:", err)
}
}()
var instance *domain.Instance
if tt.testFunc != nil {
instance = tt.testFunc(ctx, t)
instance = tt.testFunc(t, savepoint)
} else {
instance = &tt.instance
}
instanceRepo := repository.InstanceRepository(pool)
// create instance
beforeCreate := time.Now()
err := instanceRepo.Create(ctx, instance)
err = instanceRepo.Create(t.Context(), tx, instance)
assert.ErrorIs(t, err, tt.err)
if err != nil {
return
@@ -173,7 +184,7 @@ func TestCreateInstance(t *testing.T) {
afterCreate := time.Now()
// check instance values
instance, err = instanceRepo.Get(ctx,
instance, err = instanceRepo.Get(t.Context(), tx,
database.WithCondition(
instanceRepo.IDCondition(instance.ID),
),
@@ -194,22 +205,30 @@ func TestCreateInstance(t *testing.T) {
}
func TestUpdateInstance(t *testing.T) {
beforeUpdate := time.Now()
tx, err := pool.Begin(context.Background(), nil)
require.NoError(t, err)
defer func() {
err := tx.Rollback(context.Background())
if err != nil {
t.Log("error during rollback:", err)
}
}()
instanceRepo := repository.InstanceRepository()
tests := []struct {
name string
testFunc func(ctx context.Context, t *testing.T) *domain.Instance
testFunc func(t *testing.T, tx database.QueryExecutor) *domain.Instance
rowsAffected int64
getErr error
}{
{
name: "happy path",
testFunc: func(ctx context.Context, t *testing.T) *domain.Instance {
instanceRepo := repository.InstanceRepository(pool)
instanceId := gofakeit.Name()
instanceName := gofakeit.Name()
testFunc: func(t *testing.T, tx database.QueryExecutor) *domain.Instance {
inst := domain.Instance{
ID: instanceId,
Name: instanceName,
ID: gofakeit.UUID(),
Name: gofakeit.Name(),
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
ConsoleClientID: "consoleCLient",
@@ -218,7 +237,7 @@ func TestUpdateInstance(t *testing.T) {
}
// create instance
err := instanceRepo.Create(ctx, &inst)
err := instanceRepo.Create(t.Context(), tx, &inst)
require.NoError(t, err)
return &inst
},
@@ -226,14 +245,10 @@ func TestUpdateInstance(t *testing.T) {
},
{
name: "update deleted instance",
testFunc: func(ctx context.Context, t *testing.T) *domain.Instance {
instanceRepo := repository.InstanceRepository(pool)
instanceId := gofakeit.Name()
instanceName := gofakeit.Name()
testFunc: func(t *testing.T, tx database.QueryExecutor) *domain.Instance {
inst := domain.Instance{
ID: instanceId,
Name: instanceName,
ID: gofakeit.UUID(),
Name: gofakeit.Name(),
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
ConsoleClientID: "consoleCLient",
@@ -242,11 +257,11 @@ func TestUpdateInstance(t *testing.T) {
}
// create instance
err := instanceRepo.Create(ctx, &inst)
err := instanceRepo.Create(t.Context(), tx, &inst)
require.NoError(t, err)
// delete instance
affectedRows, err := instanceRepo.Delete(ctx,
affectedRows, err := instanceRepo.Delete(t.Context(), tx,
inst.ID,
)
require.NoError(t, err)
@@ -258,11 +273,9 @@ func TestUpdateInstance(t *testing.T) {
},
{
name: "update non existent instance",
testFunc: func(ctx context.Context, t *testing.T) *domain.Instance {
instanceId := gofakeit.Name()
testFunc: func(t *testing.T, tx database.QueryExecutor) *domain.Instance {
inst := domain.Instance{
ID: instanceId,
ID: gofakeit.UUID(),
}
return &inst
},
@@ -272,15 +285,11 @@ func TestUpdateInstance(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
instanceRepo := repository.InstanceRepository(pool)
instance := tt.testFunc(t, tx)
instance := tt.testFunc(ctx, t)
beforeUpdate := time.Now()
// update name
newName := "new_" + instance.Name
rowsAffected, err := instanceRepo.Update(ctx,
rowsAffected, err := instanceRepo.Update(t.Context(), tx,
instance.ID,
instanceRepo.SetName(newName),
)
@@ -294,7 +303,7 @@ func TestUpdateInstance(t *testing.T) {
}
// check instance values
instance, err = instanceRepo.Get(ctx,
instance, err = instanceRepo.Get(t.Context(), tx,
database.WithCondition(
instanceRepo.IDCondition(instance.ID),
),
@@ -308,24 +317,31 @@ func TestUpdateInstance(t *testing.T) {
}
func TestGetInstance(t *testing.T) {
instanceRepo := repository.InstanceRepository(pool)
tx, err := pool.Begin(context.Background(), nil)
require.NoError(t, err)
defer func() {
err := tx.Rollback(context.Background())
if err != nil {
t.Log("error during rollback:", err)
}
}()
instanceRepo := repository.InstanceRepository()
domainRepo := repository.InstanceDomainRepository()
type test struct {
name string
testFunc func(ctx context.Context, t *testing.T) *domain.Instance
testFunc func(t *testing.T) *domain.Instance
err error
}
tests := []test{
func() test {
instanceId := gofakeit.Name()
return test{
name: "happy path get using id",
testFunc: func(ctx context.Context, t *testing.T) *domain.Instance {
instanceName := gofakeit.Name()
testFunc: func(t *testing.T) *domain.Instance {
inst := domain.Instance{
ID: instanceId,
Name: instanceName,
ID: gofakeit.UUID(),
Name: gofakeit.BeerName(),
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
ConsoleClientID: "consoleCLient",
@@ -334,7 +350,7 @@ func TestGetInstance(t *testing.T) {
}
// create instance
err := instanceRepo.Create(ctx, &inst)
err := instanceRepo.Create(t.Context(), tx, &inst)
require.NoError(t, err)
return &inst
},
@@ -342,14 +358,10 @@ func TestGetInstance(t *testing.T) {
}(),
{
name: "happy path including domains",
testFunc: func(ctx context.Context, t *testing.T) *domain.Instance {
instanceRepo := repository.InstanceRepository(pool)
instanceId := gofakeit.Name()
instanceName := gofakeit.Name()
testFunc: func(t *testing.T) *domain.Instance {
inst := domain.Instance{
ID: instanceId,
Name: instanceName,
ID: gofakeit.NewCrypto().UUID(),
Name: gofakeit.BeerName(),
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
ConsoleClientID: "consoleCLient",
@@ -358,10 +370,9 @@ func TestGetInstance(t *testing.T) {
}
// create instance
err := instanceRepo.Create(ctx, &inst)
err := instanceRepo.Create(t.Context(), tx, &inst)
require.NoError(t, err)
domainRepo := instanceRepo.Domains(false)
d := &domain.AddInstanceDomain{
InstanceID: inst.ID,
Domain: gofakeit.DomainName(),
@@ -369,7 +380,7 @@ func TestGetInstance(t *testing.T) {
IsGenerated: gu.Ptr(false),
Type: domain.DomainTypeCustom,
}
err = domainRepo.Add(ctx, d)
err = domainRepo.Add(t.Context(), tx, d)
require.NoError(t, err)
inst.Domains = append(inst.Domains, &domain.InstanceDomain{
@@ -387,7 +398,7 @@ func TestGetInstance(t *testing.T) {
},
{
name: "get non existent instance",
testFunc: func(ctx context.Context, t *testing.T) *domain.Instance {
testFunc: func(t *testing.T) *domain.Instance {
inst := domain.Instance{
ID: "get non existent instance",
}
@@ -398,16 +409,13 @@ func TestGetInstance(t *testing.T) {
}
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(ctx, t)
instance = tt.testFunc(t)
}
// check instance values
returnedInstance, err := instanceRepo.Get(ctx,
returnedInstance, err := instanceRepo.Get(t.Context(), tx,
database.WithCondition(
instanceRepo.IDCondition(instance.ID),
),
@@ -434,28 +442,33 @@ func TestGetInstance(t *testing.T) {
}
func TestListInstance(t *testing.T) {
ctx := context.Background()
pool, stop, err := newEmbeddedDB(ctx)
tx, err := pool.Begin(context.Background(), nil)
require.NoError(t, err)
defer stop()
defer func() {
err := tx.Rollback(context.Background())
if err != nil {
t.Log("error during rollback:", err)
}
}()
instanceRepo := repository.InstanceRepository()
type test struct {
name string
testFunc func(ctx context.Context, t *testing.T) []*domain.Instance
testFunc func(t *testing.T, tx database.QueryExecutor) []*domain.Instance
conditionClauses []database.Condition
noInstanceReturned bool
}
tests := []test{
{
name: "happy path single instance no filter",
testFunc: func(ctx context.Context, t *testing.T) []*domain.Instance {
instanceRepo := repository.InstanceRepository(pool)
testFunc: func(t *testing.T, tx database.QueryExecutor) []*domain.Instance {
noOfInstances := 1
instances := make([]*domain.Instance, noOfInstances)
for i := range noOfInstances {
inst := domain.Instance{
ID: gofakeit.Name(),
ID: strconv.Itoa(i),
Name: gofakeit.Name(),
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
@@ -465,7 +478,7 @@ func TestListInstance(t *testing.T) {
}
// create instance
err := instanceRepo.Create(ctx, &inst)
err := instanceRepo.Create(t.Context(), tx, &inst)
require.NoError(t, err)
instances[i] = &inst
@@ -476,14 +489,13 @@ func TestListInstance(t *testing.T) {
},
{
name: "happy path multiple instance no filter",
testFunc: func(ctx context.Context, t *testing.T) []*domain.Instance {
instanceRepo := repository.InstanceRepository(pool)
testFunc: func(t *testing.T, tx database.QueryExecutor) []*domain.Instance {
noOfInstances := 5
instances := make([]*domain.Instance, noOfInstances)
for i := range noOfInstances {
inst := domain.Instance{
ID: gofakeit.Name(),
ID: strconv.Itoa(i),
Name: gofakeit.Name(),
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
@@ -493,7 +505,7 @@ func TestListInstance(t *testing.T) {
}
// create instance
err := instanceRepo.Create(ctx, &inst)
err := instanceRepo.Create(t.Context(), tx, &inst)
require.NoError(t, err)
instances[i] = &inst
@@ -503,17 +515,16 @@ func TestListInstance(t *testing.T) {
},
},
func() test {
instanceRepo := repository.InstanceRepository(pool)
instanceId := gofakeit.Name()
instanceID := gofakeit.BeerName()
return test{
name: "instance filter on id",
testFunc: func(ctx context.Context, t *testing.T) []*domain.Instance {
testFunc: func(t *testing.T, tx database.QueryExecutor) []*domain.Instance {
noOfInstances := 1
instances := make([]*domain.Instance, noOfInstances)
for i := range noOfInstances {
inst := domain.Instance{
ID: instanceId,
ID: instanceID,
Name: gofakeit.Name(),
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
@@ -523,7 +534,7 @@ func TestListInstance(t *testing.T) {
}
// create instance
err := instanceRepo.Create(ctx, &inst)
err := instanceRepo.Create(t.Context(), tx, &inst)
require.NoError(t, err)
instances[i] = &inst
@@ -531,21 +542,20 @@ func TestListInstance(t *testing.T) {
return instances
},
conditionClauses: []database.Condition{instanceRepo.IDCondition(instanceId)},
conditionClauses: []database.Condition{instanceRepo.IDCondition(instanceID)},
}
}(),
func() test {
instanceRepo := repository.InstanceRepository(pool)
instanceName := gofakeit.Name()
instanceName := gofakeit.BeerName()
return test{
name: "multiple instance filter on name",
testFunc: func(ctx context.Context, t *testing.T) []*domain.Instance {
testFunc: func(t *testing.T, tx database.QueryExecutor) []*domain.Instance {
noOfInstances := 5
instances := make([]*domain.Instance, noOfInstances)
for i := range noOfInstances {
inst := domain.Instance{
ID: gofakeit.Name(),
ID: strconv.Itoa(i),
Name: instanceName,
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
@@ -555,7 +565,7 @@ func TestListInstance(t *testing.T) {
}
// create instance
err := instanceRepo.Create(ctx, &inst)
err := instanceRepo.Create(t.Context(), tx, &inst)
require.NoError(t, err)
instances[i] = &inst
@@ -569,14 +579,15 @@ func TestListInstance(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Cleanup(func() {
_, err := pool.Exec(ctx, "DELETE FROM zitadel.instances")
require.NoError(t, err)
})
instances := tt.testFunc(ctx, t)
instanceRepo := repository.InstanceRepository(pool)
savepoint, err := tx.Begin(t.Context())
require.NoError(t, err)
defer func() {
err = savepoint.Rollback(t.Context())
if err != nil {
t.Log("error during rollback:", err)
}
}()
instances := tt.testFunc(t, savepoint)
var condition database.Condition
if len(tt.conditionClauses) > 0 {
@@ -584,13 +595,13 @@ func TestListInstance(t *testing.T) {
}
// check instance values
returnedInstances, err := instanceRepo.List(ctx,
returnedInstances, err := instanceRepo.List(t.Context(), tx,
database.WithCondition(condition),
database.WithOrderByAscending(instanceRepo.CreatedAtColumn()),
database.WithOrderByAscending(instanceRepo.IDColumn()),
)
require.NoError(t, err)
if tt.noInstanceReturned {
assert.Nil(t, returnedInstances)
assert.Len(t, returnedInstances, 0)
return
}
@@ -609,42 +620,45 @@ func TestListInstance(t *testing.T) {
}
func TestDeleteInstance(t *testing.T) {
tx, err := pool.Begin(context.Background(), nil)
require.NoError(t, err)
defer func() {
err := tx.Rollback(context.Background())
if err != nil {
t.Log("error during rollback:", err)
}
}()
instanceRepo := repository.InstanceRepository()
type test struct {
name string
testFunc func(ctx context.Context, t *testing.T)
testFunc func(t *testing.T, tx database.QueryExecutor)
instanceID string
noOfDeletedRows int64
}
tests := []test{
func() test {
instanceRepo := repository.InstanceRepository(pool)
instanceId := gofakeit.Name()
var noOfInstances int64 = 1
instanceID := gofakeit.NewCrypto().UUID()
return test{
name: "happy path delete single instance filter id",
testFunc: func(ctx context.Context, t *testing.T) {
instances := make([]*domain.Instance, noOfInstances)
for i := range noOfInstances {
inst := domain.Instance{
ID: instanceId,
Name: gofakeit.Name(),
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
ConsoleClientID: "consoleCLient",
ConsoleAppID: "consoleApp",
DefaultLanguage: "defaultLanguage",
}
// create instance
err := instanceRepo.Create(ctx, &inst)
require.NoError(t, err)
instances[i] = &inst
testFunc: func(t *testing.T, tx database.QueryExecutor) {
inst := domain.Instance{
ID: instanceID,
Name: gofakeit.Name(),
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
ConsoleClientID: "consoleCLient",
ConsoleAppID: "consoleApp",
DefaultLanguage: "defaultLanguage",
}
// create instance
err := instanceRepo.Create(t.Context(), tx, &inst)
require.NoError(t, err)
},
instanceID: instanceId,
noOfDeletedRows: noOfInstances,
instanceID: instanceID,
noOfDeletedRows: 1,
}
}(),
func() test {
@@ -655,40 +669,33 @@ func TestDeleteInstance(t *testing.T) {
}
}(),
func() test {
instanceRepo := repository.InstanceRepository(pool)
instanceName := gofakeit.Name()
instanceID := gofakeit.Name()
return test{
name: "deleted already deleted instance",
testFunc: func(ctx context.Context, t *testing.T) {
noOfInstances := 1
instances := make([]*domain.Instance, noOfInstances)
for i := range noOfInstances {
testFunc: func(t *testing.T, tx database.QueryExecutor) {
inst := domain.Instance{
ID: gofakeit.Name(),
Name: instanceName,
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
ConsoleClientID: "consoleCLient",
ConsoleAppID: "consoleApp",
DefaultLanguage: "defaultLanguage",
}
// create instance
err := instanceRepo.Create(ctx, &inst)
require.NoError(t, err)
instances[i] = &inst
inst := domain.Instance{
ID: instanceID,
Name: gofakeit.BeerName(),
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
ConsoleClientID: "consoleCLient",
ConsoleAppID: "consoleApp",
DefaultLanguage: "defaultLanguage",
}
// create instance
err := instanceRepo.Create(t.Context(), tx, &inst)
require.NoError(t, err)
// delete instance
affectedRows, err := instanceRepo.Delete(ctx,
instances[0].ID,
affectedRows, err := instanceRepo.Delete(t.Context(), tx,
inst.ID,
)
require.NoError(t, err)
assert.Equal(t, int64(1), affectedRows)
},
instanceID: instanceName,
instanceID: instanceID,
// this test should return 0 affected rows as the instance was already deleted
noOfDeletedRows: 0,
}
@@ -696,22 +703,26 @@ func TestDeleteInstance(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
instanceRepo := repository.InstanceRepository(pool)
savepoint, err := tx.Begin(t.Context())
require.NoError(t, err)
defer func() {
err = savepoint.Rollback(t.Context())
if err != nil {
t.Log("error during rollback:", err)
}
}()
if tt.testFunc != nil {
tt.testFunc(ctx, t)
tt.testFunc(t, savepoint)
}
// delete instance
noOfDeletedRows, err := instanceRepo.Delete(ctx,
tt.instanceID,
)
noOfDeletedRows, err := instanceRepo.Delete(t.Context(), savepoint, tt.instanceID)
require.NoError(t, err)
assert.Equal(t, noOfDeletedRows, tt.noOfDeletedRows)
// check instance was deleted
instance, err := instanceRepo.Get(ctx,
instance, err := instanceRepo.Get(t.Context(), savepoint,
database.WithCondition(
instanceRepo.IDCondition(tt.instanceID),
),
+97 -76
View File
@@ -14,25 +14,24 @@ import (
var _ domain.OrganizationRepository = (*org)(nil)
type org struct {
repository
shouldLoadDomains bool
domainRepo domain.OrganizationDomainRepository
domainRepo orgDomain
}
func OrganizationRepository(client database.QueryExecutor) domain.OrganizationRepository {
return &org{
repository: repository{
client: client,
},
}
func (o org) unqualifiedTableName() string {
return "organizations"
}
func OrganizationRepository() domain.OrganizationRepository {
return new(org)
}
const queryOrganizationStmt = `SELECT organizations.id, organizations.name, organizations.instance_id, organizations.state, organizations.created_at, organizations.updated_at` +
` , jsonb_agg(json_build_object('domain', org_domains.domain, 'isVerified', org_domains.is_verified, 'isPrimary', org_domains.is_primary, 'validationType', org_domains.validation_type, 'createdAt', org_domains.created_at, 'updatedAt', org_domains.updated_at)) FILTER (WHERE org_domains.org_id IS NOT NULL) AS domains` +
` , jsonb_agg(json_build_object('instanceId', org_domains.instance_id, 'orgId', org_domains.org_id, 'domain', org_domains.domain, 'isVerified', org_domains.is_verified, 'isPrimary', org_domains.is_primary, 'validationType', org_domains.validation_type, 'createdAt', org_domains.created_at, 'updatedAt', org_domains.updated_at)) FILTER (WHERE org_domains.org_id IS NOT NULL) AS domains` +
` FROM zitadel.organizations`
// Get implements [domain.OrganizationRepository].
func (o *org) Get(ctx context.Context, opts ...database.QueryOption) (*domain.Organization, error) {
func (o org) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.Organization, error) {
opts = append(opts,
o.joinDomains(),
database.WithGroupBy(o.InstanceIDColumn(), o.IDColumn()),
@@ -43,15 +42,19 @@ func (o *org) Get(ctx context.Context, opts ...database.QueryOption) (*domain.Or
opt(options)
}
if !options.Condition.IsRestrictingColumn(o.InstanceIDColumn()) {
return nil, database.NewMissingConditionError(o.InstanceIDColumn())
}
var builder database.StatementBuilder
builder.WriteString(queryOrganizationStmt)
options.Write(&builder)
return scanOrganization(ctx, o.client, &builder)
return scanOrganization(ctx, client, &builder)
}
// List implements [domain.OrganizationRepository].
func (o *org) List(ctx context.Context, opts ...database.QueryOption) ([]*domain.Organization, error) {
func (o org) List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*domain.Organization, error) {
opts = append(opts,
o.joinDomains(),
database.WithGroupBy(o.InstanceIDColumn(), o.IDColumn()),
@@ -62,30 +65,15 @@ func (o *org) List(ctx context.Context, opts ...database.QueryOption) ([]*domain
opt(options)
}
if !options.Condition.IsRestrictingColumn(o.InstanceIDColumn()) {
return nil, database.NewMissingConditionError(o.InstanceIDColumn())
}
var builder database.StatementBuilder
builder.WriteString(queryOrganizationStmt)
options.Write(&builder)
return scanOrganizations(ctx, o.client, &builder)
}
func (o *org) joinDomains() database.QueryOption {
columns := make([]database.Condition, 0, 3)
columns = append(columns,
database.NewColumnCondition(o.InstanceIDColumn(), o.Domains(false).InstanceIDColumn()),
database.NewColumnCondition(o.IDColumn(), o.Domains(false).OrgIDColumn()),
)
// If domains should not be joined, we make sure to return null for the domain columns
// the query optimizer of the dialect should optimize this away if no domains are requested
if !o.shouldLoadDomains {
columns = append(columns, database.IsNull(o.domainRepo.OrgIDColumn()))
}
return database.WithLeftJoin(
"zitadel.org_domains",
database.And(columns...),
)
return scanOrganizations(ctx, client, &builder)
}
const createOrganizationStmt = `INSERT INTO zitadel.organizations (id, name, instance_id, state)` +
@@ -93,46 +81,48 @@ const createOrganizationStmt = `INSERT INTO zitadel.organizations (id, name, ins
` RETURNING created_at, updated_at`
// Create implements [domain.OrganizationRepository].
func (o *org) Create(ctx context.Context, organization *domain.Organization) error {
func (o org) Create(ctx context.Context, client database.QueryExecutor, organization *domain.Organization) error {
builder := database.StatementBuilder{}
builder.AppendArgs(organization.ID, organization.Name, organization.InstanceID, organization.State)
builder.WriteString(createOrganizationStmt)
return o.client.QueryRow(ctx, builder.String(), builder.Args()...).Scan(&organization.CreatedAt, &organization.UpdatedAt)
return client.QueryRow(ctx, builder.String(), builder.Args()...).Scan(&organization.CreatedAt, &organization.UpdatedAt)
}
// Update implements [domain.OrganizationRepository].
func (o *org) Update(ctx context.Context, id domain.OrgIdentifierCondition, instanceID string, changes ...database.Change) (int64, error) {
func (o org) Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, changes ...database.Change) (int64, error) {
if len(changes) == 0 {
return 0, database.ErrNoChanges
}
builder := database.StatementBuilder{}
if !condition.IsRestrictingColumn(o.InstanceIDColumn()) {
return 0, database.NewMissingConditionError(o.InstanceIDColumn())
}
if !database.Changes(changes).IsOnColumn(o.UpdatedAtColumn()) {
changes = append(changes, database.NewChange(o.UpdatedAtColumn(), database.NullInstruction))
}
var builder database.StatementBuilder
builder.WriteString(`UPDATE zitadel.organizations SET `)
instanceIDCondition := o.InstanceIDCondition(instanceID)
conditions := []database.Condition{id, instanceIDCondition}
database.Changes(changes).Write(&builder)
writeCondition(&builder, database.And(conditions...))
writeCondition(&builder, condition)
stmt := builder.String()
rowsAffected, err := o.client.Exec(ctx, stmt, builder.Args()...)
rowsAffected, err := client.Exec(ctx, stmt, builder.Args()...)
return rowsAffected, err
}
// Delete implements [domain.OrganizationRepository].
func (o *org) Delete(ctx context.Context, id domain.OrgIdentifierCondition, instanceID string) (int64, error) {
builder := database.StatementBuilder{}
func (o org) Delete(ctx context.Context, client database.QueryExecutor, condition database.Condition) (int64, error) {
if !condition.IsRestrictingColumn(o.InstanceIDColumn()) {
return 0, database.NewMissingConditionError(o.InstanceIDColumn())
}
var builder database.StatementBuilder
builder.WriteString(`DELETE FROM zitadel.organizations`)
writeCondition(&builder, condition)
instanceIDCondition := o.InstanceIDCondition(instanceID)
conditions := []database.Condition{id, instanceIDCondition}
writeCondition(&builder, database.And(conditions...))
return o.client.Exec(ctx, builder.String(), builder.Args()...)
return client.Exec(ctx, builder.String(), builder.Args()...)
}
// -------------------------------------------------------------
@@ -154,13 +144,13 @@ func (o org) SetState(state domain.OrgState) database.Change {
// -------------------------------------------------------------
// IDCondition implements [domain.organizationConditions].
func (o org) IDCondition(id string) domain.OrgIdentifierCondition {
func (o org) IDCondition(id string) database.Condition {
return database.NewTextCondition(o.IDColumn(), database.TextOperationEqual, id)
}
// NameCondition implements [domain.organizationConditions].
func (o org) NameCondition(name string) domain.OrgIdentifierCondition {
return database.NewTextCondition(o.NameColumn(), database.TextOperationEqual, name)
func (o org) NameCondition(op database.TextOperation, name string) database.Condition {
return database.NewTextCondition(o.NameColumn(), op, name)
}
// InstanceIDCondition implements [domain.organizationConditions].
@@ -173,38 +163,62 @@ func (o org) StateCondition(state domain.OrgState) database.Condition {
return database.NewTextCondition(o.StateColumn(), database.TextOperationEqual, state.String())
}
// ExistsDomain creates a correlated [database.Exists] condition on org_domains.
// Use this filter to make sure the organization returned contains a specific domain.
// Example usage:
//
// domainRepo := orgRepo.Domains(true) // ensure domains are loaded/aggregated
// org, _ := orgRepo.Get(ctx,
// database.WithCondition(
// database.And(
// orgRepo.InstanceIDCondition(instanceID),
// orgRepo.ExistsDomain(domainRepo.DomainCondition(database.TextOperationEqual, "example.com")),
// ),
// ),
// )
func (o org) ExistsDomain(cond database.Condition) database.Condition {
return database.Exists(
o.domainRepo.qualifiedTableName(),
database.And(
database.NewColumnCondition(o.InstanceIDColumn(), o.domainRepo.InstanceIDColumn()),
database.NewColumnCondition(o.IDColumn(), o.domainRepo.OrgIDColumn()),
cond,
),
)
}
// -------------------------------------------------------------
// columns
// -------------------------------------------------------------
// IDColumn implements [domain.organizationColumns].
func (org) IDColumn() database.Column {
return database.NewColumn("organizations", "id")
func (o org) IDColumn() database.Column {
return database.NewColumn(o.unqualifiedTableName(), "id")
}
// NameColumn implements [domain.organizationColumns].
func (org) NameColumn() database.Column {
return database.NewColumn("organizations", "name")
func (o org) NameColumn() database.Column {
return database.NewColumn(o.unqualifiedTableName(), "name")
}
// InstanceIDColumn implements [domain.organizationColumns].
func (org) InstanceIDColumn() database.Column {
return database.NewColumn("organizations", "instance_id")
func (o org) InstanceIDColumn() database.Column {
return database.NewColumn(o.unqualifiedTableName(), "instance_id")
}
// StateColumn implements [domain.organizationColumns].
func (org) StateColumn() database.Column {
return database.NewColumn("organizations", "state")
func (o org) StateColumn() database.Column {
return database.NewColumn(o.unqualifiedTableName(), "state")
}
// CreatedAtColumn implements [domain.organizationColumns].
func (org) CreatedAtColumn() database.Column {
return database.NewColumn("organizations", "created_at")
func (o org) CreatedAtColumn() database.Column {
return database.NewColumn(o.unqualifiedTableName(), "created_at")
}
// UpdatedAtColumn implements [domain.organizationColumns].
func (org) UpdatedAtColumn() database.Column {
return database.NewColumn("organizations", "updated_at")
func (o org) UpdatedAtColumn() database.Column {
return database.NewColumn(o.unqualifiedTableName(), "updated_at")
}
// -------------------------------------------------------------
@@ -255,20 +269,27 @@ func scanOrganizations(ctx context.Context, querier database.Querier, builder *d
// sub repositories
// -------------------------------------------------------------
// Domains implements [domain.OrganizationRepository].
func (o *org) Domains(shouldLoad bool) domain.OrganizationDomainRepository {
if !o.shouldLoadDomains {
o.shouldLoadDomains = shouldLoad
func (o org) LoadDomains() domain.OrganizationRepository {
return &org{
shouldLoadDomains: true,
}
}
if o.domainRepo != nil {
return o.domainRepo
}
func (o org) joinDomains() database.QueryOption {
columns := make([]database.Condition, 0, 3)
columns = append(columns,
database.NewColumnCondition(o.InstanceIDColumn(), o.domainRepo.InstanceIDColumn()),
database.NewColumnCondition(o.IDColumn(), o.domainRepo.OrgIDColumn()),
)
o.domainRepo = &orgDomain{
repository: o.repository,
org: o,
// If domains should not be joined, we make sure to return null for the domain columns
// the query optimizer of the dialect should optimize this away if no domains are requested
if !o.shouldLoadDomains {
columns = append(columns, database.IsNull(o.domainRepo.OrgIDColumn()))
}
return o.domainRepo
return database.WithLeftJoin(
o.domainRepo.qualifiedTableName(),
database.And(columns...),
)
}
@@ -10,9 +10,18 @@ import (
var _ domain.OrganizationDomainRepository = (*orgDomain)(nil)
type orgDomain struct {
repository
*org
type orgDomain struct{}
func OrganizationDomainRepository() domain.OrganizationDomainRepository {
return new(orgDomain)
}
func (orgDomain) qualifiedTableName() string {
return "zitadel.org_domains"
}
func (orgDomain) unqualifiedTableName() string {
return "org_domains"
}
// -------------------------------------------------------------
@@ -24,36 +33,44 @@ const queryOrganizationDomainStmt = `SELECT instance_id, org_id, domain, is_veri
// Get implements [domain.OrganizationDomainRepository].
// Subtle: this method shadows the method ([domain.OrganizationRepository]).Get of orgDomain.org.
func (o *orgDomain) Get(ctx context.Context, opts ...database.QueryOption) (*domain.OrganizationDomain, error) {
func (o orgDomain) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.OrganizationDomain, error) {
options := new(database.QueryOpts)
for _, opt := range opts {
opt(options)
}
if !options.Condition.IsRestrictingColumn(o.InstanceIDColumn()) {
return nil, database.NewMissingConditionError(o.InstanceIDColumn())
}
var builder database.StatementBuilder
builder.WriteString(queryOrganizationDomainStmt)
options.Write(&builder)
return scanOrganizationDomain(ctx, o.client, &builder)
return scanOrganizationDomain(ctx, client, &builder)
}
// List implements [domain.OrganizationDomainRepository].
// Subtle: this method shadows the method ([domain.OrganizationRepository]).List of orgDomain.org.
func (o *orgDomain) List(ctx context.Context, opts ...database.QueryOption) ([]*domain.OrganizationDomain, error) {
func (o orgDomain) List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*domain.OrganizationDomain, error) {
options := new(database.QueryOpts)
for _, opt := range opts {
opt(options)
}
if !options.Condition.IsRestrictingColumn(o.InstanceIDColumn()) {
return nil, database.NewMissingConditionError(o.InstanceIDColumn())
}
var builder database.StatementBuilder
builder.WriteString(queryOrganizationDomainStmt)
options.Write(&builder)
return scanOrganizationDomains(ctx, o.client, &builder)
return scanOrganizationDomains(ctx, client, &builder)
}
// Add implements [domain.OrganizationDomainRepository].
func (o *orgDomain) Add(ctx context.Context, domain *domain.AddOrganizationDomain) error {
func (o orgDomain) Add(ctx context.Context, client database.QueryExecutor, domain *domain.AddOrganizationDomain) error {
var (
builder database.StatementBuilder
createdAt, updatedAt any = database.DefaultInstruction, database.DefaultInstruction
@@ -69,33 +86,47 @@ func (o *orgDomain) Add(ctx context.Context, domain *domain.AddOrganizationDomai
builder.WriteArgs(domain.InstanceID, domain.OrgID, domain.Domain, domain.IsVerified, domain.IsPrimary, domain.ValidationType, createdAt, updatedAt)
builder.WriteString(`) RETURNING created_at, updated_at`)
return o.client.QueryRow(ctx, builder.String(), builder.Args()...).Scan(&domain.CreatedAt, &domain.UpdatedAt)
return client.QueryRow(ctx, builder.String(), builder.Args()...).Scan(&domain.CreatedAt, &domain.UpdatedAt)
}
// Update implements [domain.OrganizationDomainRepository].
// Subtle: this method shadows the method ([domain.OrganizationRepository]).Update of orgDomain.org.
func (o *orgDomain) Update(ctx context.Context, condition database.Condition, changes ...database.Change) (int64, error) {
func (o orgDomain) Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, changes ...database.Change) (int64, error) {
if len(changes) == 0 {
return 0, database.ErrNoChanges
}
if !condition.IsRestrictingColumn(o.InstanceIDColumn()) {
return 0, database.NewMissingConditionError(o.InstanceIDColumn())
}
if !condition.IsRestrictingColumn(o.OrgIDColumn()) {
return 0, database.NewMissingConditionError(o.OrgIDColumn())
}
if !database.Changes(changes).IsOnColumn(o.UpdatedAtColumn()) {
changes = append(changes, database.NewChange(o.UpdatedAtColumn(), database.NullInstruction))
}
var builder database.StatementBuilder
builder.WriteString(`UPDATE zitadel.org_domains SET `)
database.Changes(changes).Write(&builder)
writeCondition(&builder, condition)
return o.client.Exec(ctx, builder.String(), builder.Args()...)
return client.Exec(ctx, builder.String(), builder.Args()...)
}
// Remove implements [domain.OrganizationDomainRepository].
func (o *orgDomain) Remove(ctx context.Context, condition database.Condition) (int64, error) {
var builder database.StatementBuilder
func (o orgDomain) Remove(ctx context.Context, client database.QueryExecutor, condition database.Condition) (int64, error) {
if !condition.IsRestrictingColumn(o.InstanceIDColumn()) {
return 0, database.NewMissingConditionError(o.InstanceIDColumn())
}
if !condition.IsRestrictingColumn(o.OrgIDColumn()) {
return 0, database.NewMissingConditionError(o.OrgIDColumn())
}
var builder database.StatementBuilder
builder.WriteString(`DELETE FROM zitadel.org_domains `)
writeCondition(&builder, condition)
return o.client.Exec(ctx, builder.String(), builder.Args()...)
return client.Exec(ctx, builder.String(), builder.Args()...)
}
// -------------------------------------------------------------
@@ -158,45 +189,43 @@ func (o orgDomain) OrgIDCondition(orgID string) database.Condition {
// CreatedAtColumn implements [domain.OrganizationDomainRepository].
// Subtle: this method shadows the method ([domain.OrganizationRepository]).CreatedAtColumn of orgDomain.org.
func (orgDomain) CreatedAtColumn() database.Column {
return database.NewColumn("org_domains", "created_at")
func (o orgDomain) CreatedAtColumn() database.Column {
return database.NewColumn(o.unqualifiedTableName(), "created_at")
}
// DomainColumn implements [domain.OrganizationDomainRepository].
func (orgDomain) DomainColumn() database.Column {
return database.NewColumn("org_domains", "domain")
func (o orgDomain) DomainColumn() database.Column {
return database.NewColumn(o.unqualifiedTableName(), "domain")
}
// InstanceIDColumn implements [domain.OrganizationDomainRepository].
// Subtle: this method shadows the method ([domain.OrganizationRepository]).InstanceIDColumn of orgDomain.org.
func (orgDomain) InstanceIDColumn() database.Column {
return database.NewColumn("org_domains", "instance_id")
func (o orgDomain) InstanceIDColumn() database.Column {
return database.NewColumn(o.unqualifiedTableName(), "instance_id")
}
// IsPrimaryColumn implements [domain.OrganizationDomainRepository].
func (orgDomain) IsPrimaryColumn() database.Column {
return database.NewColumn("org_domains", "is_primary")
func (o orgDomain) IsPrimaryColumn() database.Column {
return database.NewColumn(o.unqualifiedTableName(), "is_primary")
}
// IsVerifiedColumn implements [domain.OrganizationDomainRepository].
func (orgDomain) IsVerifiedColumn() database.Column {
return database.NewColumn("org_domains", "is_verified")
func (o orgDomain) IsVerifiedColumn() database.Column {
return database.NewColumn(o.unqualifiedTableName(), "is_verified")
}
// OrgIDColumn implements [domain.OrganizationDomainRepository].
func (orgDomain) OrgIDColumn() database.Column {
return database.NewColumn("org_domains", "org_id")
func (o orgDomain) OrgIDColumn() database.Column {
return database.NewColumn(o.unqualifiedTableName(), "org_id")
}
// UpdatedAtColumn implements [domain.OrganizationDomainRepository].
// Subtle: this method shadows the method ([domain.OrganizationRepository]).UpdatedAtColumn of orgDomain.org.
func (orgDomain) UpdatedAtColumn() database.Column {
return database.NewColumn("org_domains", "updated_at")
func (o orgDomain) UpdatedAtColumn() database.Column {
return database.NewColumn(o.unqualifiedTableName(), "updated_at")
}
// ValidationTypeColumn implements [domain.OrganizationDomainRepository].
func (orgDomain) ValidationTypeColumn() database.Column {
return database.NewColumn("org_domains", "validation_type")
func (o orgDomain) ValidationTypeColumn() database.Column {
return database.NewColumn(o.unqualifiedTableName(), "validation_type")
}
// -------------------------------------------------------------
@@ -1,7 +1,6 @@
package repository_test
import (
"context"
"testing"
"github.com/brianvoe/gofakeit/v6"
@@ -15,6 +14,15 @@ import (
)
func TestAddOrganizationDomain(t *testing.T) {
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer func() {
err = tx.Rollback(t.Context())
if err != nil {
t.Log("error during rollback:", err)
}
}()
// create instance
instanceID := gofakeit.UUID()
instance := domain.Instance{
@@ -26,8 +34,11 @@ func TestAddOrganizationDomain(t *testing.T) {
ConsoleAppID: "consoleApp",
DefaultLanguage: "defaultLanguage",
}
instanceRepo := repository.InstanceRepository(pool)
err := instanceRepo.Create(t.Context(), &instance)
instanceRepo := repository.InstanceRepository()
orgRepo := repository.OrganizationRepository()
domainRepo := repository.OrganizationDomainRepository()
err = instanceRepo.Create(t.Context(), tx, &instance)
require.NoError(t, err)
// create organization
@@ -41,7 +52,7 @@ func TestAddOrganizationDomain(t *testing.T) {
tests := []struct {
name string
testFunc func(ctx context.Context, t *testing.T, domainRepo domain.OrganizationDomainRepository) *domain.AddOrganizationDomain
testFunc func(t *testing.T, tx database.QueryExecutor) *domain.AddOrganizationDomain
organizationDomain domain.AddOrganizationDomain
err error
}{
@@ -92,7 +103,7 @@ func TestAddOrganizationDomain(t *testing.T) {
},
{
name: "add domain with same domain twice",
testFunc: func(ctx context.Context, t *testing.T, domainRepo domain.OrganizationDomainRepository) *domain.AddOrganizationDomain {
testFunc: func(t *testing.T, tx database.QueryExecutor) *domain.AddOrganizationDomain {
domainName := gofakeit.DomainName()
organizationDomain := &domain.AddOrganizationDomain{
@@ -104,7 +115,7 @@ func TestAddOrganizationDomain(t *testing.T) {
ValidationType: gu.Ptr(domain.DomainValidationTypeDNS),
}
err := domainRepo.Add(ctx, organizationDomain)
err := domainRepo.Add(t.Context(), tx, organizationDomain)
require.NoError(t, err)
// return same domain again
@@ -169,28 +180,26 @@ func TestAddOrganizationDomain(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := t.Context()
tx, err := pool.Begin(t.Context(), nil)
savepoint, err := tx.Begin(t.Context())
require.NoError(t, err)
defer func() {
require.NoError(t, tx.Rollback(t.Context()))
err = savepoint.Rollback(t.Context())
if err != nil {
t.Log("error during rollback:", err)
}
}()
orgRepo := repository.OrganizationRepository(tx)
err = orgRepo.Create(t.Context(), &organization)
err = orgRepo.Create(t.Context(), savepoint, &organization)
require.NoError(t, err)
domainRepo := orgRepo.Domains(false)
var organizationDomain *domain.AddOrganizationDomain
if test.testFunc != nil {
organizationDomain = test.testFunc(ctx, t, domainRepo)
organizationDomain = test.testFunc(t, savepoint)
} else {
organizationDomain = &test.organizationDomain
}
err = domainRepo.Add(ctx, organizationDomain)
err = domainRepo.Add(t.Context(), tx, organizationDomain)
if test.err != nil {
assert.ErrorIs(t, err, test.err)
return
@@ -204,6 +213,16 @@ func TestAddOrganizationDomain(t *testing.T) {
}
func TestGetOrganizationDomain(t *testing.T) {
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer func() {
require.NoError(t, tx.Rollback(t.Context()))
}()
instanceRepo := repository.InstanceRepository()
orgRepo := repository.OrganizationRepository()
domainRepo := repository.OrganizationDomainRepository()
// create instance
instanceID := gofakeit.UUID()
instance := domain.Instance{
@@ -225,29 +244,18 @@ func TestGetOrganizationDomain(t *testing.T) {
State: domain.OrgStateActive,
}
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer func() {
require.NoError(t, tx.Rollback(t.Context()))
}()
instanceRepo := repository.InstanceRepository(tx)
err = instanceRepo.Create(t.Context(), &instance)
err = instanceRepo.Create(t.Context(), tx, &instance)
require.NoError(t, err)
orgRepo := repository.OrganizationRepository(tx)
err = orgRepo.Create(t.Context(), &organization)
err = orgRepo.Create(t.Context(), tx, &organization)
require.NoError(t, err)
// add domains
domainRepo := orgRepo.Domains(false)
domainName1 := gofakeit.DomainName()
domainName2 := gofakeit.DomainName()
domain1 := &domain.AddOrganizationDomain{
InstanceID: instanceID,
OrgID: orgID,
Domain: domainName1,
Domain: gofakeit.DomainName(),
IsVerified: true,
IsPrimary: true,
ValidationType: gu.Ptr(domain.DomainValidationTypeDNS),
@@ -255,15 +263,15 @@ func TestGetOrganizationDomain(t *testing.T) {
domain2 := &domain.AddOrganizationDomain{
InstanceID: instanceID,
OrgID: orgID,
Domain: domainName2,
Domain: gofakeit.DomainName(),
IsVerified: false,
IsPrimary: false,
ValidationType: gu.Ptr(domain.DomainValidationTypeHTTP),
}
err = domainRepo.Add(t.Context(), domain1)
err = domainRepo.Add(t.Context(), tx, domain1)
require.NoError(t, err)
err = domainRepo.Add(t.Context(), domain2)
err = domainRepo.Add(t.Context(), tx, domain2)
require.NoError(t, err)
tests := []struct {
@@ -275,12 +283,15 @@ func TestGetOrganizationDomain(t *testing.T) {
{
name: "get primary domain",
opts: []database.QueryOption{
database.WithCondition(domainRepo.IsPrimaryCondition(true)),
database.WithCondition(database.And(
domainRepo.InstanceIDCondition(instanceID),
domainRepo.IsPrimaryCondition(true),
)),
},
expected: &domain.OrganizationDomain{
InstanceID: instanceID,
OrgID: orgID,
Domain: domainName1,
Domain: domain1.Domain,
IsVerified: true,
IsPrimary: true,
ValidationType: gu.Ptr(domain.DomainValidationTypeDNS),
@@ -289,12 +300,15 @@ func TestGetOrganizationDomain(t *testing.T) {
{
name: "get by domain name",
opts: []database.QueryOption{
database.WithCondition(domainRepo.DomainCondition(database.TextOperationEqual, domainName2)),
database.WithCondition(database.And(
domainRepo.InstanceIDCondition(instanceID),
domainRepo.DomainCondition(database.TextOperationEqual, domain2.Domain),
)),
},
expected: &domain.OrganizationDomain{
InstanceID: instanceID,
OrgID: orgID,
Domain: domainName2,
Domain: domain2.Domain,
IsVerified: false,
IsPrimary: false,
ValidationType: gu.Ptr(domain.DomainValidationTypeHTTP),
@@ -303,13 +317,16 @@ func TestGetOrganizationDomain(t *testing.T) {
{
name: "get by org ID",
opts: []database.QueryOption{
database.WithCondition(domainRepo.OrgIDCondition(orgID)),
database.WithCondition(domainRepo.IsPrimaryCondition(true)),
database.WithCondition(database.And(
domainRepo.InstanceIDCondition(instanceID),
domainRepo.OrgIDCondition(orgID),
domainRepo.IsPrimaryCondition(true),
)),
},
expected: &domain.OrganizationDomain{
InstanceID: instanceID,
OrgID: orgID,
Domain: domainName1,
Domain: domain1.Domain,
IsVerified: true,
IsPrimary: true,
ValidationType: gu.Ptr(domain.DomainValidationTypeDNS),
@@ -318,12 +335,15 @@ func TestGetOrganizationDomain(t *testing.T) {
{
name: "get verified domain",
opts: []database.QueryOption{
database.WithCondition(domainRepo.IsVerifiedCondition(true)),
database.WithCondition(database.And(
domainRepo.InstanceIDCondition(instanceID),
domainRepo.IsVerifiedCondition(true),
)),
},
expected: &domain.OrganizationDomain{
InstanceID: instanceID,
OrgID: orgID,
Domain: domainName1,
Domain: domain1.Domain,
IsVerified: true,
IsPrimary: true,
ValidationType: gu.Ptr(domain.DomainValidationTypeDNS),
@@ -332,7 +352,10 @@ func TestGetOrganizationDomain(t *testing.T) {
{
name: "get non-existent domain",
opts: []database.QueryOption{
database.WithCondition(domainRepo.DomainCondition(database.TextOperationEqual, "non-existent.com")),
database.WithCondition(database.And(
domainRepo.InstanceIDCondition(instanceID),
domainRepo.DomainCondition(database.TextOperationEqual, "non-existent.com"),
)),
},
err: new(database.NoRowFoundError),
},
@@ -340,9 +363,7 @@ func TestGetOrganizationDomain(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := t.Context()
result, err := domainRepo.Get(ctx, test.opts...)
result, err := domainRepo.Get(t.Context(), tx, test.opts...)
if test.err != nil {
assert.ErrorIs(t, err, test.err)
return
@@ -362,10 +383,22 @@ func TestGetOrganizationDomain(t *testing.T) {
}
func TestListOrganizationDomains(t *testing.T) {
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer func() {
err = tx.Rollback(t.Context())
if err != nil {
t.Log("error during rollback:", err)
}
}()
instanceRepo := repository.InstanceRepository()
orgRepo := repository.OrganizationRepository()
domainRepo := repository.OrganizationDomainRepository()
// create instance
instanceID := gofakeit.UUID()
instance := domain.Instance{
ID: instanceID,
ID: gofakeit.UUID(),
Name: gofakeit.Name(),
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
@@ -375,50 +408,40 @@ func TestListOrganizationDomains(t *testing.T) {
}
// create organization
orgID := gofakeit.UUID()
organization := domain.Organization{
ID: orgID,
ID: gofakeit.UUID(),
Name: gofakeit.Name(),
InstanceID: instanceID,
InstanceID: instance.ID,
State: domain.OrgStateActive,
}
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer func() {
require.NoError(t, tx.Rollback(t.Context()))
}()
instanceRepo := repository.InstanceRepository(tx)
err = instanceRepo.Create(t.Context(), &instance)
err = instanceRepo.Create(t.Context(), tx, &instance)
require.NoError(t, err)
orgRepo := repository.OrganizationRepository(tx)
err = orgRepo.Create(t.Context(), &organization)
err = orgRepo.Create(t.Context(), tx, &organization)
require.NoError(t, err)
// add multiple domains
domainRepo := orgRepo.Domains(false)
domains := []domain.AddOrganizationDomain{
{
InstanceID: instanceID,
OrgID: orgID,
InstanceID: instance.ID,
OrgID: organization.ID,
Domain: gofakeit.DomainName(),
IsVerified: true,
IsPrimary: true,
ValidationType: gu.Ptr(domain.DomainValidationTypeDNS),
},
{
InstanceID: instanceID,
OrgID: orgID,
InstanceID: instance.ID,
OrgID: organization.ID,
Domain: gofakeit.DomainName(),
IsVerified: false,
IsPrimary: false,
ValidationType: gu.Ptr(domain.DomainValidationTypeHTTP),
},
{
InstanceID: instanceID,
OrgID: orgID,
InstanceID: instance.ID,
OrgID: organization.ID,
Domain: gofakeit.DomainName(),
IsVerified: true,
IsPrimary: false,
@@ -427,7 +450,7 @@ func TestListOrganizationDomains(t *testing.T) {
}
for i := range domains {
err = domainRepo.Add(t.Context(), &domains[i])
err = domainRepo.Add(t.Context(), tx, &domains[i])
require.NoError(t, err)
}
@@ -437,42 +460,59 @@ func TestListOrganizationDomains(t *testing.T) {
expectedCount int
}{
{
name: "list all domains",
opts: []database.QueryOption{},
name: "list all domains",
opts: []database.QueryOption{
database.WithCondition(domainRepo.InstanceIDCondition(instance.ID)),
},
expectedCount: 3,
},
{
name: "list verified domains",
opts: []database.QueryOption{
database.WithCondition(domainRepo.IsVerifiedCondition(true)),
database.WithCondition(database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.IsVerifiedCondition(true),
)),
},
expectedCount: 2,
},
{
name: "list primary domains",
opts: []database.QueryOption{
database.WithCondition(domainRepo.IsPrimaryCondition(true)),
database.WithCondition(database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.IsPrimaryCondition(true),
)),
},
expectedCount: 1,
},
{
name: "list by organization",
opts: []database.QueryOption{
database.WithCondition(domainRepo.OrgIDCondition(orgID)),
database.WithCondition(database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.OrgIDCondition(organization.ID),
)),
},
expectedCount: 3,
},
{
name: "list by instance",
opts: []database.QueryOption{
database.WithCondition(domainRepo.InstanceIDCondition(instanceID)),
database.WithCondition(database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.InstanceIDCondition(instance.ID),
)),
},
expectedCount: 3,
},
{
name: "list non-existent organization",
opts: []database.QueryOption{
database.WithCondition(domainRepo.OrgIDCondition("non-existent")),
database.WithCondition(database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.OrgIDCondition("non-existent"),
)),
},
expectedCount: 0,
},
@@ -482,13 +522,13 @@ func TestListOrganizationDomains(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
ctx := t.Context()
results, err := domainRepo.List(ctx, test.opts...)
results, err := domainRepo.List(ctx, tx, test.opts...)
require.NoError(t, err)
assert.Len(t, results, test.expectedCount)
for _, result := range results {
assert.Equal(t, instanceID, result.InstanceID)
assert.Equal(t, orgID, result.OrgID)
assert.Equal(t, instance.ID, result.InstanceID)
assert.Equal(t, organization.ID, result.OrgID)
assert.NotEmpty(t, result.Domain)
assert.NotEmpty(t, result.CreatedAt)
assert.NotEmpty(t, result.UpdatedAt)
@@ -498,10 +538,22 @@ func TestListOrganizationDomains(t *testing.T) {
}
func TestUpdateOrganizationDomain(t *testing.T) {
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer func() {
err = tx.Rollback(t.Context())
if err != nil {
t.Log("error during rollback:", err)
}
}()
instanceRepo := repository.InstanceRepository()
orgRepo := repository.OrganizationRepository()
domainRepo := repository.OrganizationDomainRepository()
// create instance
instanceID := gofakeit.UUID()
instance := domain.Instance{
ID: instanceID,
ID: gofakeit.UUID(),
Name: gofakeit.Name(),
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
@@ -511,41 +563,30 @@ func TestUpdateOrganizationDomain(t *testing.T) {
}
// create organization
orgID := gofakeit.UUID()
organization := domain.Organization{
ID: orgID,
ID: gofakeit.UUID(),
Name: gofakeit.Name(),
InstanceID: instanceID,
InstanceID: instance.ID,
State: domain.OrgStateActive,
}
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer func() {
require.NoError(t, tx.Rollback(t.Context()))
}()
instanceRepo := repository.InstanceRepository(tx)
err = instanceRepo.Create(t.Context(), &instance)
err = instanceRepo.Create(t.Context(), tx, &instance)
require.NoError(t, err)
orgRepo := repository.OrganizationRepository(tx)
err = orgRepo.Create(t.Context(), &organization)
err = orgRepo.Create(t.Context(), tx, &organization)
require.NoError(t, err)
// add domain
domainRepo := orgRepo.Domains(false)
domainName := gofakeit.DomainName()
organizationDomain := &domain.AddOrganizationDomain{
InstanceID: instanceID,
OrgID: orgID,
Domain: domainName,
InstanceID: instance.ID,
OrgID: organization.ID,
Domain: gofakeit.DomainName(),
IsVerified: false,
IsPrimary: false,
ValidationType: gu.Ptr(domain.DomainValidationTypeDNS),
}
err = domainRepo.Add(t.Context(), organizationDomain)
err = domainRepo.Add(t.Context(), tx, organizationDomain)
require.NoError(t, err)
tests := []struct {
@@ -556,26 +597,42 @@ func TestUpdateOrganizationDomain(t *testing.T) {
err error
}{
{
name: "set verified",
condition: domainRepo.DomainCondition(database.TextOperationEqual, domainName),
changes: []database.Change{domainRepo.SetVerified()},
expected: 1,
name: "set verified",
condition: database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.OrgIDCondition(organization.ID),
domainRepo.DomainCondition(database.TextOperationEqual, organizationDomain.Domain),
),
changes: []database.Change{domainRepo.SetVerified()},
expected: 1,
},
{
name: "set primary",
condition: domainRepo.DomainCondition(database.TextOperationEqual, domainName),
changes: []database.Change{domainRepo.SetPrimary()},
expected: 1,
name: "set primary",
condition: database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.OrgIDCondition(organization.ID),
domainRepo.DomainCondition(database.TextOperationEqual, organizationDomain.Domain),
),
changes: []database.Change{domainRepo.SetPrimary()},
expected: 1,
},
{
name: "set validation type",
condition: domainRepo.DomainCondition(database.TextOperationEqual, domainName),
changes: []database.Change{domainRepo.SetValidationType(domain.DomainValidationTypeHTTP)},
expected: 1,
name: "set validation type",
condition: database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.OrgIDCondition(organization.ID),
domainRepo.DomainCondition(database.TextOperationEqual, organizationDomain.Domain),
),
changes: []database.Change{domainRepo.SetValidationType(domain.DomainValidationTypeHTTP)},
expected: 1,
},
{
name: "multiple changes",
condition: domainRepo.DomainCondition(database.TextOperationEqual, domainName),
name: "multiple changes",
condition: database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.OrgIDCondition(organization.ID),
domainRepo.DomainCondition(database.TextOperationEqual, organizationDomain.Domain),
),
changes: []database.Change{
domainRepo.SetVerified(),
domainRepo.SetPrimary(),
@@ -584,31 +641,41 @@ func TestUpdateOrganizationDomain(t *testing.T) {
expected: 1,
},
{
name: "update by org ID and domain",
condition: database.And(domainRepo.OrgIDCondition(orgID), domainRepo.DomainCondition(database.TextOperationEqual, domainName)),
changes: []database.Change{domainRepo.SetVerified()},
expected: 1,
name: "update by org ID and domain",
condition: database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.OrgIDCondition(organization.ID),
domainRepo.DomainCondition(database.TextOperationEqual, organizationDomain.Domain),
),
changes: []database.Change{domainRepo.SetVerified()},
expected: 1,
},
{
name: "update non-existent domain",
condition: domainRepo.DomainCondition(database.TextOperationEqual, "non-existent.com"),
changes: []database.Change{domainRepo.SetVerified()},
expected: 0,
name: "update non-existent domain",
condition: database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.OrgIDCondition(organization.ID),
domainRepo.DomainCondition(database.TextOperationEqual, "non-existent.com"),
),
changes: []database.Change{domainRepo.SetVerified()},
expected: 0,
},
{
name: "no changes",
condition: domainRepo.DomainCondition(database.TextOperationEqual, domainName),
changes: []database.Change{},
expected: 0,
err: database.ErrNoChanges,
name: "no changes",
condition: database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.OrgIDCondition(organization.ID),
domainRepo.DomainCondition(database.TextOperationEqual, organizationDomain.Domain),
),
changes: []database.Change{},
expected: 0,
err: database.ErrNoChanges,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := t.Context()
rowsAffected, err := domainRepo.Update(ctx, test.condition, test.changes...)
rowsAffected, err := domainRepo.Update(t.Context(), tx, test.condition, test.changes...)
if test.err != nil {
assert.ErrorIs(t, err, test.err)
return
@@ -619,7 +686,7 @@ func TestUpdateOrganizationDomain(t *testing.T) {
// verify changes were applied if rows were affected
if rowsAffected > 0 && len(test.changes) > 0 {
result, err := domainRepo.Get(ctx, database.WithCondition(test.condition))
result, err := domainRepo.Get(t.Context(), tx, database.WithCondition(test.condition))
require.NoError(t, err)
// We know changes were applied since rowsAffected > 0
@@ -632,10 +699,22 @@ func TestUpdateOrganizationDomain(t *testing.T) {
}
func TestRemoveOrganizationDomain(t *testing.T) {
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer func() {
err = tx.Rollback(t.Context())
if err != nil {
t.Log("error during rollback:", err)
}
}()
instanceRepo := repository.InstanceRepository()
orgRepo := repository.OrganizationRepository()
domainRepo := repository.OrganizationDomainRepository()
// create instance
instanceID := gofakeit.UUID()
instance := domain.Instance{
ID: instanceID,
ID: gofakeit.UUID(),
Name: gofakeit.Name(),
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
@@ -645,53 +724,40 @@ func TestRemoveOrganizationDomain(t *testing.T) {
}
// create organization
orgID := gofakeit.UUID()
organization := domain.Organization{
ID: orgID,
ID: gofakeit.UUID(),
Name: gofakeit.Name(),
InstanceID: instanceID,
InstanceID: instance.ID,
State: domain.OrgStateActive,
}
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer func() {
require.NoError(t, tx.Rollback(t.Context()))
}()
instanceRepo := repository.InstanceRepository(tx)
err = instanceRepo.Create(t.Context(), &instance)
err = instanceRepo.Create(t.Context(), tx, &instance)
require.NoError(t, err)
orgRepo := repository.OrganizationRepository(tx)
err = orgRepo.Create(t.Context(), &organization)
err = orgRepo.Create(t.Context(), tx, &organization)
require.NoError(t, err)
// add domains
domainRepo := orgRepo.Domains(false)
domainName1 := gofakeit.DomainName()
domainName2 := gofakeit.DomainName()
domain1 := &domain.AddOrganizationDomain{
InstanceID: instanceID,
OrgID: orgID,
Domain: domainName1,
InstanceID: instance.ID,
OrgID: organization.ID,
Domain: gofakeit.DomainName(),
IsVerified: true,
IsPrimary: true,
ValidationType: gu.Ptr(domain.DomainValidationTypeDNS),
}
domain2 := &domain.AddOrganizationDomain{
InstanceID: instanceID,
OrgID: orgID,
Domain: domainName2,
InstanceID: instance.ID,
OrgID: organization.ID,
Domain: gofakeit.DomainName(),
IsVerified: false,
IsPrimary: false,
ValidationType: gu.Ptr(domain.DomainValidationTypeHTTP),
}
err = domainRepo.Add(t.Context(), domain1)
err = domainRepo.Add(t.Context(), tx, domain1)
require.NoError(t, err)
err = domainRepo.Add(t.Context(), domain2)
err = domainRepo.Add(t.Context(), tx, domain2)
require.NoError(t, err)
tests := []struct {
@@ -700,50 +766,70 @@ func TestRemoveOrganizationDomain(t *testing.T) {
expected int64
}{
{
name: "remove by domain name",
condition: domainRepo.DomainCondition(database.TextOperationEqual, domainName1),
expected: 1,
name: "remove by domain name",
condition: database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.OrgIDCondition(organization.ID),
domainRepo.DomainCondition(database.TextOperationEqual, domain1.Domain),
),
expected: 1,
},
{
name: "remove by primary condition",
condition: domainRepo.IsPrimaryCondition(false),
expected: 1, // domain2 should still exist and be non-primary
name: "remove by primary condition",
condition: database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.OrgIDCondition(organization.ID),
domainRepo.IsPrimaryCondition(false),
),
expected: 1, // domain2 should still exist and be non-primary
},
{
name: "remove by org ID and domain",
condition: database.And(domainRepo.OrgIDCondition(orgID), domainRepo.DomainCondition(database.TextOperationEqual, domainName2)),
expected: 1,
name: "remove by org ID and domain",
condition: database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.OrgIDCondition(organization.ID),
domainRepo.DomainCondition(database.TextOperationEqual, domain2.Domain),
),
expected: 1,
},
{
name: "remove non-existent domain",
condition: domainRepo.DomainCondition(database.TextOperationEqual, "non-existent.com"),
expected: 0,
name: "remove non-existent domain",
condition: database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.OrgIDCondition(organization.ID),
domainRepo.DomainCondition(database.TextOperationEqual, "non-existent.com"),
),
expected: 0,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := t.Context()
snapshot, err := tx.Begin(ctx)
snapshot, err := tx.Begin(t.Context())
require.NoError(t, err)
defer func() {
require.NoError(t, snapshot.Rollback(ctx))
err = snapshot.Rollback(t.Context())
if err != nil {
t.Log("error during rollback:", err)
}
}()
orgRepo := repository.OrganizationRepository(snapshot)
domainRepo := orgRepo.Domains(false)
// count before removal
beforeCount, err := domainRepo.List(ctx)
beforeCount, err := domainRepo.List(t.Context(), snapshot, database.WithCondition(database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.OrgIDCondition(organization.ID),
)))
require.NoError(t, err)
rowsAffected, err := domainRepo.Remove(ctx, test.condition)
rowsAffected, err := domainRepo.Remove(t.Context(), snapshot, test.condition)
require.NoError(t, err)
assert.Equal(t, test.expected, rowsAffected)
// verify removal
afterCount, err := domainRepo.List(ctx)
afterCount, err := domainRepo.List(t.Context(), snapshot, database.WithCondition(database.And(
domainRepo.InstanceIDCondition(instance.ID),
domainRepo.OrgIDCondition(organization.ID),
)))
require.NoError(t, err)
assert.Equal(t, len(beforeCount)-int(test.expected), len(afterCount))
})
@@ -751,8 +837,7 @@ func TestRemoveOrganizationDomain(t *testing.T) {
}
func TestOrganizationDomainConditions(t *testing.T) {
orgRepo := repository.OrganizationRepository(pool)
domainRepo := orgRepo.Domains(false)
domainRepo := repository.OrganizationDomainRepository()
tests := []struct {
name string
@@ -811,8 +896,7 @@ func TestOrganizationDomainConditions(t *testing.T) {
}
func TestOrganizationDomainChanges(t *testing.T) {
orgRepo := repository.OrganizationRepository(pool)
domainRepo := orgRepo.Domains(false)
domainRepo := repository.OrganizationDomainRepository()
tests := []struct {
name string
@@ -851,8 +935,7 @@ func TestOrganizationDomainChanges(t *testing.T) {
}
func TestOrganizationDomainColumns(t *testing.T) {
orgRepo := repository.OrganizationRepository(pool)
domainRepo := orgRepo.Domains(false)
domainRepo := repository.OrganizationDomainRepository()
tests := []struct {
name string
+280 -135
View File
@@ -1,7 +1,7 @@
package repository_test
import (
"context"
"strconv"
"testing"
"time"
@@ -15,6 +15,18 @@ import (
)
func TestCreateOrganization(t *testing.T) {
beforeCreate := time.Now()
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer func() {
err := tx.Rollback(t.Context())
if err != nil {
t.Logf("error during rollback: %v", err)
}
}()
instanceRepo := repository.InstanceRepository()
organizationRepo := repository.OrganizationRepository()
// create instance
instanceId := gofakeit.Name()
instance := domain.Instance{
@@ -26,13 +38,12 @@ func TestCreateOrganization(t *testing.T) {
ConsoleAppID: "consoleApp",
DefaultLanguage: "defaultLanguage",
}
instanceRepo := repository.InstanceRepository(pool)
err := instanceRepo.Create(t.Context(), &instance)
err = instanceRepo.Create(t.Context(), tx, &instance)
require.NoError(t, err)
tests := []struct {
name string
testFunc func(ctx context.Context, t *testing.T) *domain.Organization
testFunc func(t *testing.T, client database.QueryExecutor) *domain.Organization
organization domain.Organization
err error
}{
@@ -67,8 +78,7 @@ func TestCreateOrganization(t *testing.T) {
},
{
name: "adding org with same id twice",
testFunc: func(ctx context.Context, t *testing.T) *domain.Organization {
organizationRepo := repository.OrganizationRepository(pool)
testFunc: func(t *testing.T, tx database.QueryExecutor) *domain.Organization {
organizationId := gofakeit.Name()
organizationName := gofakeit.Name()
@@ -79,7 +89,7 @@ func TestCreateOrganization(t *testing.T) {
State: domain.OrgStateActive,
}
err := organizationRepo.Create(ctx, &org)
err := organizationRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
// change the name to make sure same only the id clashes
org.Name = gofakeit.Name()
@@ -89,8 +99,7 @@ func TestCreateOrganization(t *testing.T) {
},
{
name: "adding org with same name twice",
testFunc: func(ctx context.Context, t *testing.T) *domain.Organization {
organizationRepo := repository.OrganizationRepository(pool)
testFunc: func(t *testing.T, tx database.QueryExecutor) *domain.Organization {
organizationId := gofakeit.Name()
organizationName := gofakeit.Name()
@@ -101,7 +110,7 @@ func TestCreateOrganization(t *testing.T) {
State: domain.OrgStateActive,
}
err := organizationRepo.Create(ctx, &org)
err := organizationRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
// change the id to make sure same name+instance causes an error
org.ID = gofakeit.Name()
@@ -111,7 +120,7 @@ func TestCreateOrganization(t *testing.T) {
},
func() struct {
name string
testFunc func(ctx context.Context, t *testing.T) *domain.Organization
testFunc func(t *testing.T, tx database.QueryExecutor) *domain.Organization
organization domain.Organization
err error
} {
@@ -120,12 +129,12 @@ func TestCreateOrganization(t *testing.T) {
return struct {
name string
testFunc func(ctx context.Context, t *testing.T) *domain.Organization
testFunc func(t *testing.T, tx database.QueryExecutor) *domain.Organization
organization domain.Organization
err error
}{
name: "adding org with same name, different instance",
testFunc: func(ctx context.Context, t *testing.T) *domain.Organization {
testFunc: func(t *testing.T, tx database.QueryExecutor) *domain.Organization {
// create instance
instId := gofakeit.Name()
instance := domain.Instance{
@@ -137,12 +146,9 @@ func TestCreateOrganization(t *testing.T) {
ConsoleAppID: "consoleApp",
DefaultLanguage: "defaultLanguage",
}
instanceRepo := repository.InstanceRepository(pool)
err := instanceRepo.Create(ctx, &instance)
err := instanceRepo.Create(t.Context(), tx, &instance)
assert.Nil(t, err)
organizationRepo := repository.OrganizationRepository(pool)
org := domain.Organization{
ID: gofakeit.Name(),
Name: organizationName,
@@ -150,7 +156,7 @@ func TestCreateOrganization(t *testing.T) {
State: domain.OrgStateActive,
}
err = organizationRepo.Create(ctx, &org)
err = organizationRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
// change the id to make it unique
@@ -214,19 +220,25 @@ func TestCreateOrganization(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
savepoint, err := tx.Begin(t.Context())
require.NoError(t, err)
defer func() {
err = savepoint.Rollback(t.Context())
if err != nil {
t.Logf("error during rollback: %v", err)
}
}()
var organization *domain.Organization
if tt.testFunc != nil {
organization = tt.testFunc(ctx, t)
organization = tt.testFunc(t, savepoint)
} else {
organization = &tt.organization
}
organizationRepo := repository.OrganizationRepository(pool)
// create organization
beforeCreate := time.Now()
err = organizationRepo.Create(ctx, organization)
err = organizationRepo.Create(t.Context(), savepoint, organization)
assert.ErrorIs(t, err, tt.err)
if err != nil {
return
@@ -234,7 +246,7 @@ func TestCreateOrganization(t *testing.T) {
afterCreate := time.Now()
// check organization values
organization, err = organizationRepo.Get(ctx,
organization, err = organizationRepo.Get(t.Context(), savepoint,
database.WithCondition(
database.And(
organizationRepo.IDCondition(organization.ID),
@@ -255,6 +267,19 @@ func TestCreateOrganization(t *testing.T) {
}
func TestUpdateOrganization(t *testing.T) {
beforeUpdate := time.Now()
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer func() {
err := tx.Rollback(t.Context())
if err != nil {
t.Logf("error during rollback: %v", err)
}
}()
instanceRepo := repository.InstanceRepository()
organizationRepo := repository.OrganizationRepository()
// create instance
instanceId := gofakeit.Name()
instance := domain.Instance{
@@ -266,20 +291,18 @@ func TestUpdateOrganization(t *testing.T) {
ConsoleAppID: "consoleApp",
DefaultLanguage: "defaultLanguage",
}
instanceRepo := repository.InstanceRepository(pool)
err := instanceRepo.Create(t.Context(), &instance)
err = instanceRepo.Create(t.Context(), tx, &instance)
require.NoError(t, err)
organizationRepo := repository.OrganizationRepository(pool)
tests := []struct {
name string
testFunc func(ctx context.Context, t *testing.T) *domain.Organization
testFunc func(t *testing.T) *domain.Organization
update []database.Change
rowsAffected int64
}{
{
name: "happy path update name",
testFunc: func(ctx context.Context, t *testing.T) *domain.Organization {
testFunc: func(t *testing.T) *domain.Organization {
organizationId := gofakeit.Name()
organizationName := gofakeit.Name()
@@ -291,7 +314,7 @@ func TestUpdateOrganization(t *testing.T) {
}
// create organization
err := organizationRepo.Create(ctx, &org)
err := organizationRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
// update with updated value
@@ -303,7 +326,7 @@ func TestUpdateOrganization(t *testing.T) {
},
{
name: "update deleted organization",
testFunc: func(ctx context.Context, t *testing.T) *domain.Organization {
testFunc: func(t *testing.T) *domain.Organization {
organizationId := gofakeit.Name()
organizationName := gofakeit.Name()
@@ -315,13 +338,15 @@ func TestUpdateOrganization(t *testing.T) {
}
// create organization
err := organizationRepo.Create(ctx, &org)
err := organizationRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
// delete instance
_, err = organizationRepo.Delete(ctx,
organizationRepo.IDCondition(org.ID),
org.InstanceID,
_, err = organizationRepo.Delete(t.Context(), tx,
database.And(
organizationRepo.InstanceIDCondition(org.InstanceID),
organizationRepo.IDCondition(org.ID),
),
)
require.NoError(t, err)
@@ -332,7 +357,7 @@ func TestUpdateOrganization(t *testing.T) {
},
{
name: "happy path change state",
testFunc: func(ctx context.Context, t *testing.T) *domain.Organization {
testFunc: func(t *testing.T) *domain.Organization {
organizationId := gofakeit.Name()
organizationName := gofakeit.Name()
@@ -344,7 +369,7 @@ func TestUpdateOrganization(t *testing.T) {
}
// create organization
err := organizationRepo.Create(ctx, &org)
err := organizationRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
// update with updated value
@@ -356,7 +381,7 @@ func TestUpdateOrganization(t *testing.T) {
},
{
name: "update non existent organization",
testFunc: func(ctx context.Context, t *testing.T) *domain.Organization {
testFunc: func(t *testing.T) *domain.Organization {
organizationId := gofakeit.Name()
org := domain.Organization{
@@ -370,16 +395,14 @@ func TestUpdateOrganization(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
organizationRepo := repository.OrganizationRepository(pool)
createdOrg := tt.testFunc(ctx, t)
createdOrg := tt.testFunc(t)
// update org
beforeUpdate := time.Now()
rowsAffected, err := organizationRepo.Update(ctx,
organizationRepo.IDCondition(createdOrg.ID),
createdOrg.InstanceID,
rowsAffected, err := organizationRepo.Update(t.Context(), tx,
database.And(
organizationRepo.InstanceIDCondition(createdOrg.InstanceID),
organizationRepo.IDCondition(createdOrg.ID),
),
tt.update...,
)
afterUpdate := time.Now()
@@ -392,7 +415,7 @@ func TestUpdateOrganization(t *testing.T) {
}
// check organization values
organization, err := organizationRepo.Get(ctx,
organization, err := organizationRepo.Get(t.Context(), tx,
database.WithCondition(
database.And(
organizationRepo.IDCondition(createdOrg.ID),
@@ -411,6 +434,19 @@ func TestUpdateOrganization(t *testing.T) {
}
func TestGetOrganization(t *testing.T) {
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer func() {
err := tx.Rollback(t.Context())
if err != nil {
t.Logf("error during rollback: %v", err)
}
}()
instanceRepo := repository.InstanceRepository()
orgRepo := repository.OrganizationRepository()
orgDomainRepo := repository.OrganizationDomainRepository()
// create instance
instanceId := gofakeit.Name()
instance := domain.Instance{
@@ -422,11 +458,9 @@ func TestGetOrganization(t *testing.T) {
ConsoleAppID: "consoleApp",
DefaultLanguage: "defaultLanguage",
}
instanceRepo := repository.InstanceRepository(pool)
err := instanceRepo.Create(t.Context(), &instance)
require.NoError(t, err)
orgRepo := repository.OrganizationRepository(pool)
err = instanceRepo.Create(t.Context(), tx, &instance)
require.NoError(t, err)
// create organization
// this org is created as an additional org which should NOT
@@ -437,13 +471,13 @@ func TestGetOrganization(t *testing.T) {
InstanceID: instanceId,
State: domain.OrgStateActive,
}
err = orgRepo.Create(t.Context(), &org)
err = orgRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
type test struct {
name string
testFunc func(ctx context.Context, t *testing.T) *domain.Organization
orgIdentifierCondition domain.OrgIdentifierCondition
testFunc func(t *testing.T) *domain.Organization
orgIdentifierCondition database.Condition
err error
}
@@ -452,7 +486,7 @@ func TestGetOrganization(t *testing.T) {
organizationId := gofakeit.Name()
return test{
name: "happy path get using id",
testFunc: func(ctx context.Context, t *testing.T) *domain.Organization {
testFunc: func(t *testing.T) *domain.Organization {
organizationName := gofakeit.Name()
org := domain.Organization{
@@ -463,7 +497,7 @@ func TestGetOrganization(t *testing.T) {
}
// create organization
err := orgRepo.Create(ctx, &org)
err := orgRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
return &org
@@ -475,7 +509,7 @@ func TestGetOrganization(t *testing.T) {
organizationId := gofakeit.Name()
return test{
name: "happy path get using id including domain",
testFunc: func(ctx context.Context, t *testing.T) *domain.Organization {
testFunc: func(t *testing.T) *domain.Organization {
organizationName := gofakeit.Name()
org := domain.Organization{
@@ -486,7 +520,7 @@ func TestGetOrganization(t *testing.T) {
}
// create organization
err := orgRepo.Create(ctx, &org)
err := orgRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
d := &domain.AddOrganizationDomain{
@@ -496,7 +530,7 @@ func TestGetOrganization(t *testing.T) {
IsVerified: true,
IsPrimary: true,
}
err = orgRepo.Domains(false).Add(ctx, d)
err = orgDomainRepo.Add(t.Context(), tx, d)
require.NoError(t, err)
org.Domains = []*domain.OrganizationDomain{
@@ -521,7 +555,7 @@ func TestGetOrganization(t *testing.T) {
organizationName := gofakeit.Name()
return test{
name: "happy path get using name",
testFunc: func(ctx context.Context, t *testing.T) *domain.Organization {
testFunc: func(t *testing.T) *domain.Organization {
organizationId := gofakeit.Name()
org := domain.Organization{
@@ -532,39 +566,36 @@ func TestGetOrganization(t *testing.T) {
}
// create organization
err := orgRepo.Create(ctx, &org)
err := orgRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
return &org
},
orgIdentifierCondition: orgRepo.NameCondition(organizationName),
orgIdentifierCondition: orgRepo.NameCondition(database.TextOperationEqual, organizationName),
}
}(),
{
name: "get non existent organization",
testFunc: func(ctx context.Context, t *testing.T) *domain.Organization {
testFunc: func(t *testing.T) *domain.Organization {
org := domain.Organization{
ID: "non existent org",
Name: "non existent org",
}
return &org
},
orgIdentifierCondition: orgRepo.NameCondition("non-existent-instance-name"),
orgIdentifierCondition: orgRepo.NameCondition(database.TextOperationEqual, "non-existent-instance-name"),
err: new(database.NoRowFoundError),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
orgRepo := repository.OrganizationRepository(pool)
var org *domain.Organization
if tt.testFunc != nil {
org = tt.testFunc(ctx, t)
org = tt.testFunc(t)
}
// get org values
returnedOrg, err := orgRepo.Get(ctx,
returnedOrg, err := orgRepo.Get(t.Context(), tx,
database.WithCondition(
database.And(
tt.orgIdentifierCondition,
@@ -592,11 +623,17 @@ func TestGetOrganization(t *testing.T) {
}
func TestListOrganization(t *testing.T) {
ctx := t.Context()
pool, stop, err := newEmbeddedDB(ctx)
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
defer stop()
organizationRepo := repository.OrganizationRepository(pool)
defer func() {
err := tx.Rollback(t.Context())
if err != nil {
t.Logf("error during rollback: %v", err)
}
}()
instanceRepo := repository.InstanceRepository()
organizationRepo := repository.OrganizationRepository()
// create instance
instanceId := gofakeit.Name()
@@ -609,33 +646,32 @@ func TestListOrganization(t *testing.T) {
ConsoleAppID: "consoleApp",
DefaultLanguage: "defaultLanguage",
}
instanceRepo := repository.InstanceRepository(pool)
err = instanceRepo.Create(ctx, &instance)
err = instanceRepo.Create(t.Context(), tx, &instance)
require.NoError(t, err)
type test struct {
name string
testFunc func(ctx context.Context, t *testing.T) []*domain.Organization
testFunc func(t *testing.T, tx database.QueryExecutor) []*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 {
testFunc: func(t *testing.T, tx database.QueryExecutor) []*domain.Organization {
noOfOrganizations := 1
organizations := make([]*domain.Organization, noOfOrganizations)
for i := range noOfOrganizations {
org := domain.Organization{
ID: gofakeit.Name(),
ID: strconv.Itoa(i),
Name: gofakeit.Name(),
InstanceID: instanceId,
State: domain.OrgStateActive,
}
// create organization
err := organizationRepo.Create(ctx, &org)
err := organizationRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
organizations[i] = &org
@@ -646,20 +682,20 @@ func TestListOrganization(t *testing.T) {
},
{
name: "happy path multiple organization no filter",
testFunc: func(ctx context.Context, t *testing.T) []*domain.Organization {
testFunc: func(t *testing.T, tx database.QueryExecutor) []*domain.Organization {
noOfOrganizations := 5
organizations := make([]*domain.Organization, noOfOrganizations)
for i := range noOfOrganizations {
org := domain.Organization{
ID: gofakeit.Name(),
ID: strconv.Itoa(i),
Name: gofakeit.Name(),
InstanceID: instanceId,
State: domain.OrgStateActive,
}
// create organization
err := organizationRepo.Create(ctx, &org)
err := organizationRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
organizations[i] = &org
@@ -672,7 +708,7 @@ func TestListOrganization(t *testing.T) {
organizationId := gofakeit.Name()
return test{
name: "organization filter on id",
testFunc: func(ctx context.Context, t *testing.T) []*domain.Organization {
testFunc: func(t *testing.T, tx database.QueryExecutor) []*domain.Organization {
// create organization
// this org is created as an additional org which should NOT
// be returned in the results of this test case
@@ -682,7 +718,7 @@ func TestListOrganization(t *testing.T) {
InstanceID: instanceId,
State: domain.OrgStateActive,
}
err = organizationRepo.Create(ctx, &org)
err = organizationRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
noOfOrganizations := 1
@@ -697,7 +733,7 @@ func TestListOrganization(t *testing.T) {
}
// create organization
err := organizationRepo.Create(ctx, &org)
err := organizationRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
organizations[i] = &org
@@ -705,12 +741,15 @@ func TestListOrganization(t *testing.T) {
return organizations
},
conditionClauses: []database.Condition{organizationRepo.IDCondition(organizationId)},
conditionClauses: []database.Condition{
organizationRepo.InstanceIDCondition(instanceId),
organizationRepo.IDCondition(organizationId),
},
}
}(),
{
name: "multiple organization filter on state",
testFunc: func(ctx context.Context, t *testing.T) []*domain.Organization {
testFunc: func(t *testing.T, tx database.QueryExecutor) []*domain.Organization {
// create organization
// this org is created as an additional org which should NOT
// be returned in the results of this test case
@@ -720,7 +759,7 @@ func TestListOrganization(t *testing.T) {
InstanceID: instanceId,
State: domain.OrgStateActive,
}
err = organizationRepo.Create(ctx, &org)
err = organizationRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
noOfOrganizations := 5
@@ -728,14 +767,14 @@ func TestListOrganization(t *testing.T) {
for i := range noOfOrganizations {
org := domain.Organization{
ID: gofakeit.Name(),
ID: strconv.Itoa(i),
Name: gofakeit.Name(),
InstanceID: instanceId,
State: domain.OrgStateInactive,
}
// create organization
err := organizationRepo.Create(ctx, &org)
err := organizationRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
organizations[i] = &org
@@ -743,13 +782,16 @@ func TestListOrganization(t *testing.T) {
return organizations
},
conditionClauses: []database.Condition{organizationRepo.StateCondition(domain.OrgStateInactive)},
conditionClauses: []database.Condition{
organizationRepo.InstanceIDCondition(instanceId),
organizationRepo.StateCondition(domain.OrgStateInactive),
},
},
func() test {
instanceId_2 := gofakeit.Name()
return test{
name: "multiple organization filter on instance",
testFunc: func(ctx context.Context, t *testing.T) []*domain.Organization {
testFunc: func(t *testing.T, tx database.QueryExecutor) []*domain.Organization {
// create instance 1
instanceId_1 := gofakeit.Name()
instance := domain.Instance{
@@ -761,8 +803,7 @@ func TestListOrganization(t *testing.T) {
ConsoleAppID: "consoleApp",
DefaultLanguage: "defaultLanguage",
}
instanceRepo := repository.InstanceRepository(pool)
err = instanceRepo.Create(ctx, &instance)
err = instanceRepo.Create(t.Context(), tx, &instance)
assert.Nil(t, err)
// create organization
@@ -774,7 +815,7 @@ func TestListOrganization(t *testing.T) {
InstanceID: instanceId_1,
State: domain.OrgStateActive,
}
err = organizationRepo.Create(ctx, &org)
err = organizationRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
// create instance 2
@@ -787,7 +828,7 @@ func TestListOrganization(t *testing.T) {
ConsoleAppID: "consoleApp",
DefaultLanguage: "defaultLanguage",
}
err = instanceRepo.Create(ctx, &instance_2)
err = instanceRepo.Create(t.Context(), tx, &instance_2)
assert.Nil(t, err)
noOfOrganizations := 5
@@ -795,14 +836,14 @@ func TestListOrganization(t *testing.T) {
for i := range noOfOrganizations {
org := domain.Organization{
ID: gofakeit.Name(),
ID: strconv.Itoa(i),
Name: gofakeit.Name(),
InstanceID: instanceId_2,
State: domain.OrgStateActive,
}
// create organization
err := organizationRepo.Create(ctx, &org)
err := organizationRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
organizations[i] = &org
@@ -816,22 +857,25 @@ func TestListOrganization(t *testing.T) {
}
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)
})
savepoint, err := tx.Begin(t.Context())
require.NoError(t, err)
defer func() {
err = savepoint.Rollback(t.Context())
if err != nil {
t.Logf("error during rollback: %v", err)
}
}()
organizations := tt.testFunc(t, savepoint)
organizations := tt.testFunc(ctx, t)
var condition database.Condition
condition := organizationRepo.InstanceIDCondition(instanceId)
if len(tt.conditionClauses) > 0 {
condition = database.And(tt.conditionClauses...)
}
// check organization values
returnedOrgs, err := organizationRepo.List(ctx,
returnedOrgs, err := organizationRepo.List(t.Context(), tx,
database.WithCondition(condition),
database.WithOrderByAscending(organizationRepo.CreatedAtColumn()),
database.WithOrderByAscending(organizationRepo.CreatedAtColumn(), organizationRepo.IDColumn()),
)
require.NoError(t, err)
if tt.noOrganizationReturned {
@@ -851,6 +895,15 @@ func TestListOrganization(t *testing.T) {
}
func TestDeleteOrganization(t *testing.T) {
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
t.Cleanup(func() {
_ = tx.Rollback(t.Context())
})
instanceRepo := repository.InstanceRepository()
organizationRepo := repository.OrganizationRepository()
// create instance
instanceId := gofakeit.Name()
instance := domain.Instance{
@@ -862,24 +915,22 @@ func TestDeleteOrganization(t *testing.T) {
ConsoleAppID: "consoleApp",
DefaultLanguage: "defaultLanguage",
}
instanceRepo := repository.InstanceRepository(pool)
err := instanceRepo.Create(t.Context(), &instance)
err = instanceRepo.Create(t.Context(), tx, &instance)
require.NoError(t, err)
type test struct {
name string
testFunc func(ctx context.Context, t *testing.T)
orgIdentifierCondition domain.OrgIdentifierCondition
testFunc func(t *testing.T)
orgIdentifierCondition database.Condition
noOfDeletedRows int64
}
tests := []test{
func() test {
organizationRepo := repository.OrganizationRepository(pool)
organizationId := gofakeit.Name()
var noOfOrganizations int64 = 1
return test{
name: "happy path delete organization filter id",
testFunc: func(ctx context.Context, t *testing.T) {
testFunc: func(t *testing.T) {
organizations := make([]*domain.Organization, noOfOrganizations)
for i := range noOfOrganizations {
@@ -891,7 +942,7 @@ func TestDeleteOrganization(t *testing.T) {
}
// create organization
err := organizationRepo.Create(ctx, &org)
err := organizationRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
organizations[i] = &org
@@ -902,12 +953,11 @@ func TestDeleteOrganization(t *testing.T) {
}
}(),
func() test {
organizationRepo := repository.OrganizationRepository(pool)
organizationName := gofakeit.Name()
var noOfOrganizations int64 = 1
return test{
name: "happy path delete organization filter name",
testFunc: func(ctx context.Context, t *testing.T) {
testFunc: func(t *testing.T) {
organizations := make([]*domain.Organization, noOfOrganizations)
for i := range noOfOrganizations {
@@ -919,30 +969,28 @@ func TestDeleteOrganization(t *testing.T) {
}
// create organization
err := organizationRepo.Create(ctx, &org)
err := organizationRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
organizations[i] = &org
}
},
orgIdentifierCondition: organizationRepo.NameCondition(organizationName),
orgIdentifierCondition: organizationRepo.NameCondition(database.TextOperationEqual, organizationName),
noOfDeletedRows: noOfOrganizations,
}
}(),
func() test {
organizationRepo := repository.OrganizationRepository(pool)
non_existent_organization_name := gofakeit.Name()
return test{
name: "delete non existent organization",
orgIdentifierCondition: organizationRepo.NameCondition(non_existent_organization_name),
orgIdentifierCondition: organizationRepo.NameCondition(database.TextOperationEqual, non_existent_organization_name),
}
}(),
func() test {
organizationRepo := repository.OrganizationRepository(pool)
organizationName := gofakeit.Name()
return test{
name: "deleted already deleted organization",
testFunc: func(ctx context.Context, t *testing.T) {
testFunc: func(t *testing.T) {
noOfOrganizations := 1
organizations := make([]*domain.Organization, noOfOrganizations)
for i := range noOfOrganizations {
@@ -955,21 +1003,23 @@ func TestDeleteOrganization(t *testing.T) {
}
// create organization
err := organizationRepo.Create(ctx, &org)
err := organizationRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
organizations[i] = &org
}
// delete organization
affectedRows, err := organizationRepo.Delete(ctx,
organizationRepo.NameCondition(organizationName),
organizations[0].InstanceID,
affectedRows, err := organizationRepo.Delete(t.Context(), tx,
database.And(
organizationRepo.InstanceIDCondition(organizations[0].InstanceID),
organizationRepo.NameCondition(database.TextOperationEqual, organizationName),
),
)
assert.Equal(t, int64(1), affectedRows)
require.NoError(t, err)
},
orgIdentifierCondition: organizationRepo.NameCondition(organizationName),
orgIdentifierCondition: organizationRepo.NameCondition(database.TextOperationEqual, organizationName),
// this test should return 0 affected rows as the org was already deleted
noOfDeletedRows: 0,
}
@@ -977,23 +1027,22 @@ func TestDeleteOrganization(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
organizationRepo := repository.OrganizationRepository(pool)
if tt.testFunc != nil {
tt.testFunc(ctx, t)
tt.testFunc(t)
}
// delete organization
noOfDeletedRows, err := organizationRepo.Delete(ctx,
tt.orgIdentifierCondition,
instanceId,
noOfDeletedRows, err := organizationRepo.Delete(t.Context(), tx,
database.And(
organizationRepo.InstanceIDCondition(instanceId),
tt.orgIdentifierCondition,
),
)
require.NoError(t, err)
assert.Equal(t, noOfDeletedRows, tt.noOfDeletedRows)
// check organization was deleted
organization, err := organizationRepo.Get(ctx,
organization, err := organizationRepo.Get(t.Context(), tx,
database.WithCondition(
database.And(
tt.orgIdentifierCondition,
@@ -1006,3 +1055,99 @@ func TestDeleteOrganization(t *testing.T) {
})
}
}
func TestGetOrganizationWithSubResources(t *testing.T) {
tx, err := pool.Begin(t.Context(), nil)
require.NoError(t, err)
t.Cleanup(func() {
_ = tx.Rollback(t.Context())
})
instanceRepo := repository.InstanceRepository()
orgRepo := repository.OrganizationRepository()
// create instance
instanceId := gofakeit.Name()
err = instanceRepo.Create(t.Context(), tx, &domain.Instance{
ID: instanceId,
Name: gofakeit.Name(),
DefaultOrgID: "defaultOrgId",
IAMProjectID: "iamProject",
ConsoleClientID: "consoleCLient",
ConsoleAppID: "consoleApp",
DefaultLanguage: "defaultLanguage",
})
require.NoError(t, err)
// create organization
org := domain.Organization{
ID: "1",
Name: "org-name",
InstanceID: instanceId,
State: domain.OrgStateActive,
}
err = orgRepo.Create(t.Context(), tx, &org)
require.NoError(t, err)
err = orgRepo.Create(t.Context(), tx, &domain.Organization{
ID: "without-sub-resources",
Name: "org-name-2",
InstanceID: instanceId,
State: domain.OrgStateActive,
})
require.NoError(t, err)
t.Run("domains", func(t *testing.T) {
domainRepo := repository.OrganizationDomainRepository()
domain1 := &domain.AddOrganizationDomain{
InstanceID: org.InstanceID,
OrgID: org.ID,
Domain: "domain1.com",
IsVerified: true,
IsPrimary: true,
}
err = domainRepo.Add(t.Context(), tx, domain1)
require.NoError(t, err)
domain2 := &domain.AddOrganizationDomain{
InstanceID: org.InstanceID,
OrgID: org.ID,
Domain: "domain2.com",
IsVerified: false,
IsPrimary: false,
}
err = domainRepo.Add(t.Context(), tx, domain2)
require.NoError(t, err)
t.Run("org by domain", func(t *testing.T) {
orgRepo := orgRepo.LoadDomains()
returnedOrg, err := orgRepo.Get(t.Context(), tx,
database.WithCondition(
database.And(
orgRepo.InstanceIDCondition(instanceId),
orgRepo.ExistsDomain(domainRepo.DomainCondition(database.TextOperationEqual, domain1.Domain)),
),
),
)
require.NoError(t, err)
assert.Equal(t, org.ID, returnedOrg.ID)
assert.Len(t, returnedOrg.Domains, 2)
})
t.Run("ensure org by domain works without LoadDomains", func(t *testing.T) {
returnedOrg, err := orgRepo.Get(t.Context(), tx,
database.WithCondition(
database.And(
orgRepo.InstanceIDCondition(instanceId),
orgRepo.ExistsDomain(domainRepo.DomainCondition(database.TextOperationEqual, domain1.Domain)),
),
),
)
require.NoError(t, err)
assert.Equal(t, org.ID, returnedOrg.ID)
assert.Len(t, returnedOrg.Domains, 0)
})
})
}
@@ -4,10 +4,6 @@ import (
"github.com/zitadel/zitadel/backend/v3/storage/database"
)
type repository struct {
client database.QueryExecutor
}
func writeCondition(
builder *database.StatementBuilder,
condition database.Condition,
+13 -19
View File
@@ -12,16 +12,10 @@ const queryUserStmt = `SELECT instance_id, org_id, id, username, type, created_a
` first_name, last_name, email_address, email_verified_at, phone_number, phone_verified_at, description` +
` FROM users_view users`
type user struct {
repository
}
type user struct{}
func UserRepository(client database.QueryExecutor) domain.UserRepository {
return &user{
repository: repository{
client: client,
},
}
func UserRepository() domain.UserRepository {
return new(user)
}
var _ domain.UserRepository = (*user)(nil)
@@ -31,17 +25,17 @@ var _ domain.UserRepository = (*user)(nil)
// -------------------------------------------------------------
// Human implements [domain.UserRepository].
func (u *user) Human() domain.HumanRepository {
func (u user) Human() domain.HumanRepository {
return &userHuman{user: u}
}
// Machine implements [domain.UserRepository].
func (u *user) Machine() domain.MachineRepository {
func (u user) Machine() domain.MachineRepository {
return &userMachine{user: u}
}
// List implements [domain.UserRepository].
func (u *user) List(ctx context.Context, opts ...database.QueryOption) (users []*domain.User, err error) {
func (u user) List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (users []*domain.User, err error) {
options := new(database.QueryOpts)
for _, opt := range opts {
opt(options)
@@ -54,7 +48,7 @@ func (u *user) List(ctx context.Context, opts ...database.QueryOption) (users []
options.WriteLimit(&builder)
options.WriteOffset(&builder)
rows, err := u.client.Query(ctx, builder.String(), builder.Args()...)
rows, err := client.Query(ctx, builder.String(), builder.Args()...)
if err != nil {
return nil, err
}
@@ -79,7 +73,7 @@ func (u *user) List(ctx context.Context, opts ...database.QueryOption) (users []
}
// Get implements [domain.UserRepository].
func (u *user) Get(ctx context.Context, opts ...database.QueryOption) (*domain.User, error) {
func (u user) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.User, error) {
options := new(database.QueryOpts)
for _, opt := range opts {
opt(options)
@@ -92,7 +86,7 @@ func (u *user) Get(ctx context.Context, opts ...database.QueryOption) (*domain.U
options.WriteLimit(&builder)
options.WriteOffset(&builder)
return scanUser(u.client.QueryRow(ctx, builder.String(), builder.Args()...))
return scanUser(client.QueryRow(ctx, builder.String(), builder.Args()...))
}
const (
@@ -105,7 +99,7 @@ const (
)
// Create implements [domain.UserRepository].
func (u *user) Create(ctx context.Context, user *domain.User) error {
func (u user) Create(ctx context.Context, client database.QueryExecutor, user *domain.User) error {
builder := database.StatementBuilder{}
builder.AppendArgs(user.InstanceID, user.OrgID, user.ID, user.Username, user.Traits.Type())
switch trait := user.Traits.(type) {
@@ -116,15 +110,15 @@ func (u *user) Create(ctx context.Context, user *domain.User) error {
builder.WriteString(createMachineStmt)
builder.AppendArgs(trait.Description)
}
return u.client.QueryRow(ctx, builder.String(), builder.Args()...).Scan(&user.CreatedAt, &user.UpdatedAt)
return client.QueryRow(ctx, builder.String(), builder.Args()...).Scan(&user.CreatedAt, &user.UpdatedAt)
}
// Delete implements [domain.UserRepository].
func (u *user) Delete(ctx context.Context, condition database.Condition) error {
func (u user) Delete(ctx context.Context, client database.QueryExecutor, condition database.Condition) error {
builder := database.StatementBuilder{}
builder.WriteString("DELETE FROM users")
writeCondition(&builder, condition)
_, err := u.client.Exec(ctx, builder.String(), builder.Args()...)
_, err := client.Exec(ctx, builder.String(), builder.Args()...)
return err
}
@@ -13,7 +13,7 @@ import (
// -------------------------------------------------------------
type userHuman struct {
*user
user
}
var _ domain.HumanRepository = (*userHuman)(nil)
@@ -21,14 +21,14 @@ var _ domain.HumanRepository = (*userHuman)(nil)
const userEmailQuery = `SELECT h.email_address, h.email_verified_at FROM user_humans h`
// GetEmail implements [domain.HumanRepository].
func (u *userHuman) GetEmail(ctx context.Context, condition database.Condition) (*domain.Email, error) {
func (u *userHuman) GetEmail(ctx context.Context, client database.QueryExecutor, condition database.Condition) (*domain.Email, error) {
var email domain.Email
builder := database.StatementBuilder{}
builder.WriteString(userEmailQuery)
writeCondition(&builder, condition)
err := u.client.QueryRow(ctx, builder.String(), builder.Args()...).Scan(
err := client.QueryRow(ctx, builder.String(), builder.Args()...).Scan(
&email.Address,
&email.VerifiedAt,
)
@@ -39,7 +39,7 @@ func (u *userHuman) GetEmail(ctx context.Context, condition database.Condition)
}
// Update implements [domain.HumanRepository].
func (h userHuman) Update(ctx context.Context, condition database.Condition, changes ...database.Change) error {
func (h userHuman) Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, changes ...database.Change) error {
builder := database.StatementBuilder{}
builder.WriteString(`UPDATE human_users SET `)
database.Changes(changes).Write(&builder)
@@ -47,7 +47,7 @@ func (h userHuman) Update(ctx context.Context, condition database.Condition, cha
stmt := builder.String()
_, err := h.client.Exec(ctx, stmt, builder.Args()...)
_, err := client.Exec(ctx, stmt, builder.Args()...)
return err
}
@@ -8,7 +8,7 @@ import (
)
type userMachine struct {
*user
user
}
var _ domain.MachineRepository = (*userMachine)(nil)
@@ -18,14 +18,14 @@ var _ domain.MachineRepository = (*userMachine)(nil)
// -------------------------------------------------------------
// Update implements [domain.MachineRepository].
func (m userMachine) Update(ctx context.Context, condition database.Condition, changes ...database.Change) error {
func (m userMachine) Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, changes ...database.Change) error {
builder := database.StatementBuilder{}
builder.WriteString("UPDATE user_machines SET ")
database.Changes(changes).Write(&builder)
writeCondition(&builder, condition)
m.writeReturning()
_, err := m.client.Exec(ctx, builder.String(), builder.Args()...)
_, err := client.Exec(ctx, builder.String(), builder.Args()...)
return err
}