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]>
117 lines
2.9 KiB
Go
117 lines
2.9 KiB
Go
package projection
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"slices"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
|
"github.com/zitadel/zitadel/backend/v3/storage/database/dialect/postgres/embedded"
|
|
v3_sql "github.com/zitadel/zitadel/backend/v3/storage/database/dialect/sql"
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
|
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
|
)
|
|
|
|
var (
|
|
pool database.PoolTest
|
|
rawDB *sql.DB
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
os.Exit(runTests(m))
|
|
}
|
|
|
|
func runTests(m *testing.M) int {
|
|
var stop func()
|
|
var err error
|
|
ctx := context.Background()
|
|
pool, stop, err = newEmbeddedDB(ctx)
|
|
if err != nil {
|
|
log.Printf("error with embedded postgres database: %v", err)
|
|
return 1
|
|
}
|
|
defer stop()
|
|
|
|
rawDB = pool.RawDB()
|
|
defer rawDB.Close()
|
|
defer pool.Close(ctx)
|
|
|
|
return m.Run()
|
|
}
|
|
|
|
func newEmbeddedDB(ctx context.Context) (pool database.PoolTest, stop func(), err error) {
|
|
connector, stop, err := embedded.StartEmbedded()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("unable to start embedded postgres: %w", err)
|
|
}
|
|
|
|
dummyPool, err := connector.Connect(ctx)
|
|
if err != nil {
|
|
return nil, stop, fmt.Errorf("unable to connect to embedded postgres: %w", err)
|
|
}
|
|
|
|
pool, ok := dummyPool.(database.PoolTest)
|
|
if !ok {
|
|
return nil, stop, fmt.Errorf("expecting database.PoolTest, got %T", dummyPool)
|
|
}
|
|
|
|
err = pool.MigrateTest(ctx)
|
|
if err != nil {
|
|
return nil, stop, fmt.Errorf("unable to migrate database: %w", err)
|
|
}
|
|
|
|
return pool, stop, err
|
|
}
|
|
|
|
func getTransactions(t *testing.T) (rawTx *sql.Tx, v3SQLTx *v3_sql.Transaction) {
|
|
rawTx, err := rawDB.Begin()
|
|
require.NoError(t, err)
|
|
v3SQLTx = v3_sql.SQLTx(rawTx)
|
|
return
|
|
}
|
|
|
|
// callReduce on the input projection tests that:
|
|
//
|
|
// 1. the aggregate type of the input event exists
|
|
// 2. the event type of the input event exists for that aggregate type
|
|
// 3. the reducer function for the input event is not nil
|
|
// 4. the reducer function for the input event executes without errors
|
|
//
|
|
// Returns false if any of the above checks fails
|
|
func callReduce(t *testing.T, tx *sql.Tx, projection handler.Projection, event eventstore.Event) bool {
|
|
reducers := projection.Reducers()
|
|
aggregateReducerIdx := slices.IndexFunc(reducers, func(r handler.AggregateReducer) bool {
|
|
return r.Aggregate == event.Aggregate().Type
|
|
})
|
|
if !assert.Greater(t, aggregateReducerIdx, -1) {
|
|
return false
|
|
}
|
|
|
|
eventReducerIdx := slices.IndexFunc(reducers[aggregateReducerIdx].EventReducers, func(er handler.EventReducer) bool {
|
|
return er.Event == event.Type()
|
|
})
|
|
if !assert.Greater(t, eventReducerIdx, -1) {
|
|
return false
|
|
}
|
|
|
|
reduceFn := reducers[aggregateReducerIdx].EventReducers[eventReducerIdx].Reduce
|
|
if !assert.NotNil(t, reduceFn) {
|
|
return false
|
|
}
|
|
|
|
stmt, err := reduceFn(event)
|
|
if !assert.NoError(t, err) {
|
|
return false
|
|
}
|
|
|
|
err = stmt.Execute(t.Context(), tx, "")
|
|
return assert.NoError(t, err)
|
|
}
|