feat: test event reduction to relational tables (#11718)

# Which Problems Are Solved

This PR provides an example for testing DB reduction to relational
tables without exploiting integration tests.

# How the Problems Are Solved

- Set up a disposable embedded Postgres DB used to store the results of
the calls to the reducers and verify such results
- Add utility method to return transactions from the existing DB pool.
These transactions can be used by the single test cases to insert and
read data from DB
- Add a test to verify that the handler contains the expected reducers
(checking also that they are on the right aggregate type)
- Add tests to verify the single reduce functions. A parent test creates
the required objects to satisfy the foreign key constraints.

# Additional Changes

  - Typo fixed in `postgres.DecodeConfig()`
  - Discard unneeded `projectionName` parameter for `Executor` function
 
# Additional Context

- Closes #11430
This commit is contained in:
Marco A.
2026-03-02 16:44:51 +00:00
committed by GitHub
parent d3434dd46c
commit 3ebeb61b80
7 changed files with 338 additions and 318 deletions
+4
View File
@@ -2,6 +2,7 @@ package database
import (
"context"
"database/sql"
)
// Pool is a connection pool. e.g. pgxpool
@@ -20,6 +21,9 @@ type PoolTest interface {
Pool
// MigrateTest is the same as [Migrator] but executes the migrations multiple times instead of only once.
MigrateTest(ctx context.Context) error
// RawDB returns an *[sql.DB] handle backed by the same database as the [PoolTest].
// The returned handle is intended for use in tests, and the caller is responsible for closing it when done.
RawDB() *sql.DB
}
// Connection is a single database connection which can be released back to the pool.
@@ -81,9 +81,7 @@ func DecodeConfig(input any) (database.Connector, error) {
if err = decoder.Decode(c); err != nil {
return nil, err
}
return &Config{
Config: &pgxpool.Config{},
}, nil
return connector, nil
}
return nil, errors.New("invalid configuration")
}
@@ -2,8 +2,10 @@ package postgres
import (
"context"
"database/sql"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/jackc/pgx/v5/stdlib"
"github.com/zitadel/zitadel/backend/v3/storage/database"
"github.com/zitadel/zitadel/backend/v3/storage/database/dialect/postgres/migration"
@@ -13,7 +15,13 @@ type pgxPool struct {
*pgxpool.Pool
}
// RawDB implements [database.PoolTest].
func (p *pgxPool) RawDB() *sql.DB {
return stdlib.OpenDBFromPool(p.Pool)
}
var _ database.Pool = (*pgxPool)(nil)
var _ database.PoolTest = (*pgxPool)(nil)
func PGxPool(pool *pgxpool.Pool) *pgxPool {
return &pgxPool{
@@ -1,310 +0,0 @@
//go:build integration
package events_test
import (
"testing"
"time"
"github.com/brianvoe/gofakeit/v6"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/backend/v3/domain"
"github.com/zitadel/zitadel/backend/v3/storage/database"
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
"github.com/zitadel/zitadel/internal/integration"
v2beta "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
"github.com/zitadel/zitadel/pkg/grpc/system"
)
func TestServer_TestInstanceDomainReduces(t *testing.T) {
instance := integration.NewInstance(CTX)
instanceRepo := repository.InstanceRepository()
instanceDomainRepo := repository.InstanceDomainRepository()
t.Cleanup(func() {
_, err := instance.Client.InstanceV2Beta.DeleteInstance(CTX, &v2beta.DeleteInstanceRequest{
InstanceId: instance.Instance.Id,
})
if err != nil {
t.Logf("Failed to delete instance on cleanup: %v", err)
}
})
// Wait for instance to be created
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(t *assert.CollectT) {
_, err := instanceRepo.Get(CTX, pool,
database.WithCondition(instanceRepo.IDCondition(instance.ID())),
)
assert.NoError(t, err)
}, retryDuration, tick)
t.Run("test instance custom domain add reduces", func(t *testing.T) {
// Add a domain to the instance
domainName := gofakeit.DomainName()
beforeAdd := time.Now()
_, err := instance.Client.InstanceV2Beta.AddCustomDomain(CTX, &v2beta.AddCustomDomainRequest{
InstanceId: instance.Instance.Id,
Domain: domainName,
})
require.NoError(t, err)
afterAdd := time.Now()
t.Cleanup(func() {
_, err := instance.Client.InstanceV2Beta.RemoveCustomDomain(CTX, &v2beta.RemoveCustomDomainRequest{
InstanceId: instance.Instance.Id,
Domain: domainName,
})
if err != nil {
t.Logf("Failed to delete instance domain on cleanup: %v", err)
}
})
// Test that domain add reduces
retryDuration, tick = integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(t *assert.CollectT) {
domain, err := instanceDomainRepo.Get(CTX, pool,
database.WithCondition(
database.And(
instanceDomainRepo.InstanceIDCondition(instance.Instance.Id),
instanceDomainRepo.DomainCondition(database.TextOperationEqual, domainName),
instanceDomainRepo.TypeCondition(domain.DomainTypeCustom),
),
),
)
require.NoError(t, err)
// event instance.domain.added
assert.Equal(t, domainName, domain.Domain)
assert.Equal(t, instance.Instance.Id, domain.InstanceID)
assert.False(t, *domain.IsPrimary)
assert.WithinRange(t, domain.CreatedAt, beforeAdd, afterAdd)
assert.WithinRange(t, domain.UpdatedAt, beforeAdd, afterAdd)
}, retryDuration, tick)
})
t.Run("test instance custom domain set primary reduces", func(t *testing.T) {
// Add a domain to the instance
domainName := gofakeit.DomainName()
_, err := instance.Client.InstanceV2Beta.AddCustomDomain(CTX, &v2beta.AddCustomDomainRequest{
InstanceId: instance.Instance.Id,
Domain: domainName,
})
require.NoError(t, err)
t.Cleanup(func() {
// first we change the primary domain to something else
domain, err := instanceDomainRepo.Get(CTX, pool,
database.WithCondition(
database.And(
instanceDomainRepo.InstanceIDCondition(instance.Instance.Id),
instanceDomainRepo.TypeCondition(domain.DomainTypeCustom),
instanceDomainRepo.IsPrimaryCondition(false),
),
),
database.WithLimit(1),
)
require.NoError(t, err)
_, err = SystemClient.SetPrimaryDomain(CTX, &system.SetPrimaryDomainRequest{
InstanceId: instance.Instance.Id,
Domain: domain.Domain,
})
require.NoError(t, err)
_, err = instance.Client.InstanceV2Beta.RemoveCustomDomain(CTX, &v2beta.RemoveCustomDomainRequest{
InstanceId: instance.Instance.Id,
Domain: domainName,
})
if err != nil {
t.Logf("Failed to delete instance domain on cleanup: %v", err)
}
})
// Wait for domain to be created
retryDuration, tick = integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(t *assert.CollectT) {
domain, err := instanceDomainRepo.Get(CTX, pool,
database.WithCondition(
database.And(
instanceDomainRepo.InstanceIDCondition(instance.Instance.Id),
instanceDomainRepo.DomainCondition(database.TextOperationEqual, domainName),
instanceDomainRepo.TypeCondition(domain.DomainTypeCustom),
),
),
)
require.NoError(t, err)
require.False(t, *domain.IsPrimary)
assert.Equal(t, domainName, domain.Domain)
}, retryDuration, tick)
// Set domain as primary
beforeSetPrimary := time.Now()
_, err = SystemClient.SetPrimaryDomain(CTX, &system.SetPrimaryDomainRequest{
InstanceId: instance.Instance.Id,
Domain: domainName,
})
require.NoError(t, err)
afterSetPrimary := time.Now()
// Test that set primary reduces
retryDuration, tick = integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(t *assert.CollectT) {
domain, err := instanceDomainRepo.Get(CTX, pool,
database.WithCondition(
database.And(
instanceDomainRepo.InstanceIDCondition(instance.Instance.Id),
instanceDomainRepo.IsPrimaryCondition(true),
instanceDomainRepo.TypeCondition(domain.DomainTypeCustom),
),
),
)
require.NoError(t, err)
// event instance.domain.primary.set
assert.Equal(t, domainName, domain.Domain)
assert.True(t, *domain.IsPrimary)
assert.WithinRange(t, domain.UpdatedAt, beforeSetPrimary, afterSetPrimary)
}, retryDuration, tick)
})
t.Run("test instance custom domain remove reduces", func(t *testing.T) {
// Add a domain to the instance
domainName := gofakeit.DomainName()
_, err := instance.Client.InstanceV2Beta.AddCustomDomain(CTX, &v2beta.AddCustomDomainRequest{
InstanceId: instance.Instance.Id,
Domain: domainName,
})
require.NoError(t, err)
// Wait for domain to be created and verify it exists
retryDuration, tick = integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(t *assert.CollectT) {
_, err := instanceDomainRepo.Get(CTX, pool,
database.WithCondition(
database.And(
instanceDomainRepo.InstanceIDCondition(instance.Instance.Id),
instanceDomainRepo.DomainCondition(database.TextOperationEqual, domainName),
instanceDomainRepo.TypeCondition(domain.DomainTypeCustom),
),
),
)
require.NoError(t, err)
}, retryDuration, tick)
// Remove the domain
_, err = instance.Client.InstanceV2Beta.RemoveCustomDomain(CTX, &v2beta.RemoveCustomDomainRequest{
InstanceId: instance.Instance.Id,
Domain: domainName,
})
require.NoError(t, err)
// Test that domain remove reduces
retryDuration, tick = integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(t *assert.CollectT) {
domain, err := instanceDomainRepo.Get(CTX, pool,
database.WithCondition(
database.And(
instanceDomainRepo.InstanceIDCondition(instance.Instance.Id),
instanceDomainRepo.DomainCondition(database.TextOperationEqual, domainName),
instanceDomainRepo.TypeCondition(domain.DomainTypeCustom),
),
),
)
// event instance.domain.removed
assert.Nil(t, domain)
require.ErrorIs(t, err, new(database.NoRowFoundError))
}, retryDuration, tick)
})
t.Run("test instance trusted domain add reduces", func(t *testing.T) {
// Add a domain to the instance
domainName := gofakeit.DomainName()
beforeAdd := time.Now()
_, err := instance.Client.InstanceV2Beta.AddTrustedDomain(CTX, &v2beta.AddTrustedDomainRequest{
InstanceId: instance.Instance.Id,
Domain: domainName,
})
require.NoError(t, err)
afterAdd := time.Now()
t.Cleanup(func() {
_, err := instance.Client.InstanceV2Beta.RemoveTrustedDomain(CTX, &v2beta.RemoveTrustedDomainRequest{
InstanceId: instance.Instance.Id,
Domain: domainName,
})
if err != nil {
t.Logf("Failed to delete instance domain on cleanup: %v", err)
}
})
// Test that domain add reduces
retryDuration, tick = integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(t *assert.CollectT) {
domain, err := instanceDomainRepo.Get(CTX, pool,
database.WithCondition(
database.And(
instanceDomainRepo.InstanceIDCondition(instance.Instance.Id),
instanceDomainRepo.DomainCondition(database.TextOperationEqual, domainName),
instanceDomainRepo.TypeCondition(domain.DomainTypeTrusted),
),
),
)
require.NoError(t, err)
// event instance.domain.added
assert.Equal(t, domainName, domain.Domain)
assert.Equal(t, instance.Instance.Id, domain.InstanceID)
assert.WithinRange(t, domain.CreatedAt, beforeAdd, afterAdd)
assert.WithinRange(t, domain.UpdatedAt, beforeAdd, afterAdd)
}, retryDuration, tick)
})
t.Run("test instance trusted domain remove reduces", func(t *testing.T) {
// Add a domain to the instance
domainName := gofakeit.DomainName()
_, err := instance.Client.InstanceV2Beta.AddTrustedDomain(CTX, &v2beta.AddTrustedDomainRequest{
InstanceId: instance.Instance.Id,
Domain: domainName,
})
require.NoError(t, err)
// Wait for domain to be created and verify it exists
retryDuration, tick = integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(t *assert.CollectT) {
_, err := instanceDomainRepo.Get(CTX, pool,
database.WithCondition(
database.And(
instanceDomainRepo.InstanceIDCondition(instance.Instance.Id),
instanceDomainRepo.DomainCondition(database.TextOperationEqual, domainName),
instanceDomainRepo.TypeCondition(domain.DomainTypeTrusted),
),
),
)
require.NoError(t, err)
}, retryDuration, tick)
// Remove the domain
_, err = instance.Client.InstanceV2Beta.RemoveTrustedDomain(CTX, &v2beta.RemoveTrustedDomainRequest{
InstanceId: instance.Instance.Id,
Domain: domainName,
})
require.NoError(t, err)
// Test that domain remove reduces
retryDuration, tick = integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(t *assert.CollectT) {
domain, err := instanceDomainRepo.Get(CTX, pool,
database.WithCondition(
database.And(
instanceDomainRepo.InstanceIDCondition(instance.Instance.Id),
instanceDomainRepo.DomainCondition(database.TextOperationEqual, domainName),
instanceDomainRepo.TypeCondition(domain.DomainTypeTrusted),
),
),
)
// event instance.domain.removed
assert.Nil(t, domain)
require.ErrorIs(t, err, new(database.NoRowFoundError))
}, retryDuration, tick)
})
}