mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 04:07:31 +00:00
feat: actions (#2377)
* feat(actions): begin api * feat(actions): begin api * api and projections * fix: handle multiple statements for a single event in projections * export func type * fix test * update to new reduce interface * flows in login * feat: jwt idp * feat: command side * feat: add tests * actions and flows * fill idp views with jwt idps and return apis * add jwtEndpoint to jwt idp * begin jwt request handling * add feature * merge * merge * handle jwt idp * cleanup * bug fixes * autoregister * get token from specific header name * fix: proto * fixes * i18n * begin tests * fix and log http proxy * remove docker cache * fixes * usergrants in actions api * tests adn cleanup * cleanup * fix add user grant * set login context * i18n Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/caos/zitadel/internal/config/types"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/action"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/http"
|
||||
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
@@ -78,6 +79,7 @@ func StartCommands(eventstore *eventstore.Eventstore, defaults sd.SystemDefaults
|
||||
usr_grant_repo.RegisterEventMappers(repo.eventstore)
|
||||
proj_repo.RegisterEventMappers(repo.eventstore)
|
||||
keypair.RegisterEventMappers(repo.eventstore)
|
||||
action.RegisterEventMappers(repo.eventstore)
|
||||
|
||||
repo.idpConfigSecretCrypto, err = crypto.NewAESCrypto(defaults.IDPConfigVerificationKey)
|
||||
if err != nil {
|
||||
|
@@ -31,6 +31,7 @@ type FeaturesWriteModel struct {
|
||||
CustomTextMessage bool
|
||||
CustomTextLogin bool
|
||||
LockoutPolicy bool
|
||||
Actions bool
|
||||
}
|
||||
|
||||
func (wm *FeaturesWriteModel) Reduce() error {
|
||||
@@ -98,6 +99,9 @@ func (wm *FeaturesWriteModel) Reduce() error {
|
||||
if e.LockoutPolicy != nil {
|
||||
wm.LockoutPolicy = *e.LockoutPolicy
|
||||
}
|
||||
if e.Actions != nil {
|
||||
wm.Actions = *e.Actions
|
||||
}
|
||||
case *features.FeaturesRemovedEvent:
|
||||
wm.State = domain.FeaturesStateRemoved
|
||||
}
|
||||
|
52
internal/command/flow_model.go
Normal file
52
internal/command/flow_model.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/flow"
|
||||
)
|
||||
|
||||
type FlowWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
FlowType domain.FlowType
|
||||
State domain.FlowState
|
||||
Triggers map[domain.TriggerType][]string
|
||||
}
|
||||
|
||||
func NewFlowWriteModel(flowType domain.FlowType, resourceOwner string) *FlowWriteModel {
|
||||
return &FlowWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: resourceOwner,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
FlowType: flowType,
|
||||
Triggers: make(map[domain.TriggerType][]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *FlowWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *flow.TriggerActionsSetEvent:
|
||||
if wm.Triggers == nil {
|
||||
wm.Triggers = make(map[domain.TriggerType][]string)
|
||||
}
|
||||
wm.Triggers[e.TriggerType] = e.ActionIDs
|
||||
case *flow.TriggerActionsCascadeRemovedEvent:
|
||||
remove(wm.Triggers[e.TriggerType], e.ActionID)
|
||||
case *flow.FlowClearedEvent:
|
||||
wm.Triggers = nil
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func remove(ids []string, id string) {
|
||||
for i := 0; i < len(ids); i++ {
|
||||
if ids[i] == id {
|
||||
ids = append(ids[:i], ids[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
@@ -54,6 +54,7 @@ func (c *Commands) setDefaultFeatures(ctx context.Context, existingFeatures *IAM
|
||||
features.CustomTextMessage,
|
||||
features.CustomTextLogin,
|
||||
features.LockoutPolicy,
|
||||
features.Actions,
|
||||
)
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")
|
||||
|
@@ -71,7 +71,8 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent(
|
||||
metadataUser,
|
||||
customTextMessage,
|
||||
customTextLogin,
|
||||
lockoutPolicy bool,
|
||||
lockoutPolicy,
|
||||
actions bool,
|
||||
) (*iam.FeaturesSetEvent, bool) {
|
||||
|
||||
changes := make([]features.FeaturesChanges, 0)
|
||||
@@ -133,6 +134,9 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent(
|
||||
if wm.LockoutPolicy != lockoutPolicy {
|
||||
changes = append(changes, features.ChangeLockoutPolicy(lockoutPolicy))
|
||||
}
|
||||
if wm.Actions != actions {
|
||||
changes = append(changes, features.ChangeActions(actions))
|
||||
}
|
||||
if len(changes) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||
"github.com/caos/zitadel/internal/eventstore/repository/mock"
|
||||
action_repo "github.com/caos/zitadel/internal/repository/action"
|
||||
iam_repo "github.com/caos/zitadel/internal/repository/iam"
|
||||
key_repo "github.com/caos/zitadel/internal/repository/keypair"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
@@ -35,6 +36,7 @@ func eventstoreExpect(t *testing.T, expects ...expect) *eventstore.Eventstore {
|
||||
proj_repo.RegisterEventMappers(es)
|
||||
usergrant.RegisterEventMappers(es)
|
||||
key_repo.RegisterEventMappers(es)
|
||||
action_repo.RegisterEventMappers(es)
|
||||
return es
|
||||
}
|
||||
|
||||
|
200
internal/command/org_action.go
Normal file
200
internal/command/org_action.go
Normal file
@@ -0,0 +1,200 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/action"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
)
|
||||
|
||||
func (c *Commands) AddAction(ctx context.Context, addAction *domain.Action, resourceOwner string) (_ string, _ *domain.ObjectDetails, err error) {
|
||||
if !addAction.IsValid() {
|
||||
return "", nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-eg2gf", "Errors.Action.Invalid")
|
||||
}
|
||||
addAction.AggregateID, err = c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
actionModel := NewActionWriteModel(addAction.AggregateID, resourceOwner)
|
||||
actionAgg := ActionAggregateFromWriteModel(&actionModel.WriteModel)
|
||||
|
||||
pushedEvents, err := c.eventstore.PushEvents(ctx, action.NewAddedEvent(
|
||||
ctx,
|
||||
actionAgg,
|
||||
addAction.Name,
|
||||
addAction.Script,
|
||||
addAction.Timeout,
|
||||
addAction.AllowedToFail,
|
||||
))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
err = AppendAndReduce(actionModel, pushedEvents...)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return actionModel.AggregateID, writeModelToObjectDetails(&actionModel.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) ChangeAction(ctx context.Context, actionChange *domain.Action, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
if !actionChange.IsValid() || actionChange.AggregateID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Df2f3", "Errors.Action.Invalid")
|
||||
}
|
||||
|
||||
existingAction, err := c.getActionWriteModelByID(ctx, actionChange.AggregateID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !existingAction.State.Exists() {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-Sfg2t", "Errors.Action.NotFound")
|
||||
}
|
||||
|
||||
actionAgg := ActionAggregateFromWriteModel(&existingAction.WriteModel)
|
||||
changedEvent, err := existingAction.NewChangedEvent(
|
||||
ctx,
|
||||
actionAgg,
|
||||
actionChange.Name,
|
||||
actionChange.Script,
|
||||
actionChange.Timeout,
|
||||
actionChange.AllowedToFail)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pushedEvents, err := c.eventstore.PushEvents(ctx, changedEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingAction, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&existingAction.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) DeactivateAction(ctx context.Context, actionID string, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
if actionID == "" || resourceOwner == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-DAhk5", "Errors.IDMissing")
|
||||
}
|
||||
|
||||
existingAction, err := c.getActionWriteModelByID(ctx, actionID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !existingAction.State.Exists() {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-NRmhu", "Errors.Action.NotFound")
|
||||
}
|
||||
if existingAction.State != domain.ActionStateActive {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Dgj92", "Errors.Action.NotActive")
|
||||
}
|
||||
actionAgg := ActionAggregateFromWriteModel(&existingAction.WriteModel)
|
||||
events := []eventstore.EventPusher{
|
||||
action.NewDeactivatedEvent(ctx, actionAgg),
|
||||
}
|
||||
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingAction, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&existingAction.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) ReactivateAction(ctx context.Context, actionID string, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
if actionID == "" || resourceOwner == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-BNm56", "Errors.IDMissing")
|
||||
}
|
||||
|
||||
existingAction, err := c.getActionWriteModelByID(ctx, actionID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !existingAction.State.Exists() {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-Aa22g", "Errors.Action.NotFound")
|
||||
}
|
||||
if existingAction.State != domain.ActionStateInactive {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-J53zh", "Errors.Action.NotInactive")
|
||||
}
|
||||
actionAgg := ActionAggregateFromWriteModel(&existingAction.WriteModel)
|
||||
events := []eventstore.EventPusher{
|
||||
action.NewReactivatedEvent(ctx, actionAgg),
|
||||
}
|
||||
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingAction, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&existingAction.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) DeleteAction(ctx context.Context, actionID, resourceOwner string, flowTypes ...domain.FlowType) (*domain.ObjectDetails, error) {
|
||||
if actionID == "" || resourceOwner == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Gfg3g", "Errors.IDMissing")
|
||||
}
|
||||
|
||||
existingAction, err := c.getActionWriteModelByID(ctx, actionID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !existingAction.State.Exists() {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-Dgh4h", "Errors.Action.NotFound")
|
||||
}
|
||||
actionAgg := ActionAggregateFromWriteModel(&existingAction.WriteModel)
|
||||
events := []eventstore.EventPusher{
|
||||
action.NewRemovedEvent(ctx, actionAgg, existingAction.Name),
|
||||
}
|
||||
orgAgg := org.NewAggregate(resourceOwner, resourceOwner).Aggregate
|
||||
for _, flowType := range flowTypes {
|
||||
events = append(events, org.NewTriggerActionsCascadeRemovedEvent(ctx, &orgAgg, flowType, actionID))
|
||||
}
|
||||
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingAction, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&existingAction.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) removeActionsFromOrg(ctx context.Context, resourceOwner string) ([]eventstore.EventPusher, error) {
|
||||
existingActions, err := c.getActionsByOrgWriteModelByID(ctx, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(existingActions.Actions) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
events := make([]eventstore.EventPusher, 0, len(existingActions.Actions))
|
||||
for id, name := range existingActions.Actions {
|
||||
actionAgg := NewActionAggregate(id, resourceOwner)
|
||||
events = append(events, action.NewRemovedEvent(ctx, actionAgg, name))
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
func (c *Commands) getActionWriteModelByID(ctx context.Context, actionID string, resourceOwner string) (*ActionWriteModel, error) {
|
||||
actionWriteModel := NewActionWriteModel(actionID, resourceOwner)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, actionWriteModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return actionWriteModel, nil
|
||||
}
|
||||
|
||||
func (c *Commands) getActionsByOrgWriteModelByID(ctx context.Context, resourceOwner string) (*ActionsListByOrgModel, error) {
|
||||
actionWriteModel := NewActionsListByOrgModel(resourceOwner)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, actionWriteModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return actionWriteModel, nil
|
||||
}
|
194
internal/command/org_action_model.go
Normal file
194
internal/command/org_action_model.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/action"
|
||||
)
|
||||
|
||||
type ActionWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
Name string
|
||||
Script string
|
||||
Timeout time.Duration
|
||||
AllowedToFail bool
|
||||
State domain.ActionState
|
||||
}
|
||||
|
||||
func NewActionWriteModel(actionID string, resourceOwner string) *ActionWriteModel {
|
||||
return &ActionWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: actionID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *ActionWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *action.AddedEvent:
|
||||
wm.Name = e.Name
|
||||
wm.Script = e.Script
|
||||
wm.Timeout = e.Timeout
|
||||
wm.AllowedToFail = e.AllowedToFail
|
||||
wm.State = domain.ActionStateActive
|
||||
case *action.ChangedEvent:
|
||||
if e.Name != nil {
|
||||
wm.Name = *e.Name
|
||||
}
|
||||
if e.Script != nil {
|
||||
wm.Script = *e.Script
|
||||
}
|
||||
if e.Timeout != nil {
|
||||
wm.Timeout = *e.Timeout
|
||||
}
|
||||
if e.AllowedToFail != nil {
|
||||
wm.AllowedToFail = *e.AllowedToFail
|
||||
}
|
||||
case *action.DeactivatedEvent:
|
||||
wm.State = domain.ActionStateInactive
|
||||
case *action.ReactivatedEvent:
|
||||
wm.State = domain.ActionStateActive
|
||||
case *action.RemovedEvent:
|
||||
wm.State = domain.ActionStateRemoved
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *ActionWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(action.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(action.AddedEventType,
|
||||
action.ChangedEventType,
|
||||
action.DeactivatedEventType,
|
||||
action.ReactivatedEventType,
|
||||
action.RemovedEventType).
|
||||
Builder()
|
||||
}
|
||||
|
||||
func (wm *ActionWriteModel) NewChangedEvent(
|
||||
ctx context.Context,
|
||||
agg *eventstore.Aggregate,
|
||||
name string,
|
||||
script string,
|
||||
timeout time.Duration,
|
||||
allowedToFail bool,
|
||||
) (*action.ChangedEvent, error) {
|
||||
changes := make([]action.ActionChanges, 0)
|
||||
if wm.Name != name {
|
||||
changes = append(changes, action.ChangeName(name, wm.Name))
|
||||
}
|
||||
if wm.Script != script {
|
||||
changes = append(changes, action.ChangeScript(script))
|
||||
}
|
||||
if wm.Timeout != timeout {
|
||||
changes = append(changes, action.ChangeTimeout(timeout))
|
||||
}
|
||||
if wm.AllowedToFail != allowedToFail {
|
||||
changes = append(changes, action.ChangeAllowedToFail(allowedToFail))
|
||||
}
|
||||
return action.NewChangedEvent(ctx, agg, changes)
|
||||
}
|
||||
|
||||
func ActionAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
|
||||
return eventstore.AggregateFromWriteModel(wm, action.AggregateType, action.AggregateVersion)
|
||||
}
|
||||
|
||||
func NewActionAggregate(id, resourceOwner string) *eventstore.Aggregate {
|
||||
return ActionAggregateFromWriteModel(&eventstore.WriteModel{
|
||||
AggregateID: id,
|
||||
ResourceOwner: resourceOwner,
|
||||
})
|
||||
}
|
||||
|
||||
type ActionExistsModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
actionIDs []string
|
||||
checkedIDs []string
|
||||
}
|
||||
|
||||
func NewActionsExistModel(actionIDs []string, resourceOwner string) *ActionExistsModel {
|
||||
return &ActionExistsModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
actionIDs: actionIDs,
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *ActionExistsModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *action.AddedEvent:
|
||||
wm.checkedIDs = append(wm.checkedIDs, e.Aggregate().ID)
|
||||
case *action.RemovedEvent:
|
||||
for i := len(wm.checkedIDs) - 1; i >= 0; i-- {
|
||||
if wm.checkedIDs[i] == e.Aggregate().ID {
|
||||
wm.checkedIDs[i] = wm.checkedIDs[len(wm.checkedIDs)-1]
|
||||
wm.checkedIDs[len(wm.checkedIDs)-1] = ""
|
||||
wm.checkedIDs = wm.checkedIDs[:len(wm.checkedIDs)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *ActionExistsModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(action.AggregateType).
|
||||
AggregateIDs(wm.actionIDs...).
|
||||
EventTypes(action.AddedEventType,
|
||||
action.RemovedEventType).
|
||||
Builder()
|
||||
}
|
||||
|
||||
type ActionsListByOrgModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
Actions map[string]string
|
||||
}
|
||||
|
||||
func NewActionsListByOrgModel(resourceOwner string) *ActionsListByOrgModel {
|
||||
return &ActionsListByOrgModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
Actions: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *ActionsListByOrgModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *action.AddedEvent:
|
||||
wm.Actions[e.Aggregate().ID] = e.Name
|
||||
case *action.RemovedEvent:
|
||||
delete(wm.Actions, e.Aggregate().ID)
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *ActionsListByOrgModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(action.AggregateType).
|
||||
EventTypes(action.AddedEventType,
|
||||
action.RemovedEventType).
|
||||
Builder()
|
||||
}
|
791
internal/command/org_action_test.go
Normal file
791
internal/command/org_action_test.go
Normal file
@@ -0,0 +1,791 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
"github.com/caos/zitadel/internal/id/mock"
|
||||
"github.com/caos/zitadel/internal/repository/action"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCommands_AddAction(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
addAction *domain.Action
|
||||
resourceOwner string
|
||||
}
|
||||
type res struct {
|
||||
id string
|
||||
details *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"no name, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
addAction: &domain.Action{
|
||||
Script: "test()",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
"unique constraint failed, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectPushFailed(
|
||||
errors.ThrowPreconditionFailed(nil, "id", "name already exists"),
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
action.NewAddedEvent(context.Background(),
|
||||
&action.NewAggregate("id1", "org1").Aggregate,
|
||||
"name",
|
||||
"name() {};",
|
||||
0,
|
||||
false,
|
||||
),
|
||||
),
|
||||
},
|
||||
uniqueConstraintsFromEventConstraint(action.NewAddActionNameUniqueConstraint("name", "org1")),
|
||||
),
|
||||
),
|
||||
idGenerator: mock.ExpectID(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
addAction: &domain.Action{
|
||||
Name: "name",
|
||||
Script: "name() {};",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
"push ok",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
action.NewAddedEvent(context.Background(),
|
||||
&action.NewAggregate("id1", "org1").Aggregate,
|
||||
"name",
|
||||
"name() {};",
|
||||
0,
|
||||
false,
|
||||
),
|
||||
),
|
||||
},
|
||||
uniqueConstraintsFromEventConstraint(action.NewAddActionNameUniqueConstraint("name", "org1")),
|
||||
),
|
||||
),
|
||||
idGenerator: mock.ExpectID(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
addAction: &domain.Action{
|
||||
Name: "name",
|
||||
Script: "name() {};",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
id: "id1",
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
}
|
||||
id, details, err := c.AddAction(tt.args.ctx, tt.args.addAction, tt.args.resourceOwner)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.id, id)
|
||||
assert.Equal(t, tt.res.details, details)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_ChangeAction(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
changeAction *domain.Action
|
||||
resourceOwner string
|
||||
}
|
||||
type res struct {
|
||||
details *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"id missing, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
changeAction: &domain.Action{
|
||||
Name: "name",
|
||||
Script: "name() {};",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
"not found, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
changeAction: &domain.Action{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "id1",
|
||||
},
|
||||
Name: "name",
|
||||
Script: "name() {};",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
err: errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
"no changes, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
action.NewAddedEvent(context.Background(),
|
||||
&action.NewAggregate("id1", "org1").Aggregate,
|
||||
"name",
|
||||
"name() {};",
|
||||
0,
|
||||
false,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
changeAction: &domain.Action{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "id1",
|
||||
},
|
||||
Name: "name",
|
||||
Script: "name() {};",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
"unique constraint failed, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
action.NewAddedEvent(context.Background(),
|
||||
&action.NewAggregate("id1", "org1").Aggregate,
|
||||
"name",
|
||||
"name() {};",
|
||||
0,
|
||||
false,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPushFailed(
|
||||
errors.ThrowPreconditionFailed(nil, "id", "name already exists"),
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
func() *action.ChangedEvent {
|
||||
event, _ := action.NewChangedEvent(context.Background(),
|
||||
&action.NewAggregate("id1", "org1").Aggregate,
|
||||
[]action.ActionChanges{
|
||||
action.ChangeName("name2", "name"),
|
||||
action.ChangeScript("name2() {};"),
|
||||
},
|
||||
)
|
||||
return event
|
||||
}(),
|
||||
),
|
||||
},
|
||||
uniqueConstraintsFromEventConstraint(action.NewRemoveActionNameUniqueConstraint("name", "org1")),
|
||||
uniqueConstraintsFromEventConstraint(action.NewAddActionNameUniqueConstraint("name2", "org1")),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
changeAction: &domain.Action{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "id1",
|
||||
},
|
||||
Name: "name2",
|
||||
Script: "name2() {};",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
"push ok",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
action.NewAddedEvent(context.Background(),
|
||||
&action.NewAggregate("id1", "org1").Aggregate,
|
||||
"name",
|
||||
"name() {};",
|
||||
0,
|
||||
false,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
func() *action.ChangedEvent {
|
||||
event, _ := action.NewChangedEvent(context.Background(),
|
||||
&action.NewAggregate("id1", "org1").Aggregate,
|
||||
[]action.ActionChanges{
|
||||
action.ChangeName("name2", "name"),
|
||||
action.ChangeScript("name2() {};"),
|
||||
},
|
||||
)
|
||||
return event
|
||||
}(),
|
||||
),
|
||||
},
|
||||
uniqueConstraintsFromEventConstraint(action.NewRemoveActionNameUniqueConstraint("name", "org1")),
|
||||
uniqueConstraintsFromEventConstraint(action.NewAddActionNameUniqueConstraint("name2", "org1")),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
changeAction: &domain.Action{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "id1",
|
||||
},
|
||||
Name: "name2",
|
||||
Script: "name2() {};",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
details, err := c.ChangeAction(tt.args.ctx, tt.args.changeAction, tt.args.resourceOwner)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.details, details)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_DeactivateAction(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
actionID string
|
||||
resourceOwner string
|
||||
}
|
||||
type res struct {
|
||||
details *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"id missing, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
actionID: "",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
"not found, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
actionID: "id1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
err: errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
"not active, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
action.NewAddedEvent(context.Background(),
|
||||
&action.NewAggregate("id1", "org1").Aggregate,
|
||||
"name",
|
||||
"name() {};",
|
||||
0,
|
||||
false,
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
action.NewDeactivatedEvent(context.Background(),
|
||||
&action.NewAggregate("id1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
actionID: "id1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
"deactivate ok",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
action.NewAddedEvent(context.Background(),
|
||||
&action.NewAggregate("id1", "org1").Aggregate,
|
||||
"name",
|
||||
"name() {};",
|
||||
0,
|
||||
false,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
action.NewDeactivatedEvent(context.Background(),
|
||||
&action.NewAggregate("id1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
actionID: "id1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
details, err := c.DeactivateAction(tt.args.ctx, tt.args.actionID, tt.args.resourceOwner)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.details, details)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_ReactivateAction(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
actionID string
|
||||
resourceOwner string
|
||||
}
|
||||
type res struct {
|
||||
details *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"id missing, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
actionID: "",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
"not found, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
actionID: "id1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
err: errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
"not inactive, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
action.NewAddedEvent(context.Background(),
|
||||
&action.NewAggregate("id1", "org1").Aggregate,
|
||||
"name",
|
||||
"name() {};",
|
||||
0,
|
||||
false,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
actionID: "id1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
"reactivate ok",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
action.NewAddedEvent(context.Background(),
|
||||
&action.NewAggregate("id1", "org1").Aggregate,
|
||||
"name",
|
||||
"name() {};",
|
||||
0,
|
||||
false,
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
action.NewDeactivatedEvent(context.Background(),
|
||||
&action.NewAggregate("id1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
action.NewReactivatedEvent(context.Background(),
|
||||
&action.NewAggregate("id1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
actionID: "id1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
details, err := c.ReactivateAction(tt.args.ctx, tt.args.actionID, tt.args.resourceOwner)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.details, details)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_DeleteAction(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
id string
|
||||
resourceOwner string
|
||||
flowTypes []domain.FlowType
|
||||
}
|
||||
type res struct {
|
||||
details *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"id or resourceOwner emtpy, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
id: "",
|
||||
resourceOwner: "",
|
||||
},
|
||||
res{
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
"action not found, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
id: "id1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
err: errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
"remove ok",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
action.NewAddedEvent(context.Background(),
|
||||
&action.NewAggregate("id1", "org1").Aggregate,
|
||||
"name",
|
||||
"name() {};",
|
||||
0,
|
||||
false,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
action.NewRemovedEvent(context.Background(),
|
||||
&action.NewAggregate("id1", "org1").Aggregate,
|
||||
"name",
|
||||
),
|
||||
),
|
||||
},
|
||||
uniqueConstraintsFromEventConstraint(action.NewRemoveActionNameUniqueConstraint("name", "org1")),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
id: "id1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"remove with used action ok",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
action.NewAddedEvent(context.Background(),
|
||||
&action.NewAggregate("id1", "org1").Aggregate,
|
||||
"name",
|
||||
"name() {};",
|
||||
0,
|
||||
false,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
action.NewRemovedEvent(context.Background(),
|
||||
&action.NewAggregate("id1", "org1").Aggregate,
|
||||
"name",
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
org.NewTriggerActionsCascadeRemovedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
domain.FlowTypeExternalAuthentication,
|
||||
"id1",
|
||||
),
|
||||
),
|
||||
},
|
||||
uniqueConstraintsFromEventConstraint(action.NewRemoveActionNameUniqueConstraint("name", "org1")),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
id: "id1",
|
||||
resourceOwner: "org1",
|
||||
flowTypes: []domain.FlowType{
|
||||
domain.FlowTypeExternalAuthentication,
|
||||
},
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
details, err := c.DeleteAction(tt.args.ctx, tt.args.id, tt.args.resourceOwner, tt.args.flowTypes...)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.details, details)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -45,6 +45,7 @@ func (c *Commands) SetOrgFeatures(ctx context.Context, resourceOwner string, fea
|
||||
features.CustomTextMessage,
|
||||
features.CustomTextLogin,
|
||||
features.LockoutPolicy,
|
||||
features.Actions,
|
||||
)
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")
|
||||
@@ -176,6 +177,15 @@ func (c *Commands) ensureOrgSettingsToFeatures(ctx context.Context, orgID string
|
||||
events = append(events, removeOrgUserMetadatas...)
|
||||
}
|
||||
}
|
||||
if !features.Actions {
|
||||
removeOrgActions, err := c.removeActionsFromOrg(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(removeOrgActions) > 0 {
|
||||
events = append(events, removeOrgActions...)
|
||||
}
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
|
@@ -78,7 +78,8 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent(
|
||||
metadataUser,
|
||||
customTextMessage,
|
||||
customTextLogin,
|
||||
lockoutPolicy bool,
|
||||
lockoutPolicy,
|
||||
actions bool,
|
||||
) (*org.FeaturesSetEvent, bool) {
|
||||
|
||||
changes := make([]features.FeaturesChanges, 0)
|
||||
@@ -143,6 +144,9 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent(
|
||||
if wm.LockoutPolicy != lockoutPolicy {
|
||||
changes = append(changes, features.ChangeLockoutPolicy(lockoutPolicy))
|
||||
}
|
||||
if wm.Actions != actions {
|
||||
changes = append(changes, features.ChangeActions(actions))
|
||||
}
|
||||
|
||||
if len(changes) == 0 {
|
||||
return nil, false
|
||||
|
@@ -275,6 +275,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
@@ -307,6 +308,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
||||
PrivacyPolicy: false,
|
||||
MetadataUser: false,
|
||||
LockoutPolicy: false,
|
||||
Actions: false,
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -472,6 +474,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
@@ -509,6 +512,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
||||
MetadataUser: false,
|
||||
PrivacyPolicy: false,
|
||||
LockoutPolicy: false,
|
||||
Actions: false,
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -681,6 +685,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
@@ -721,6 +726,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
||||
MetadataUser: false,
|
||||
PrivacyPolicy: false,
|
||||
LockoutPolicy: false,
|
||||
Actions: false,
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -900,6 +906,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
@@ -943,6 +950,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
||||
MetadataUser: false,
|
||||
PrivacyPolicy: false,
|
||||
LockoutPolicy: false,
|
||||
Actions: false,
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -1174,6 +1182,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
@@ -1234,6 +1243,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
||||
MetadataUser: false,
|
||||
PrivacyPolicy: false,
|
||||
LockoutPolicy: false,
|
||||
Actions: false,
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -1387,6 +1397,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
@@ -1422,6 +1433,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
||||
PrivacyPolicy: false,
|
||||
MetadataUser: false,
|
||||
LockoutPolicy: false,
|
||||
Actions: false,
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -1635,6 +1647,7 @@ func TestCommandSide_RemoveOrgFeatures(t *testing.T) {
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
|
83
internal/command/org_flow.go
Normal file
83
internal/command/org_flow.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
)
|
||||
|
||||
func (c *Commands) ClearFlow(ctx context.Context, flowType domain.FlowType, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
if !flowType.Valid() || resourceOwner == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Dfw2h", "Errors.Flow.FlowTypeMissing")
|
||||
}
|
||||
existingFlow, err := c.getOrgFlowWriteModelByType(ctx, flowType, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(existingFlow.Triggers) == 0 {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-DgGh3", "Errors.Flow.Empty")
|
||||
}
|
||||
orgAgg := OrgAggregateFromWriteModel(&existingFlow.WriteModel)
|
||||
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewFlowClearedEvent(ctx, orgAgg, flowType))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingFlow, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&existingFlow.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) SetTriggerActions(ctx context.Context, flowType domain.FlowType, triggerType domain.TriggerType, actionIDs []string, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
if !flowType.Valid() || !triggerType.Valid() || resourceOwner == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Dfhj5", "Errors.Flow.FlowTypeMissing")
|
||||
}
|
||||
if !flowType.HasTrigger(triggerType) {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Dfgh6", "Errors.Flow.WrongTriggerType")
|
||||
}
|
||||
existingFlow, err := c.getOrgFlowWriteModelByType(ctx, flowType, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if reflect.DeepEqual(existingFlow.Triggers[triggerType], actionIDs) {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Nfh52", "Errors.Flow.NoChanges")
|
||||
}
|
||||
if len(actionIDs) > 0 {
|
||||
exists, err := c.actionsIDsExist(ctx, actionIDs, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-dg422", "Errors.Flow.ActionIDsNotExist")
|
||||
}
|
||||
}
|
||||
orgAgg := OrgAggregateFromWriteModel(&existingFlow.WriteModel)
|
||||
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewTriggerActionsSetEvent(ctx, orgAgg, flowType, triggerType, actionIDs))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingFlow, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&existingFlow.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) getOrgFlowWriteModelByType(ctx context.Context, flowType domain.FlowType, resourceOwner string) (*OrgFlowWriteModel, error) {
|
||||
flowWriteModel := NewOrgFlowWriteModel(flowType, resourceOwner)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, flowWriteModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return flowWriteModel, nil
|
||||
}
|
||||
|
||||
func (c *Commands) actionsIDsExist(ctx context.Context, ids []string, resourceOwner string) (bool, error) {
|
||||
actionIDsModel := NewActionsExistModel(ids, resourceOwner)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, actionIDsModel)
|
||||
return len(actionIDsModel.actionIDs) == len(actionIDsModel.checkedIDs), err
|
||||
}
|
54
internal/command/org_flow_model.go
Normal file
54
internal/command/org_flow_model.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
)
|
||||
|
||||
type OrgFlowWriteModel struct {
|
||||
FlowWriteModel
|
||||
}
|
||||
|
||||
func NewOrgFlowWriteModel(flowType domain.FlowType, resourceOwner string) *OrgFlowWriteModel {
|
||||
return &OrgFlowWriteModel{
|
||||
FlowWriteModel: *NewFlowWriteModel(flowType, resourceOwner),
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *OrgFlowWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *org.TriggerActionsSetEvent:
|
||||
if e.FlowType != wm.FlowType {
|
||||
continue
|
||||
}
|
||||
wm.FlowWriteModel.AppendEvents(&e.TriggerActionsSetEvent)
|
||||
case *org.TriggerActionsCascadeRemovedEvent:
|
||||
if e.FlowType != wm.FlowType {
|
||||
continue
|
||||
}
|
||||
wm.FlowWriteModel.AppendEvents(&e.TriggerActionsCascadeRemovedEvent)
|
||||
case *org.FlowClearedEvent:
|
||||
if e.FlowType != wm.FlowType {
|
||||
continue
|
||||
}
|
||||
wm.FlowWriteModel.AppendEvents(&e.FlowClearedEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *OrgFlowWriteModel) Reduce() error {
|
||||
return wm.FlowWriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *OrgFlowWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(org.AggregateType).
|
||||
EventTypes(org.TriggerActionsSetEventType,
|
||||
org.TriggerActionsCascadeRemovedEventType,
|
||||
org.FlowClearedEventType).
|
||||
Builder()
|
||||
}
|
286
internal/command/org_flow_test.go
Normal file
286
internal/command/org_flow_test.go
Normal file
@@ -0,0 +1,286 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/action"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCommands_ClearFlow(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
flowType domain.FlowType
|
||||
resourceOwner string
|
||||
}
|
||||
type res struct {
|
||||
details *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"invalid flow type, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
flowType: domain.FlowTypeUnspecified,
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
details: nil,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
"already empty, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
flowType: domain.FlowTypeExternalAuthentication,
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
details: nil,
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
"clear ok",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewTriggerActionsSetEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
domain.FlowTypeExternalAuthentication,
|
||||
domain.TriggerTypePostAuthentication,
|
||||
[]string{"actionID1"},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
eventPusherToEvents(
|
||||
org.NewFlowClearedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
domain.FlowTypeExternalAuthentication,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
flowType: domain.FlowTypeExternalAuthentication,
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
details, err := c.ClearFlow(tt.args.ctx, tt.args.flowType, tt.args.resourceOwner)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.details, details)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_SetTriggerActions(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
flowType domain.FlowType
|
||||
resourceOwner string
|
||||
triggerType domain.TriggerType
|
||||
actionIDs []string
|
||||
}
|
||||
type res struct {
|
||||
details *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"invalid flow type, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
flowType: domain.FlowTypeUnspecified,
|
||||
triggerType: domain.TriggerTypePostAuthentication,
|
||||
actionIDs: []string{"actionID1"},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
details: nil,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
//TODO: combination not possible at the moment, add when more flow types available
|
||||
//{
|
||||
// "impossible flow / trigger type, error",
|
||||
// fields{
|
||||
// eventstore: eventstoreExpect(t,),
|
||||
// },
|
||||
// args{
|
||||
// ctx: context.Background(),
|
||||
// flowType: domain.FlowTypeUnspecified,
|
||||
// triggerType: domain.TriggerTypePostAuthentication,
|
||||
// actionIDs: []string{"actionID1"},
|
||||
// resourceOwner: "org1",
|
||||
// },
|
||||
// res{
|
||||
// details: nil,
|
||||
// err: errors.IsErrorInvalidArgument,
|
||||
// },
|
||||
//},
|
||||
{
|
||||
"no changes, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewTriggerActionsSetEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
domain.FlowTypeExternalAuthentication,
|
||||
domain.TriggerTypePostAuthentication,
|
||||
[]string{"actionID1"},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
flowType: domain.FlowTypeExternalAuthentication,
|
||||
triggerType: domain.TriggerTypePostAuthentication,
|
||||
actionIDs: []string{"actionID1"},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
details: nil,
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
"actionID not exists, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
flowType: domain.FlowTypeExternalAuthentication,
|
||||
triggerType: domain.TriggerTypePostAuthentication,
|
||||
actionIDs: []string{"actionID1"},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
details: nil,
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
"set ok",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
action.NewAddedEvent(context.Background(),
|
||||
&action.NewAggregate("action1", "org1").Aggregate,
|
||||
"actionID1",
|
||||
"function(ctx, api) action {};",
|
||||
0,
|
||||
false,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
eventPusherToEvents(
|
||||
org.NewTriggerActionsSetEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
domain.FlowTypeExternalAuthentication,
|
||||
domain.TriggerTypePostAuthentication,
|
||||
[]string{"actionID1"},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
flowType: domain.FlowTypeExternalAuthentication,
|
||||
triggerType: domain.TriggerTypePostAuthentication,
|
||||
actionIDs: []string{"actionID1"},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
details, err := c.SetTriggerActions(tt.args.ctx, tt.args.flowType, tt.args.triggerType, tt.args.actionIDs, tt.args.resourceOwner)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.details, details)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -2,9 +2,10 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"reflect"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/repository/usergrant"
|
||||
@@ -29,10 +30,6 @@ func (c *Commands) AddUserGrant(ctx context.Context, usergrant *domain.UserGrant
|
||||
}
|
||||
|
||||
func (c *Commands) addUserGrant(ctx context.Context, userGrant *domain.UserGrant, resourceOwner string) (pusher eventstore.EventPusher, _ *UserGrantWriteModel, err error) {
|
||||
err = checkExplicitProjectPermission(ctx, userGrant.ProjectGrantID, userGrant.ProjectID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if !userGrant.IsValid() {
|
||||
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M0fs", "Errors.UserGrant.Invalid")
|
||||
}
|
||||
|
@@ -2,6 +2,8 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
@@ -15,7 +17,6 @@ import (
|
||||
"github.com/caos/zitadel/internal/repository/usergrant"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/text/language"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCommandSide_AddUserGrant(t *testing.T) {
|
||||
@@ -38,24 +39,6 @@ func TestCommandSide_AddUserGrant(t *testing.T) {
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "invalid permissions, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userGrant: &domain.UserGrant{
|
||||
UserID: "user1",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid usergrant, error",
|
||||
fields: fields{
|
||||
|
Reference in New Issue
Block a user