Files
zitadel/internal/query/projection/instance_domain_relational_test.go
T
f138082b64 feat: add administrator repository (#11803)
Introduces the `administrators` relational table and repository layer
for the v3 backend, unifying instance, organization, project, and
project-grant membership into a single scoped model.

Closes #10220

## Changes

- **Domain**: `Administrator` struct, `AdministratorScope` enum,
`AdministratorRepository` interface with typed CRUD, column, condition,
and change helpers. Generated enumer and GoMock.
- **Migration** (`016`): `zitadel.administrators` table with a generated
`id`, scope-alignment check constraint, cascading foreign keys, and a
child `administrator_roles` table.
- **Repository**: CTE-based role mutations (`AddRole`, `RemoveRole`,
`SetRoles`) for atomicity. Roles aggregated via `ARRAY_AGG`.
- **Projection**: Event reducers for
`member.added/changed/removed/cascade_removed` across all four admin
scopes.
- **Fixes**: `Rows.Close()` error wrapping; `TextArray` nil-safe scan +
full `pgtype` array interface; `authorization` and `projectGrant` scan
helpers fixed for `ARRAY_AGG` under `database/sql`; removed unused `ctx`
parameter from `callReduce`.

---------

Co-authored-by: Marco Ardizzone <[email protected]>
2026-03-18 10:24:09 +01:00

205 lines
6.2 KiB
Go

package projection
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/repository/instance"
)
func TestInstanceDomainReducers(t *testing.T) {
handler := &relationalTablesProjection{}
rawTx, tx := getTransactions(t)
t.Cleanup(func() {
require.NoError(t, rawTx.Rollback())
})
ctx := t.Context()
instanceDomainRepo := repository.InstanceDomainRepository()
instanceRepo := repository.InstanceRepository()
err := instanceRepo.Create(ctx, tx, &domain.Instance{
ID: "123",
Name: "my instance",
DefaultOrgID: gofakeit.UUID(),
IAMProjectID: gofakeit.UUID(),
ConsoleClientID: gofakeit.UUID(),
ConsoleAppID: gofakeit.UUID(),
DefaultLanguage: gofakeit.LanguageAbbreviation(),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
})
require.NoError(t, err)
t.Run("'custom domain added' reducer should create custom domain", func(t *testing.T) {
// Given
domainName := "test-domain.com"
domainAddedEvt := instance.NewDomainAddedEvent(ctx, &instance.NewAggregate("123").Aggregate, domainName, false)
// Test
res := callReduce(t, rawTx, handler, domainAddedEvt)
require.True(t, res)
// Verify
domain, err := instanceDomainRepo.Get(ctx, tx,
database.WithCondition(
database.And(
instanceDomainRepo.InstanceIDCondition("123"),
instanceDomainRepo.DomainCondition(database.TextOperationEqual, domainName),
instanceDomainRepo.TypeCondition(domain.DomainTypeCustom),
),
),
)
require.NoError(t, err)
assert.Equal(t, domainName, domain.Domain)
assert.Equal(t, "123", domain.InstanceID)
require.NotNil(t, domain.IsPrimary)
assert.False(t, *domain.IsPrimary)
assert.NotZero(t, domain.CreatedAt)
assert.NotZero(t, domain.UpdatedAt)
})
t.Run("'primary domain set' reducer should update existing domain", func(t *testing.T) {
// Given
domainName := "test-domain-primary-set.com"
err := instanceDomainRepo.Add(ctx, tx, &domain.AddInstanceDomain{
InstanceID: "123",
Domain: domainName,
Type: domain.DomainTypeCustom,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
IsPrimary: new(bool),
IsGenerated: new(bool),
})
require.NoError(t, err)
domainPrimarySetEvt := instance.NewDomainPrimarySetEvent(ctx, &instance.NewAggregate("123").Aggregate, domainName)
// Test
res := callReduce(t, rawTx, handler, domainPrimarySetEvt)
require.True(t, res)
// Verify
domain, err := instanceDomainRepo.Get(ctx, tx,
database.WithCondition(
database.And(
instanceDomainRepo.InstanceIDCondition("123"),
instanceDomainRepo.DomainCondition(database.TextOperationEqual, domainName),
instanceDomainRepo.TypeCondition(domain.DomainTypeCustom),
instanceDomainRepo.IsPrimaryCondition(true),
),
),
)
require.NoError(t, err)
assert.Equal(t, domainName, domain.Domain)
assert.Equal(t, "123", domain.InstanceID)
require.NotNil(t, domain.IsPrimary)
assert.True(t, *domain.IsPrimary)
assert.NotZero(t, domain.CreatedAt)
assert.NotZero(t, domain.UpdatedAt)
})
t.Run("'custom domain removed' reducer should remove custom domain", func(t *testing.T) {
// Given
domainName := "test-domain-removed.com"
err := instanceDomainRepo.Add(ctx, tx, &domain.AddInstanceDomain{
InstanceID: "123",
Domain: domainName,
Type: domain.DomainTypeCustom,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
IsPrimary: new(bool),
IsGenerated: new(bool),
})
require.NoError(t, err)
domainRemovedEvt := instance.NewDomainRemovedEvent(ctx, &instance.NewAggregate("123").Aggregate, domainName)
// Test
res := callReduce(t, rawTx, handler, domainRemovedEvt)
require.True(t, res)
// Verify
_, err = instanceDomainRepo.Get(ctx, tx,
database.WithCondition(
database.And(
instanceDomainRepo.InstanceIDCondition("123"),
instanceDomainRepo.DomainCondition(database.TextOperationEqual, domainName),
instanceDomainRepo.TypeCondition(domain.DomainTypeCustom),
),
),
)
require.Error(t, err)
assert.ErrorIs(t, err, &database.NoRowFoundError{})
})
t.Run("'trusted domain added' reducer should create trusted domain", func(t *testing.T) {
// Given
domainName := "trusted-test-domain.com"
trustedDomainAddedEvt := instance.NewTrustedDomainAddedEvent(ctx, &instance.NewAggregate("123").Aggregate, domainName)
// Test
res := callReduce(t, rawTx, handler, trustedDomainAddedEvt)
require.True(t, res)
// Verify
domain, err := instanceDomainRepo.Get(ctx, tx,
database.WithCondition(
database.And(
instanceDomainRepo.InstanceIDCondition("123"),
instanceDomainRepo.DomainCondition(database.TextOperationEqual, domainName),
instanceDomainRepo.TypeCondition(domain.DomainTypeTrusted),
),
),
)
require.NoError(t, err)
assert.Equal(t, domainName, domain.Domain)
assert.Equal(t, "123", domain.InstanceID)
assert.NotZero(t, domain.CreatedAt)
assert.NotZero(t, domain.UpdatedAt)
})
t.Run("'trusted domain removed' reducer should remove trusted domain", func(t *testing.T) {
// Given
domainName := "test-trusted-domain-removed.com"
err := instanceDomainRepo.Add(ctx, tx, &domain.AddInstanceDomain{
InstanceID: "123",
Domain: domainName,
Type: domain.DomainTypeTrusted,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
})
require.NoError(t, err)
trustedDomainRemovedEvt := instance.NewTrustedDomainRemovedEvent(ctx, &instance.NewAggregate("123").Aggregate, domainName)
// Test
res := callReduce(t, rawTx, handler, trustedDomainRemovedEvt)
require.True(t, res)
// Verify
_, err = instanceDomainRepo.Get(ctx, tx,
database.WithCondition(
database.And(
instanceDomainRepo.InstanceIDCondition("123"),
instanceDomainRepo.DomainCondition(database.TextOperationEqual, domainName),
instanceDomainRepo.TypeCondition(domain.DomainTypeTrusted),
),
),
)
require.Error(t, err)
assert.ErrorIs(t, err, &database.NoRowFoundError{})
})
}