mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-06 06:12:16 +00:00
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]>
This commit is contained in:
co-authored by
Marco Ardizzone
parent
6d90a120a6
commit
f138082b64
@@ -0,0 +1,226 @@
|
||||
package projection
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
v3_sql "github.com/zitadel/zitadel/backend/v3/storage/database/dialect/sql"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
"github.com/zitadel/zitadel/internal/repository/project"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func (p *relationalTablesProjection) reduceInstanceAdminAdded(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*instance.MemberAddedEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.createAdministratorStatement(e, &domain.Administrator{
|
||||
InstanceID: e.Aggregate().InstanceID,
|
||||
UserID: e.UserID,
|
||||
Scope: domain.AdministratorScopeInstance,
|
||||
Roles: e.Roles,
|
||||
CreatedAt: e.CreationDate(),
|
||||
UpdatedAt: e.CreationDate(),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *relationalTablesProjection) reduceInstanceAdminChanged(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*instance.MemberChangedEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.updateAdministratorStatement(e,
|
||||
repository.AdministratorRepository().InstanceAdministratorCondition(e.Aggregate().InstanceID, e.UserID),
|
||||
repository.AdministratorRepository().SetUpdatedAt(e.CreationDate()),
|
||||
repository.AdministratorRepository().SetRoles(e.Roles),
|
||||
)
|
||||
}
|
||||
|
||||
func (p *relationalTablesProjection) reduceInstanceAdminRemoved(event eventstore.Event) (*handler.Statement, error) {
|
||||
switch e := event.(type) {
|
||||
case *instance.MemberRemovedEvent:
|
||||
return p.removeAdministratorStatement(e,
|
||||
repository.AdministratorRepository().InstanceAdministratorCondition(e.Aggregate().InstanceID, e.UserID),
|
||||
)
|
||||
case *instance.MemberCascadeRemovedEvent:
|
||||
return p.removeAdministratorStatement(e,
|
||||
repository.AdministratorRepository().InstanceAdministratorCondition(e.Aggregate().InstanceID, e.UserID),
|
||||
)
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-v3ADM03", "reduce.wrong.event.type %v", []eventstore.EventType{instance.MemberRemovedEventType, instance.MemberCascadeRemovedEventType})
|
||||
}
|
||||
}
|
||||
|
||||
func (p *relationalTablesProjection) reduceOrganizationAdminAdded(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*org.MemberAddedEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.createAdministratorStatement(e, &domain.Administrator{
|
||||
InstanceID: e.Aggregate().InstanceID,
|
||||
UserID: e.UserID,
|
||||
Scope: domain.AdministratorScopeOrganization,
|
||||
OrganizationID: stringPtr(e.Aggregate().ID),
|
||||
Roles: e.Roles,
|
||||
CreatedAt: e.CreationDate(),
|
||||
UpdatedAt: e.CreationDate(),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *relationalTablesProjection) reduceOrganizationAdminChanged(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*org.MemberChangedEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.updateAdministratorStatement(e,
|
||||
repository.AdministratorRepository().OrganizationAdministratorCondition(e.Aggregate().InstanceID, e.Aggregate().ID, e.UserID),
|
||||
repository.AdministratorRepository().SetUpdatedAt(e.CreationDate()),
|
||||
repository.AdministratorRepository().SetRoles(e.Roles),
|
||||
)
|
||||
}
|
||||
|
||||
func (p *relationalTablesProjection) reduceOrganizationAdminRemoved(event eventstore.Event) (*handler.Statement, error) {
|
||||
switch e := event.(type) {
|
||||
case *org.MemberRemovedEvent:
|
||||
return p.removeAdministratorStatement(e,
|
||||
repository.AdministratorRepository().OrganizationAdministratorCondition(e.Aggregate().InstanceID, e.Aggregate().ID, e.UserID),
|
||||
)
|
||||
case *org.MemberCascadeRemovedEvent:
|
||||
return p.removeAdministratorStatement(e,
|
||||
repository.AdministratorRepository().OrganizationAdministratorCondition(e.Aggregate().InstanceID, e.Aggregate().ID, e.UserID),
|
||||
)
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-v3ADM06", "reduce.wrong.event.type %v", []eventstore.EventType{org.MemberRemovedEventType, org.MemberCascadeRemovedEventType})
|
||||
}
|
||||
}
|
||||
|
||||
func (p *relationalTablesProjection) reduceProjectAdminAdded(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*project.MemberAddedEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.createAdministratorStatement(e, &domain.Administrator{
|
||||
InstanceID: e.Aggregate().InstanceID,
|
||||
UserID: e.UserID,
|
||||
Scope: domain.AdministratorScopeProject,
|
||||
ProjectID: stringPtr(e.Aggregate().ID),
|
||||
Roles: e.Roles,
|
||||
CreatedAt: e.CreationDate(),
|
||||
UpdatedAt: e.CreationDate(),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *relationalTablesProjection) reduceProjectAdminChanged(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*project.MemberChangedEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.updateAdministratorStatement(e,
|
||||
repository.AdministratorRepository().ProjectAdministratorCondition(e.Aggregate().InstanceID, e.Aggregate().ID, e.UserID),
|
||||
repository.AdministratorRepository().SetUpdatedAt(e.CreationDate()),
|
||||
repository.AdministratorRepository().SetRoles(e.Roles),
|
||||
)
|
||||
}
|
||||
|
||||
func (p *relationalTablesProjection) reduceProjectAdminRemoved(event eventstore.Event) (*handler.Statement, error) {
|
||||
switch e := event.(type) {
|
||||
case *project.MemberRemovedEvent:
|
||||
return p.removeAdministratorStatement(e,
|
||||
repository.AdministratorRepository().ProjectAdministratorCondition(e.Aggregate().InstanceID, e.Aggregate().ID, e.UserID),
|
||||
)
|
||||
case *project.MemberCascadeRemovedEvent:
|
||||
return p.removeAdministratorStatement(e,
|
||||
repository.AdministratorRepository().ProjectAdministratorCondition(e.Aggregate().InstanceID, e.Aggregate().ID, e.UserID),
|
||||
)
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-v3ADM09", "reduce.wrong.event.type %v", []eventstore.EventType{project.MemberRemovedEventType, project.MemberCascadeRemovedEventType})
|
||||
}
|
||||
}
|
||||
|
||||
func (p *relationalTablesProjection) reduceProjectGrantAdminAdded(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*project.GrantMemberAddedEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.createAdministratorStatement(e, &domain.Administrator{
|
||||
InstanceID: e.Aggregate().InstanceID,
|
||||
UserID: e.UserID,
|
||||
Scope: domain.AdministratorScopeProjectGrant,
|
||||
ProjectGrantID: stringPtr(e.GrantID),
|
||||
Roles: e.Roles,
|
||||
CreatedAt: e.CreationDate(),
|
||||
UpdatedAt: e.CreationDate(),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *relationalTablesProjection) reduceProjectGrantAdminChanged(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*project.GrantMemberChangedEvent](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.updateAdministratorStatement(e,
|
||||
repository.AdministratorRepository().ProjectGrantAdministratorCondition(e.Aggregate().InstanceID, e.GrantID, e.UserID),
|
||||
repository.AdministratorRepository().SetUpdatedAt(e.CreationDate()),
|
||||
repository.AdministratorRepository().SetRoles(e.Roles),
|
||||
)
|
||||
}
|
||||
|
||||
func (p *relationalTablesProjection) reduceProjectGrantAdminRemoved(event eventstore.Event) (*handler.Statement, error) {
|
||||
switch e := event.(type) {
|
||||
case *project.GrantMemberRemovedEvent:
|
||||
return p.removeAdministratorStatement(e,
|
||||
repository.AdministratorRepository().ProjectGrantAdministratorCondition(e.Aggregate().InstanceID, e.GrantID, e.UserID),
|
||||
)
|
||||
case *project.GrantMemberCascadeRemovedEvent:
|
||||
return p.removeAdministratorStatement(e,
|
||||
repository.AdministratorRepository().ProjectGrantAdministratorCondition(e.Aggregate().InstanceID, e.GrantID, e.UserID),
|
||||
)
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-v3ADM12", "reduce.wrong.event.type %v", []eventstore.EventType{project.GrantMemberRemovedType, project.GrantMemberCascadeRemovedType})
|
||||
}
|
||||
}
|
||||
|
||||
func (p *relationalTablesProjection) createAdministratorStatement(event eventstore.Event, administrator *domain.Administrator) (*handler.Statement, error) {
|
||||
return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, _ string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-v3ADM13", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
return repository.AdministratorRepository().Create(ctx, v3_sql.SQLTx(tx), administrator)
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *relationalTablesProjection) updateAdministratorStatement(event eventstore.Event, condition database.Condition, changes ...database.Change) (*handler.Statement, error) {
|
||||
return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, _ string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-v3ADM14", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
repo := repository.AdministratorRepository()
|
||||
_, err := repo.Update(ctx, v3_sql.SQLTx(tx), condition, changes...)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *relationalTablesProjection) removeAdministratorStatement(event eventstore.Event, condition database.Condition) (*handler.Statement, error) {
|
||||
return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, _ string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-v3ADM15", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
repo := repository.AdministratorRepository()
|
||||
_, err := repo.Delete(ctx, v3_sql.SQLTx(tx), condition)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func stringPtr(value string) *string {
|
||||
return &value
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
package projection
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
repoDomain "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"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
"github.com/zitadel/zitadel/internal/repository/project"
|
||||
)
|
||||
|
||||
func TestAdministratorRelationalReducers(t *testing.T) {
|
||||
handler := new(relationalTablesProjection)
|
||||
rawTx, tx := getTransactions(t)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, rawTx.Rollback())
|
||||
})
|
||||
|
||||
instanceID, _, orgID, projectID, grantID := seedAdministratorRelationalState(t, tx)
|
||||
adminRepo := repository.AdministratorRepository()
|
||||
userRepo := repository.UserRepository()
|
||||
|
||||
var userSeq int
|
||||
createUser := func(t *testing.T) string {
|
||||
t.Helper()
|
||||
userSeq++
|
||||
userID := fmt.Sprintf("admin-test-user-%d", userSeq)
|
||||
err := userRepo.Create(t.Context(), tx, &repoDomain.User{
|
||||
InstanceID: instanceID,
|
||||
OrganizationID: orgID,
|
||||
ID: userID,
|
||||
Username: userID,
|
||||
State: repoDomain.UserStateActive,
|
||||
Machine: &repoDomain.MachineUser{
|
||||
Name: "machine",
|
||||
},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return userID
|
||||
}
|
||||
|
||||
t.Run("'instance member added' reducer should create instance administrator", func(t *testing.T) {
|
||||
userID := createUser(t)
|
||||
|
||||
added := instance.NewMemberAddedEvent(t.Context(), &instance.NewAggregate(instanceID).Aggregate, userID, "IAM_OWNER")
|
||||
require.True(t, callReduce(t, rawTx, handler, added))
|
||||
|
||||
admin, err := adminRepo.Get(t.Context(), tx, database.WithCondition(
|
||||
adminRepo.InstanceAdministratorCondition(instanceID, userID),
|
||||
))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"IAM_OWNER"}, admin.Roles)
|
||||
})
|
||||
|
||||
t.Run("'instance member changed' reducer should update instance administrator roles", func(t *testing.T) {
|
||||
userID := createUser(t)
|
||||
err := adminRepo.Create(t.Context(), tx, &repoDomain.Administrator{
|
||||
InstanceID: instanceID,
|
||||
UserID: userID,
|
||||
Scope: repoDomain.AdministratorScopeInstance,
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
changed := instance.NewMemberChangedEvent(t.Context(), &instance.NewAggregate(instanceID).Aggregate, userID, "IAM_OWNER_VIEWER")
|
||||
require.True(t, callReduce(t, rawTx, handler, changed))
|
||||
|
||||
admin, err := adminRepo.Get(t.Context(), tx, database.WithCondition(
|
||||
adminRepo.InstanceAdministratorCondition(instanceID, userID),
|
||||
))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"IAM_OWNER_VIEWER"}, admin.Roles)
|
||||
})
|
||||
|
||||
t.Run("'instance member removed' reducer should remove instance administrator", func(t *testing.T) {
|
||||
userID := createUser(t)
|
||||
err := adminRepo.Create(t.Context(), tx, &repoDomain.Administrator{
|
||||
InstanceID: instanceID,
|
||||
UserID: userID,
|
||||
Scope: repoDomain.AdministratorScopeInstance,
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
removed := instance.NewMemberRemovedEvent(t.Context(), &instance.NewAggregate(instanceID).Aggregate, userID)
|
||||
require.True(t, callReduce(t, rawTx, handler, removed))
|
||||
|
||||
_, err = adminRepo.Get(t.Context(), tx, database.WithCondition(
|
||||
adminRepo.InstanceAdministratorCondition(instanceID, userID),
|
||||
))
|
||||
require.ErrorIs(t, err, database.NewNoRowFoundError(nil))
|
||||
})
|
||||
|
||||
t.Run("'org member added' reducer should create organization administrator", func(t *testing.T) {
|
||||
userID := createUser(t)
|
||||
|
||||
orgAggregate := org.NewAggregate(orgID)
|
||||
orgAggregate.InstanceID = instanceID
|
||||
|
||||
added := org.NewMemberAddedEvent(t.Context(), &orgAggregate.Aggregate, userID, "ORG_OWNER")
|
||||
require.True(t, callReduce(t, rawTx, handler, added))
|
||||
|
||||
admin, err := adminRepo.Get(t.Context(), tx, database.WithCondition(
|
||||
adminRepo.OrganizationAdministratorCondition(instanceID, orgID, userID),
|
||||
))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"ORG_OWNER"}, admin.Roles)
|
||||
})
|
||||
|
||||
t.Run("'org member changed' reducer should update organization administrator roles", func(t *testing.T) {
|
||||
userID := createUser(t)
|
||||
err := adminRepo.Create(t.Context(), tx, &repoDomain.Administrator{
|
||||
InstanceID: instanceID,
|
||||
UserID: userID,
|
||||
Scope: repoDomain.AdministratorScopeOrganization,
|
||||
OrganizationID: &orgID,
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
orgAggregate := org.NewAggregate(orgID)
|
||||
orgAggregate.InstanceID = instanceID
|
||||
|
||||
changed := org.NewMemberChangedEvent(t.Context(), &orgAggregate.Aggregate, userID, "ORG_OWNER_VIEWER")
|
||||
require.True(t, callReduce(t, rawTx, handler, changed))
|
||||
|
||||
admin, err := adminRepo.Get(t.Context(), tx, database.WithCondition(
|
||||
adminRepo.OrganizationAdministratorCondition(instanceID, orgID, userID),
|
||||
))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"ORG_OWNER_VIEWER"}, admin.Roles)
|
||||
})
|
||||
|
||||
t.Run("'org member removed' reducer should remove organization administrator", func(t *testing.T) {
|
||||
userID := createUser(t)
|
||||
err := adminRepo.Create(t.Context(), tx, &repoDomain.Administrator{
|
||||
InstanceID: instanceID,
|
||||
UserID: userID,
|
||||
Scope: repoDomain.AdministratorScopeOrganization,
|
||||
OrganizationID: &orgID,
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
orgAggregate := org.NewAggregate(orgID)
|
||||
orgAggregate.InstanceID = instanceID
|
||||
|
||||
removed := org.NewMemberRemovedEvent(t.Context(), &orgAggregate.Aggregate, userID)
|
||||
require.True(t, callReduce(t, rawTx, handler, removed))
|
||||
|
||||
_, err = adminRepo.Get(t.Context(), tx, database.WithCondition(
|
||||
adminRepo.OrganizationAdministratorCondition(instanceID, orgID, userID),
|
||||
))
|
||||
require.ErrorIs(t, err, database.NewNoRowFoundError(nil))
|
||||
})
|
||||
|
||||
t.Run("'project member added' reducer should create project administrator", func(t *testing.T) {
|
||||
userID := createUser(t)
|
||||
|
||||
projectAggregate := project.NewAggregate(projectID, orgID)
|
||||
projectAggregate.InstanceID = instanceID
|
||||
|
||||
added := project.NewProjectMemberAddedEvent(t.Context(), &projectAggregate.Aggregate, userID, "PROJECT_OWNER")
|
||||
require.True(t, callReduce(t, rawTx, handler, added))
|
||||
|
||||
admin, err := adminRepo.Get(t.Context(), tx, database.WithCondition(
|
||||
adminRepo.ProjectAdministratorCondition(instanceID, projectID, userID),
|
||||
))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"PROJECT_OWNER"}, admin.Roles)
|
||||
})
|
||||
|
||||
t.Run("'project member changed' reducer should update project administrator roles", func(t *testing.T) {
|
||||
userID := createUser(t)
|
||||
err := adminRepo.Create(t.Context(), tx, &repoDomain.Administrator{
|
||||
InstanceID: instanceID,
|
||||
UserID: userID,
|
||||
Scope: repoDomain.AdministratorScopeProject,
|
||||
ProjectID: &projectID,
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
projectAggregate := project.NewAggregate(projectID, orgID)
|
||||
projectAggregate.InstanceID = instanceID
|
||||
|
||||
changed := project.NewProjectMemberChangedEvent(t.Context(), &projectAggregate.Aggregate, userID, "PROJECT_OWNER_VIEWER")
|
||||
require.True(t, callReduce(t, rawTx, handler, changed))
|
||||
|
||||
admin, err := adminRepo.Get(t.Context(), tx, database.WithCondition(
|
||||
adminRepo.ProjectAdministratorCondition(instanceID, projectID, userID),
|
||||
))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"PROJECT_OWNER_VIEWER"}, admin.Roles)
|
||||
})
|
||||
|
||||
t.Run("'project member removed' reducer should remove project administrator", func(t *testing.T) {
|
||||
userID := createUser(t)
|
||||
err := adminRepo.Create(t.Context(), tx, &repoDomain.Administrator{
|
||||
InstanceID: instanceID,
|
||||
UserID: userID,
|
||||
Scope: repoDomain.AdministratorScopeProject,
|
||||
ProjectID: &projectID,
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
projectAggregate := project.NewAggregate(projectID, orgID)
|
||||
projectAggregate.InstanceID = instanceID
|
||||
|
||||
removed := project.NewProjectMemberRemovedEvent(t.Context(), &projectAggregate.Aggregate, userID)
|
||||
require.True(t, callReduce(t, rawTx, handler, removed))
|
||||
|
||||
_, err = adminRepo.Get(t.Context(), tx, database.WithCondition(
|
||||
adminRepo.ProjectAdministratorCondition(instanceID, projectID, userID),
|
||||
))
|
||||
require.ErrorIs(t, err, database.NewNoRowFoundError(nil))
|
||||
})
|
||||
|
||||
t.Run("'project grant member added' reducer should create project grant administrator", func(t *testing.T) {
|
||||
userID := createUser(t)
|
||||
|
||||
projectAggregate := project.NewAggregate(projectID, orgID)
|
||||
projectAggregate.InstanceID = instanceID
|
||||
|
||||
added := project.NewProjectGrantMemberAddedEvent(t.Context(), &projectAggregate.Aggregate, userID, grantID, "PROJECT_OWNER")
|
||||
require.True(t, callReduce(t, rawTx, handler, added))
|
||||
|
||||
admin, err := adminRepo.Get(t.Context(), tx, database.WithCondition(
|
||||
adminRepo.ProjectGrantAdministratorCondition(instanceID, grantID, userID),
|
||||
))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"PROJECT_OWNER"}, admin.Roles)
|
||||
})
|
||||
|
||||
t.Run("'project grant member changed' reducer should update project grant administrator roles", func(t *testing.T) {
|
||||
userID := createUser(t)
|
||||
err := adminRepo.Create(t.Context(), tx, &repoDomain.Administrator{
|
||||
InstanceID: instanceID,
|
||||
UserID: userID,
|
||||
Scope: repoDomain.AdministratorScopeProjectGrant,
|
||||
ProjectGrantID: &grantID,
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
projectAggregate := project.NewAggregate(projectID, orgID)
|
||||
projectAggregate.InstanceID = instanceID
|
||||
|
||||
changed := project.NewProjectGrantMemberChangedEvent(t.Context(), &projectAggregate.Aggregate, userID, grantID, "PROJECT_OWNER_VIEWER")
|
||||
require.True(t, callReduce(t, rawTx, handler, changed))
|
||||
|
||||
admin, err := adminRepo.Get(t.Context(), tx, database.WithCondition(
|
||||
adminRepo.ProjectGrantAdministratorCondition(instanceID, grantID, userID),
|
||||
))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"PROJECT_OWNER_VIEWER"}, admin.Roles)
|
||||
})
|
||||
|
||||
t.Run("'project grant member cascade removed' reducer should remove project grant administrator", func(t *testing.T) {
|
||||
userID := createUser(t)
|
||||
err := adminRepo.Create(t.Context(), tx, &repoDomain.Administrator{
|
||||
InstanceID: instanceID,
|
||||
UserID: userID,
|
||||
Scope: repoDomain.AdministratorScopeProjectGrant,
|
||||
ProjectGrantID: &grantID,
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
projectAggregate := project.NewAggregate(projectID, orgID)
|
||||
projectAggregate.InstanceID = instanceID
|
||||
|
||||
removed := project.NewProjectGrantMemberCascadeRemovedEvent(t.Context(), &projectAggregate.Aggregate, userID, grantID)
|
||||
require.True(t, callReduce(t, rawTx, handler, removed))
|
||||
|
||||
_, err = adminRepo.Get(t.Context(), tx, database.WithCondition(
|
||||
adminRepo.ProjectGrantAdministratorCondition(instanceID, grantID, userID),
|
||||
))
|
||||
require.ErrorIs(t, err, database.NewNoRowFoundError(nil))
|
||||
})
|
||||
}
|
||||
|
||||
func seedAdministratorRelationalState(t *testing.T, tx database.QueryExecutor) (instanceID, userID, orgID, projectID, projectGrantID string) {
|
||||
t.Helper()
|
||||
|
||||
instanceRepo := repository.InstanceRepository()
|
||||
orgRepo := repository.OrganizationRepository()
|
||||
projectRepo := repository.ProjectRepository()
|
||||
projectGrantRepo := repository.ProjectGrantRepository()
|
||||
userRepo := repository.UserRepository()
|
||||
|
||||
now := time.Now().UnixNano()
|
||||
instanceID = fmt.Sprintf("instance-%d", now)
|
||||
err := instanceRepo.Create(t.Context(), tx, &repoDomain.Instance{
|
||||
ID: instanceID,
|
||||
Name: "instance",
|
||||
DefaultOrgID: "default-org",
|
||||
IAMProjectID: "iam-project",
|
||||
ConsoleClientID: "console-client",
|
||||
ConsoleAppID: "console-app",
|
||||
DefaultLanguage: "en",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
orgID = fmt.Sprintf("org-%d", now+1)
|
||||
err = orgRepo.Create(t.Context(), tx, &repoDomain.Organization{
|
||||
InstanceID: instanceID,
|
||||
ID: orgID,
|
||||
Name: "org",
|
||||
State: repoDomain.OrgStateActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
projectID = fmt.Sprintf("project-%d", now+2)
|
||||
err = projectRepo.Create(t.Context(), tx, &repoDomain.Project{
|
||||
InstanceID: instanceID,
|
||||
OrganizationID: orgID,
|
||||
ID: projectID,
|
||||
Name: "project",
|
||||
State: repoDomain.ProjectStateActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
projectGrantID = fmt.Sprintf("grant-%d", now+3)
|
||||
err = projectGrantRepo.Create(t.Context(), tx, &repoDomain.ProjectGrant{
|
||||
InstanceID: instanceID,
|
||||
ID: projectGrantID,
|
||||
ProjectID: projectID,
|
||||
GrantingOrganizationID: orgID,
|
||||
GrantedOrganizationID: orgID,
|
||||
State: repoDomain.ProjectGrantStateActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
userID = fmt.Sprintf("user-%d", now+4)
|
||||
err = userRepo.Create(t.Context(), tx, &repoDomain.User{
|
||||
InstanceID: instanceID,
|
||||
OrganizationID: orgID,
|
||||
ID: userID,
|
||||
Username: userID,
|
||||
State: repoDomain.UserStateActive,
|
||||
Machine: &repoDomain.MachineUser{
|
||||
Name: "machine",
|
||||
},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return instanceID, userID, orgID, projectID, projectGrantID
|
||||
}
|
||||
@@ -85,7 +85,7 @@ func getTransactions(t *testing.T) (rawTx *sql.Tx, v3SQLTx *v3_sql.Transaction)
|
||||
// 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, ctx context.Context, tx *sql.Tx, projection handler.Projection, event eventstore.Event) bool {
|
||||
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
|
||||
@@ -111,6 +111,6 @@ func callReduce(t *testing.T, ctx context.Context, tx *sql.Tx, projection handle
|
||||
return false
|
||||
}
|
||||
|
||||
err = stmt.Execute(ctx, tx, "")
|
||||
err = stmt.Execute(t.Context(), tx, "")
|
||||
return assert.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func TestInstanceDomainReducers(t *testing.T) {
|
||||
domainAddedEvt := instance.NewDomainAddedEvent(ctx, &instance.NewAggregate("123").Aggregate, domainName, false)
|
||||
|
||||
// Test
|
||||
res := callReduce(t, ctx, rawTx, handler, domainAddedEvt)
|
||||
res := callReduce(t, rawTx, handler, domainAddedEvt)
|
||||
require.True(t, res)
|
||||
|
||||
// Verify
|
||||
@@ -85,7 +85,7 @@ func TestInstanceDomainReducers(t *testing.T) {
|
||||
domainPrimarySetEvt := instance.NewDomainPrimarySetEvent(ctx, &instance.NewAggregate("123").Aggregate, domainName)
|
||||
|
||||
// Test
|
||||
res := callReduce(t, ctx, rawTx, handler, domainPrimarySetEvt)
|
||||
res := callReduce(t, rawTx, handler, domainPrimarySetEvt)
|
||||
require.True(t, res)
|
||||
|
||||
// Verify
|
||||
@@ -127,7 +127,7 @@ func TestInstanceDomainReducers(t *testing.T) {
|
||||
domainRemovedEvt := instance.NewDomainRemovedEvent(ctx, &instance.NewAggregate("123").Aggregate, domainName)
|
||||
|
||||
// Test
|
||||
res := callReduce(t, ctx, rawTx, handler, domainRemovedEvt)
|
||||
res := callReduce(t, rawTx, handler, domainRemovedEvt)
|
||||
require.True(t, res)
|
||||
|
||||
// Verify
|
||||
@@ -150,7 +150,7 @@ func TestInstanceDomainReducers(t *testing.T) {
|
||||
trustedDomainAddedEvt := instance.NewTrustedDomainAddedEvent(ctx, &instance.NewAggregate("123").Aggregate, domainName)
|
||||
|
||||
// Test
|
||||
res := callReduce(t, ctx, rawTx, handler, trustedDomainAddedEvt)
|
||||
res := callReduce(t, rawTx, handler, trustedDomainAddedEvt)
|
||||
require.True(t, res)
|
||||
|
||||
// Verify
|
||||
@@ -185,7 +185,7 @@ func TestInstanceDomainReducers(t *testing.T) {
|
||||
trustedDomainRemovedEvt := instance.NewTrustedDomainRemovedEvent(ctx, &instance.NewAggregate("123").Aggregate, domainName)
|
||||
|
||||
// Test
|
||||
res := callReduce(t, ctx, rawTx, handler, trustedDomainRemovedEvt)
|
||||
res := callReduce(t, rawTx, handler, trustedDomainRemovedEvt)
|
||||
require.True(t, res)
|
||||
|
||||
// Verify
|
||||
|
||||
@@ -377,6 +377,24 @@ func (p *relationalTablesProjection) Reducers() []handler.AggregateReducer {
|
||||
Event: instance.IDPRemovedEventType,
|
||||
Reduce: p.reduceIDPRemoved,
|
||||
},
|
||||
|
||||
// Administrators
|
||||
{
|
||||
Event: instance.MemberAddedEventType,
|
||||
Reduce: p.reduceInstanceAdminAdded,
|
||||
},
|
||||
{
|
||||
Event: instance.MemberChangedEventType,
|
||||
Reduce: p.reduceInstanceAdminChanged,
|
||||
},
|
||||
{
|
||||
Event: instance.MemberRemovedEventType,
|
||||
Reduce: p.reduceInstanceAdminRemoved,
|
||||
},
|
||||
{
|
||||
Event: instance.MemberCascadeRemovedEventType,
|
||||
Reduce: p.reduceInstanceAdminRemoved,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -750,6 +768,24 @@ func (p *relationalTablesProjection) Reducers() []handler.AggregateReducer {
|
||||
Event: org.IDPRemovedEventType,
|
||||
Reduce: p.reduceIDPRemoved,
|
||||
},
|
||||
|
||||
// Administrators
|
||||
{
|
||||
Event: org.MemberAddedEventType,
|
||||
Reduce: p.reduceOrganizationAdminAdded,
|
||||
},
|
||||
{
|
||||
Event: org.MemberChangedEventType,
|
||||
Reduce: p.reduceOrganizationAdminChanged,
|
||||
},
|
||||
{
|
||||
Event: org.MemberRemovedEventType,
|
||||
Reduce: p.reduceOrganizationAdminRemoved,
|
||||
},
|
||||
{
|
||||
Event: org.MemberCascadeRemovedEventType,
|
||||
Reduce: p.reduceOrganizationAdminRemoved,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -828,6 +864,40 @@ func (p *relationalTablesProjection) Reducers() []handler.AggregateReducer {
|
||||
Event: project.RoleRemovedType,
|
||||
Reduce: p.reduceProjectRoleRemoved,
|
||||
},
|
||||
|
||||
// Administrators
|
||||
{
|
||||
Event: project.MemberAddedEventType,
|
||||
Reduce: p.reduceProjectAdminAdded,
|
||||
},
|
||||
{
|
||||
Event: project.MemberChangedEventType,
|
||||
Reduce: p.reduceProjectAdminChanged,
|
||||
},
|
||||
{
|
||||
Event: project.MemberRemovedEventType,
|
||||
Reduce: p.reduceProjectAdminRemoved,
|
||||
},
|
||||
{
|
||||
Event: project.MemberCascadeRemovedEventType,
|
||||
Reduce: p.reduceProjectAdminRemoved,
|
||||
},
|
||||
{
|
||||
Event: project.GrantMemberAddedType,
|
||||
Reduce: p.reduceProjectGrantAdminAdded,
|
||||
},
|
||||
{
|
||||
Event: project.GrantMemberChangedType,
|
||||
Reduce: p.reduceProjectGrantAdminChanged,
|
||||
},
|
||||
{
|
||||
Event: project.GrantMemberRemovedType,
|
||||
Reduce: p.reduceProjectGrantAdminRemoved,
|
||||
},
|
||||
{
|
||||
Event: project.GrantMemberCascadeRemovedType,
|
||||
Reduce: p.reduceProjectGrantAdminRemoved,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package projection
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -23,13 +22,11 @@ func TestUserRelationalProjection_Reducers(t *testing.T) {
|
||||
require.NoError(t, rawTx.Rollback())
|
||||
})
|
||||
|
||||
ctx := t.Context()
|
||||
|
||||
// create instance
|
||||
instanceRepo := repository.InstanceRepository()
|
||||
instanceID := gofakeit.UUID()
|
||||
orgID := gofakeit.UUID()
|
||||
err := instanceRepo.Create(ctx, tx, &domain.Instance{
|
||||
err := instanceRepo.Create(t.Context(), tx, &domain.Instance{
|
||||
ID: instanceID,
|
||||
Name: "test-instance",
|
||||
DefaultOrgID: orgID,
|
||||
@@ -38,7 +35,7 @@ func TestUserRelationalProjection_Reducers(t *testing.T) {
|
||||
|
||||
// create org
|
||||
orgRepo := repository.OrganizationRepository()
|
||||
err = orgRepo.Create(ctx, tx, &domain.Organization{
|
||||
err = orgRepo.Create(t.Context(), tx, &domain.Organization{
|
||||
InstanceID: instanceID,
|
||||
ID: orgID,
|
||||
Name: "test-org",
|
||||
@@ -54,22 +51,22 @@ func TestUserRelationalProjection_Reducers(t *testing.T) {
|
||||
t.Run("reduce user.human.mfa.recoverycode.added event", func(t *testing.T) {
|
||||
// create user
|
||||
existingUserID := gofakeit.UUID()
|
||||
existingUserAgg := createUser(t, ctx, tx, userRepo, instanceID, orgID, existingUserID)
|
||||
existingUserAgg := createUser(t, tx, userRepo, instanceID, orgID, existingUserID)
|
||||
|
||||
// set `user.human.mfa.recoverycode.added` event
|
||||
recoveryCodes := []string{"code1", "code2", "code3"}
|
||||
recoveryCodesAddedEvent := user.NewHumanRecoveryCodesAddedEvent(
|
||||
ctx,
|
||||
t.Context(),
|
||||
&existingUserAgg.Aggregate,
|
||||
recoveryCodes,
|
||||
nil)
|
||||
|
||||
// reduce the event
|
||||
eventReduced := callReduce(t, ctx, rawTx, handler, recoveryCodesAddedEvent)
|
||||
eventReduced := callReduce(t, rawTx, handler, recoveryCodesAddedEvent)
|
||||
require.True(t, eventReduced)
|
||||
|
||||
// assert that the recovery codes are stored
|
||||
gotUser, err := userRepo.Get(ctx, tx, database.WithCondition(
|
||||
gotUser, err := userRepo.Get(t.Context(), tx, database.WithCondition(
|
||||
database.And(
|
||||
userRepo.IDCondition(existingUserID),
|
||||
userRepo.InstanceIDCondition(instanceID),
|
||||
@@ -84,26 +81,26 @@ func TestUserRelationalProjection_Reducers(t *testing.T) {
|
||||
t.Run("reduce user.human.mfa.recoverycode.removed event", func(t *testing.T) {
|
||||
// create user
|
||||
existingUserID := gofakeit.UUID()
|
||||
existingUserAgg := createUser(t, ctx, tx, userRepo, instanceID, orgID, existingUserID)
|
||||
existingUserAgg := createUser(t, tx, userRepo, instanceID, orgID, existingUserID)
|
||||
|
||||
// add recovery codes to the user
|
||||
_, err = userRepo.Update(ctx, tx,
|
||||
_, err = userRepo.Update(t.Context(), tx,
|
||||
userRepo.PrimaryKeyCondition(instanceID, existingUserID),
|
||||
userRepo.Human().AddRecoveryCodes([]string{"code1", "code2", "code3"}))
|
||||
require.NoError(t, err)
|
||||
|
||||
// set `user.human.mfa.recoverycode.removed` event
|
||||
recoveryCodesRemovedEvent := user.NewHumanRecoveryCodeRemovedEvent(
|
||||
ctx,
|
||||
t.Context(),
|
||||
&existingUserAgg.Aggregate,
|
||||
nil)
|
||||
|
||||
// reduce the event
|
||||
eventReduced := callReduce(t, ctx, rawTx, handler, recoveryCodesRemovedEvent)
|
||||
eventReduced := callReduce(t, rawTx, handler, recoveryCodesRemovedEvent)
|
||||
require.True(t, eventReduced)
|
||||
|
||||
// assert that the recovery codes are removed
|
||||
gotUser, err := userRepo.Get(ctx, tx, database.WithCondition(
|
||||
gotUser, err := userRepo.Get(t.Context(), tx, database.WithCondition(
|
||||
database.And(
|
||||
userRepo.IDCondition(existingUserID),
|
||||
userRepo.InstanceIDCondition(instanceID),
|
||||
@@ -118,27 +115,27 @@ func TestUserRelationalProjection_Reducers(t *testing.T) {
|
||||
t.Run("reduce user.human.mfa.recoverycode.check.succeeded event", func(t *testing.T) {
|
||||
// create user
|
||||
existingUserID := gofakeit.UUID()
|
||||
existingUserAgg := createUser(t, ctx, tx, userRepo, instanceID, orgID, existingUserID)
|
||||
existingUserAgg := createUser(t, tx, userRepo, instanceID, orgID, existingUserID)
|
||||
|
||||
// add recovery codes to the user
|
||||
_, err = userRepo.Update(ctx, tx,
|
||||
_, err = userRepo.Update(t.Context(), tx,
|
||||
userRepo.PrimaryKeyCondition(instanceID, existingUserID),
|
||||
userRepo.Human().AddRecoveryCodes([]string{"code1", "code2", "code3"}))
|
||||
require.NoError(t, err)
|
||||
|
||||
// set `user.human.mfa.recoverycode.check.succeeded` event
|
||||
recoveryCodeCheckSucceededEvent := user.NewHumanRecoveryCodeCheckSucceededEvent(
|
||||
ctx,
|
||||
t.Context(),
|
||||
&existingUserAgg.Aggregate,
|
||||
"code1",
|
||||
nil)
|
||||
|
||||
// reduce the event
|
||||
eventReduced := callReduce(t, ctx, rawTx, handler, recoveryCodeCheckSucceededEvent)
|
||||
eventReduced := callReduce(t, rawTx, handler, recoveryCodeCheckSucceededEvent)
|
||||
require.True(t, eventReduced)
|
||||
|
||||
// assert that the recovery code is removed and the last_successfully_checked_at timestamp is set
|
||||
gotUser, err := userRepo.Get(ctx, tx, database.WithCondition(
|
||||
gotUser, err := userRepo.Get(t.Context(), tx, database.WithCondition(
|
||||
database.And(
|
||||
userRepo.IDCondition(existingUserID),
|
||||
userRepo.InstanceIDCondition(instanceID),
|
||||
@@ -153,27 +150,27 @@ func TestUserRelationalProjection_Reducers(t *testing.T) {
|
||||
t.Run("reduce user.human.mfa.recoverycode.check.failed event", func(t *testing.T) {
|
||||
// create user
|
||||
existingUserID := gofakeit.UUID()
|
||||
existingUserAgg := createUser(t, ctx, tx, userRepo, instanceID, orgID, existingUserID)
|
||||
existingUserAgg := createUser(t, tx, userRepo, instanceID, orgID, existingUserID)
|
||||
|
||||
// add recovery codes to the user
|
||||
_, err = userRepo.Update(ctx, tx,
|
||||
_, err = userRepo.Update(t.Context(), tx,
|
||||
userRepo.PrimaryKeyCondition(instanceID, existingUserID),
|
||||
userRepo.Human().AddRecoveryCodes([]string{"code1", "code2", "code3"}))
|
||||
require.NoError(t, err)
|
||||
|
||||
// set `user.human.mfa.recoverycode.check.failed` event
|
||||
recoveryCodeCheckFailedEvent := user.NewHumanRecoveryCodeCheckFailedEvent(
|
||||
ctx,
|
||||
t.Context(),
|
||||
&existingUserAgg.Aggregate,
|
||||
nil,
|
||||
)
|
||||
|
||||
// reduce the event
|
||||
eventReduced := callReduce(t, ctx, rawTx, handler, recoveryCodeCheckFailedEvent)
|
||||
eventReduced := callReduce(t, rawTx, handler, recoveryCodeCheckFailedEvent)
|
||||
require.True(t, eventReduced)
|
||||
|
||||
// assert that the recovery code failed_attempts is incremented
|
||||
gotUser, err := userRepo.Get(ctx, tx, database.WithCondition(
|
||||
gotUser, err := userRepo.Get(t.Context(), tx, database.WithCondition(
|
||||
database.And(
|
||||
userRepo.IDCondition(existingUserID),
|
||||
userRepo.InstanceIDCondition(instanceID),
|
||||
@@ -187,7 +184,6 @@ func TestUserRelationalProjection_Reducers(t *testing.T) {
|
||||
}
|
||||
|
||||
func createUser(t *testing.T,
|
||||
ctx context.Context,
|
||||
tx *sql.Transaction,
|
||||
userRepo domain.UserRepository,
|
||||
instanceID,
|
||||
@@ -197,7 +193,7 @@ func createUser(t *testing.T,
|
||||
userAgg := user.NewAggregate(userID, orgID)
|
||||
userAgg.InstanceID = instanceID
|
||||
|
||||
err := userRepo.Create(ctx, tx, &domain.User{
|
||||
err := userRepo.Create(t.Context(), tx, &domain.User{
|
||||
InstanceID: instanceID,
|
||||
OrganizationID: orgID,
|
||||
ID: userID,
|
||||
|
||||
Reference in New Issue
Block a user