mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
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]>
205 lines
6.2 KiB
Go
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{})
|
|
})
|
|
}
|