zitadel/internal/command/action_v2_execution_test.go
Elio Bischof cc3ec1e2a7
feat(v3alpha): write actions (#8225)
# Which Problems Are Solved

The current v3alpha actions APIs don't exactly adhere to the [new
resources API
design](https://zitadel.com/docs/apis/v3#standard-resources).

# How the Problems Are Solved

- **Breaking**: The current v3alpha actions APIs are removed. This is
breaking.
- **Resource Namespace**: New v3alpha actions APIs for targets and
executions are added under the namespace /resources.
- **Feature Flag**: New v3alpha actions APIs still have to be activated
using the actions feature flag
- **Reduced Executions Overhead**: Executions are managed similar to
settings according to the new API design: an empty list of targets
basically makes an execution a Noop. So a single method, SetExecution is
enough to cover all use cases. Noop executions are not returned in
future search requests.
- **Compatibility**: The executions created with previous v3alpha APIs
are still available to be managed with the new executions API.

# Additional Changes

- Removed integration tests which test executions but rely on readable
targets. They are added again with #8169

# Additional Context

Closes #8168
2024-07-31 14:42:12 +02:00

2207 lines
49 KiB
Go

package command
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/execution"
"github.com/zitadel/zitadel/internal/repository/target"
"github.com/zitadel/zitadel/internal/zerrors"
)
func existsMock(exists bool) func(method string) bool {
return func(method string) bool {
return exists
}
}
func TestCommands_SetExecutionRequest(t *testing.T) {
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
grpcMethodExists func(method string) bool
grpcServiceExists func(method string) bool
}
type args struct {
ctx context.Context
cond *ExecutionAPICondition
set *SetExecution
resourceOwner string
}
type res struct {
details *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"no resourceowner, error",
fields{
eventstore: expectEventstore(),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{},
set: &SetExecution{},
resourceOwner: "",
},
res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
"no cond, error",
fields{
eventstore: expectEventstore(),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{},
set: &SetExecution{},
resourceOwner: "instance",
},
res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
"no valid cond, error",
fields{
eventstore: expectEventstore(),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"notvalid",
"notvalid",
false,
},
set: &SetExecution{},
resourceOwner: "instance",
},
res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
"empty target, error",
fields{
eventstore: expectEventstore(),
grpcMethodExists: existsMock(true),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"notvalid",
"",
false,
},
set: &SetExecution{Targets: []*execution.Target{{}}},
resourceOwner: "instance",
},
res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
"method not found, error",
fields{
eventstore: expectEventstore(),
grpcMethodExists: existsMock(false),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"method",
"",
false,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
},
resourceOwner: "instance",
},
res{
err: zerrors.IsNotFound,
},
},
{
"service not found, error",
fields{
eventstore: expectEventstore(),
grpcServiceExists: existsMock(false),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"",
"service",
false,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
},
resourceOwner: "instance",
},
res{
err: zerrors.IsNotFound,
},
},
{
"push ok, method target",
fields{
eventstore: expectEventstore(
expectFilter(), // execution doesn't exist yet
expectFilter(
eventFromEventPusher(
target.NewAddedEvent(context.Background(),
target.NewAggregate("target", "instance"),
"name",
domain.TargetTypeWebhook,
"https://example.com",
time.Second,
true,
),
),
),
expectPush(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("request/method", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
grpcMethodExists: existsMock(true),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"method",
"",
false,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
},
resourceOwner: "instance",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instance",
},
},
},
{
"push ok, service target",
fields{
eventstore: expectEventstore(
expectFilter(), // execution doesn't exist yet
expectFilter(
eventFromEventPusher(
target.NewAddedEvent(context.Background(),
target.NewAggregate("target", "instance"),
"name",
domain.TargetTypeWebhook,
"https://example.com",
time.Second,
true,
),
),
),
expectPush(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("request/service", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
grpcServiceExists: existsMock(true),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"",
"service",
false,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
},
resourceOwner: "instance",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instance",
},
},
},
{
"push ok, all target",
fields{
eventstore: expectEventstore(
expectFilter(), // execution doesn't exist yet
expectFilter(
eventFromEventPusher(
target.NewAddedEvent(context.Background(),
target.NewAggregate("target", "instance"),
"name",
domain.TargetTypeWebhook,
"https://example.com",
time.Second,
true,
),
),
),
expectPush(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("request", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"",
"",
true,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
},
resourceOwner: "instance",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instance",
},
},
},
{
"push not found, method include",
fields{
eventstore: expectEventstore(
expectFilter(), // execution doesn't exist yet
expectFilter(), // target doesn't exist
),
grpcMethodExists: existsMock(true),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"method",
"",
false,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeInclude, Target: "request/include"},
},
},
resourceOwner: "instance",
},
res{
err: zerrors.IsNotFound,
},
},
{
"push ok, method include",
fields{
eventstore: expectEventstore(
expectFilter(), // execution doesn't exist yet
expectFilter(
eventFromEventPusher(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("request/include", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
expectFilter(
eventFromEventPusher(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("request/include", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
expectPush(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("request/method", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeInclude, Target: "request/include"},
},
),
),
),
grpcMethodExists: existsMock(true),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"method",
"",
false,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeInclude, Target: "request/include"},
},
},
resourceOwner: "instance",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instance",
},
},
},
{
"push not found, service include",
fields{
eventstore: expectEventstore(
expectFilter(), // execution doesn't exist yet
expectFilter(), // target doesn't exist
),
grpcServiceExists: existsMock(true),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"",
"service",
false,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeInclude, Target: "request/include"},
},
},
resourceOwner: "instance",
},
res{
err: zerrors.IsNotFound,
},
},
{
"push ok, service include",
fields{
eventstore: expectEventstore(
expectFilter(), // execution doesn't exist yet
expectFilter(
eventFromEventPusher(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("request/include", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
expectFilter(
eventFromEventPusher(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("request/include", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
expectPush(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("request/service", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeInclude, Target: "request/include"},
},
),
),
),
grpcServiceExists: existsMock(true),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"",
"service",
false,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeInclude, Target: "request/include"},
},
},
resourceOwner: "instance",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instance",
},
},
},
{
"push not found, all include",
fields{
eventstore: expectEventstore(
expectFilter(), // execution doesn't exist yet
expectFilter(), // target doesn't exist
),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"",
"",
true,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeInclude, Target: "request/include"},
},
},
resourceOwner: "instance",
},
res{
err: zerrors.IsNotFound,
},
},
{
"push ok, all include",
fields{
eventstore: expectEventstore(
expectFilter(), // execution doesn't exist yet
expectFilter(
eventFromEventPusher(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("request/include", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
expectFilter(
eventFromEventPusher(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("request/include", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
expectPush(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("request", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeInclude, Target: "request/include"},
},
),
),
),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"",
"",
true,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeInclude, Target: "request/include"},
},
},
resourceOwner: "instance",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instance",
},
},
},
{
"push ok, remove all targets",
fields{
eventstore: expectEventstore(
expectFilter( // execution has targets
eventFromEventPusher(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("request", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
expectPush(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("request", "instance"),
[]*execution.Target{},
),
),
),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"",
"",
true,
},
set: &SetExecution{
Targets: []*execution.Target{},
},
resourceOwner: "instance",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instance",
},
},
},
{
"push ok, unchanged execution",
fields{
eventstore: expectEventstore(
expectFilter( // execution has targets
eventFromEventPusher(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("request", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"",
"",
true,
},
set: &SetExecution{
Targets: []*execution.Target{{
Type: domain.ExecutionTargetTypeTarget,
Target: "target",
}},
},
resourceOwner: "instance",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instance",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore(t),
GrpcMethodExisting: tt.fields.grpcMethodExists,
GrpcServiceExisting: tt.fields.grpcServiceExists,
}
details, err := c.SetExecutionRequest(tt.args.ctx, tt.args.cond, tt.args.set, 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_SetExecutionResponse(t *testing.T) {
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
grpcMethodExists func(method string) bool
grpcServiceExists func(method string) bool
}
type args struct {
ctx context.Context
cond *ExecutionAPICondition
set *SetExecution
resourceOwner string
}
type res struct {
details *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"no resourceowner, error",
fields{
eventstore: expectEventstore(),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{},
set: &SetExecution{},
resourceOwner: "",
},
res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
"no cond, error",
fields{
eventstore: expectEventstore(),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{},
set: &SetExecution{},
resourceOwner: "instance",
},
res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
"no valid cond, error",
fields{
eventstore: expectEventstore(),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"notvalid",
"notvalid",
false,
},
set: &SetExecution{},
resourceOwner: "instance",
},
res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
"empty target, error",
fields{
eventstore: expectEventstore(),
grpcMethodExists: existsMock(true),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"notvalid",
"",
false,
},
set: &SetExecution{Targets: []*execution.Target{{}}},
resourceOwner: "instance",
},
res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
"push failed, error",
fields{
eventstore: expectEventstore(
expectFilter(), // execution doesn't exist yet
expectFilter(
target.NewAddedEvent(context.Background(),
target.NewAggregate("target", "instance"),
"name",
domain.TargetTypeWebhook,
"https://example.com",
time.Second,
true,
),
),
expectPushFailed(
zerrors.ThrowPreconditionFailed(nil, "id", "name already exists"),
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("response/valid", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
grpcMethodExists: existsMock(true),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"valid",
"",
false,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
},
resourceOwner: "instance",
},
res{
err: zerrors.IsPreconditionFailed,
},
},
{
"method not found, error",
fields{
eventstore: expectEventstore(),
grpcMethodExists: existsMock(false),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"method",
"",
false,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
},
resourceOwner: "instance",
},
res{
err: zerrors.IsNotFound,
},
},
{
"service not found, error",
fields{
eventstore: expectEventstore(),
grpcServiceExists: existsMock(false),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"",
"service",
false,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
},
resourceOwner: "instance",
},
res{
err: zerrors.IsNotFound,
},
},
{
"push ok, method target",
fields{
eventstore: expectEventstore(
expectFilter(), // execution doesn't exist yet
expectFilter(
eventFromEventPusher(
target.NewAddedEvent(context.Background(),
target.NewAggregate("target", "instance"),
"name",
domain.TargetTypeWebhook,
"https://example.com",
time.Second,
true,
),
),
),
expectPush(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("response/method", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
grpcMethodExists: existsMock(true),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"method",
"",
false,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
},
resourceOwner: "instance",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instance",
},
},
},
{
"push ok, service target",
fields{
eventstore: expectEventstore(
expectFilter(), // execution doesn't exist yet
expectFilter(
targetAddEvent("target", "instance"),
),
expectPush(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("response/service", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
grpcServiceExists: existsMock(true),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"",
"service",
false,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
},
resourceOwner: "instance",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instance",
},
},
},
{
"push ok, all target",
fields{
eventstore: expectEventstore(
expectFilter(), // execution doesn't exist yet
expectFilter(
targetAddEvent("target", "instance"),
),
expectPush(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("response", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"",
"",
true,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
},
resourceOwner: "instance",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instance",
},
},
},
{
"push ok, remove all targets",
fields{
eventstore: expectEventstore(
expectFilter( // execution has targets
eventFromEventPusher(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("response", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
expectPush(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("response", "instance"),
[]*execution.Target{},
),
),
),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"",
"",
true,
},
set: &SetExecution{
Targets: []*execution.Target{},
},
resourceOwner: "instance",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instance",
},
},
},
{
"push ok, unchanged execution",
fields{
eventstore: expectEventstore(
expectFilter( // execution has targets
eventFromEventPusher(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("response", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
),
},
args{
ctx: context.Background(),
cond: &ExecutionAPICondition{
"",
"",
true,
},
set: &SetExecution{
Targets: []*execution.Target{{
Type: domain.ExecutionTargetTypeTarget,
Target: "target",
}},
},
resourceOwner: "instance",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instance",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore(t),
GrpcMethodExisting: tt.fields.grpcMethodExists,
GrpcServiceExisting: tt.fields.grpcServiceExists,
}
details, err := c.SetExecutionResponse(tt.args.ctx, tt.args.cond, tt.args.set, 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_SetExecutionEvent(t *testing.T) {
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
eventExists func(string) bool
eventGroupExists func(string) bool
}
type args struct {
ctx context.Context
cond *ExecutionEventCondition
set *SetExecution
resourceOwner string
}
type res struct {
details *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"no resourceowner, error",
fields{
eventstore: expectEventstore(),
},
args{
ctx: context.Background(),
cond: &ExecutionEventCondition{},
set: &SetExecution{},
resourceOwner: "",
},
res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
"no cond, error",
fields{
eventstore: expectEventstore(),
},
args{
ctx: context.Background(),
cond: &ExecutionEventCondition{},
set: &SetExecution{},
resourceOwner: "instance",
},
res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
"no valid cond, error",
fields{
eventstore: expectEventstore(),
},
args{
ctx: context.Background(),
cond: &ExecutionEventCondition{
"notvalid",
"notvalid",
false,
},
set: &SetExecution{},
resourceOwner: "instance",
},
res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
"empty executionType, error",
fields{
eventstore: expectEventstore(),
eventExists: existsMock(true),
},
args{
ctx: context.Background(),
cond: &ExecutionEventCondition{
"notvalid",
"",
false,
},
set: &SetExecution{Targets: []*execution.Target{{Target: "target"}}},
resourceOwner: "instance",
},
res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
"empty target, error",
fields{
eventstore: expectEventstore(),
eventExists: existsMock(true),
},
args{
ctx: context.Background(),
cond: &ExecutionEventCondition{
"notvalid",
"",
false,
},
set: &SetExecution{Targets: []*execution.Target{{}}},
resourceOwner: "instance",
},
res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
"push failed, error",
fields{
eventstore: expectEventstore(
expectFilter(), // execution doesn't exist yet
expectFilter(
targetAddEvent("target", "instance"),
),
expectPushFailed(
zerrors.ThrowPreconditionFailed(nil, "id", "name already exists"),
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("event/valid", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
eventExists: existsMock(true),
},
args{
ctx: context.Background(),
cond: &ExecutionEventCondition{
"valid",
"",
false,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
},
resourceOwner: "instance",
},
res{
err: zerrors.IsPreconditionFailed,
},
},
{
"event not found, error",
fields{
eventstore: expectEventstore(),
eventExists: existsMock(false),
},
args{
ctx: context.Background(),
cond: &ExecutionEventCondition{
"event",
"",
false,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
},
resourceOwner: "instance",
},
res{
err: zerrors.IsNotFound,
},
},
{
"group not found, error",
fields{
eventstore: expectEventstore(),
eventGroupExists: existsMock(false),
},
args{
ctx: context.Background(),
cond: &ExecutionEventCondition{
"",
"group",
false,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
},
resourceOwner: "instance",
},
res{
err: zerrors.IsNotFound,
},
},
{
"push ok, event target",
fields{
eventstore: expectEventstore(
expectFilter(), // execution doesn't exist yet
expectFilter(
targetAddEvent("target", "instance"),
),
expectPush(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("event/event", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
eventExists: existsMock(true),
},
args{
ctx: context.Background(),
cond: &ExecutionEventCondition{
"event",
"",
false,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
},
resourceOwner: "instance",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instance",
},
},
},
{
"push ok, group target",
fields{
eventstore: expectEventstore(
expectFilter(), // execution doesn't exist yet
expectFilter(
targetAddEvent("target", "instance"),
),
expectPush(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("event/group.*", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
eventGroupExists: existsMock(true),
},
args{
ctx: context.Background(),
cond: &ExecutionEventCondition{
"",
"group",
false,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
},
resourceOwner: "instance",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instance",
},
},
},
{
"push ok, all target",
fields{
eventstore: expectEventstore(
expectFilter(), // execution doesn't exist yet
expectFilter(
targetAddEvent("target", "instance"),
),
expectPush(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("event", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
},
args{
ctx: context.Background(),
cond: &ExecutionEventCondition{
"",
"",
true,
},
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
},
resourceOwner: "instance",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instance",
},
},
},
{
"push ok, remove all targets",
fields{
eventstore: expectEventstore(
expectFilter( // execution has targets
eventFromEventPusher(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("event", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
expectPush(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("event", "instance"),
[]*execution.Target{},
),
),
),
},
args{
ctx: context.Background(),
cond: &ExecutionEventCondition{
"",
"",
true,
},
set: &SetExecution{
Targets: []*execution.Target{},
},
resourceOwner: "instance",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instance",
},
},
},
{
"push ok, unchanged execution",
fields{
eventstore: expectEventstore(
expectFilter( // execution has targets
eventFromEventPusher(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("event", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
),
},
args{
ctx: context.Background(),
cond: &ExecutionEventCondition{
"",
"",
true,
},
set: &SetExecution{
Targets: []*execution.Target{{
Type: domain.ExecutionTargetTypeTarget,
Target: "target",
}},
},
resourceOwner: "instance",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instance",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore(t),
EventExisting: tt.fields.eventExists,
EventGroupExisting: tt.fields.eventGroupExists,
}
details, err := c.SetExecutionEvent(tt.args.ctx, tt.args.cond, tt.args.set, 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_SetExecutionFunction(t *testing.T) {
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
actionFunctionExists func(string) bool
}
type args struct {
ctx context.Context
cond ExecutionFunctionCondition
set *SetExecution
resourceOwner string
}
type res struct {
details *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"no resourceowner, error",
fields{
eventstore: expectEventstore(),
actionFunctionExists: existsMock(true),
},
args{
ctx: context.Background(),
cond: "",
set: &SetExecution{},
resourceOwner: "",
},
res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
"no cond, error",
fields{
eventstore: expectEventstore(),
},
args{
ctx: context.Background(),
cond: "",
set: &SetExecution{},
resourceOwner: "instance",
},
res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
"empty target, error",
fields{
eventstore: expectEventstore(),
actionFunctionExists: existsMock(true),
},
args{
ctx: context.Background(),
cond: "function",
set: &SetExecution{Targets: []*execution.Target{{}}},
resourceOwner: "instance",
},
res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
"push failed, error",
fields{
eventstore: expectEventstore(
expectFilter(), // execution doesn't exist yet
expectFilter(
targetAddEvent("target", "instance"),
),
expectPushFailed(
zerrors.ThrowPreconditionFailed(nil, "id", "name already exists"),
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("function/function", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
actionFunctionExists: existsMock(true),
},
args{
ctx: context.Background(),
cond: "function",
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
},
resourceOwner: "instance",
},
res{
err: zerrors.IsPreconditionFailed,
},
}, {
"push error, function target",
fields{
eventstore: expectEventstore(
expectFilter(), // execution doesn't exist yet
expectFilter(), // target doesn't exist
),
actionFunctionExists: existsMock(true),
},
args{
ctx: context.Background(),
cond: "function",
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
},
resourceOwner: "instance",
},
res{
err: zerrors.IsNotFound,
},
},
{
"push error, function not existing",
fields{
eventstore: expectEventstore(),
actionFunctionExists: existsMock(false),
},
args{
ctx: context.Background(),
cond: "function",
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
},
resourceOwner: "instance",
},
res{
err: zerrors.IsNotFound,
},
},
{
"push ok, function target",
fields{
eventstore: expectEventstore(
expectFilter(), // execution doesn't exist yet
expectFilter(
targetAddEvent("target", "instance"),
),
expectPush(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("function/function", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
actionFunctionExists: existsMock(true),
},
args{
ctx: context.Background(),
cond: "function",
set: &SetExecution{
Targets: []*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
},
resourceOwner: "instance",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instance",
},
},
},
{
"push ok, remove all targets",
fields{
actionFunctionExists: existsMock(true),
eventstore: expectEventstore(
expectFilter( // execution has targets
eventFromEventPusher(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("function/function", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
expectPush(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("function/function", "instance"),
[]*execution.Target{},
),
),
),
},
args{
ctx: context.Background(),
cond: "function",
set: &SetExecution{
Targets: []*execution.Target{},
},
resourceOwner: "instance",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instance",
},
},
},
{
"push ok, unchanged execution",
fields{
actionFunctionExists: existsMock(true),
eventstore: expectEventstore(
expectFilter( // execution has targets
eventFromEventPusher(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("function/function", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
),
},
args{
ctx: context.Background(),
cond: "function",
set: &SetExecution{
Targets: []*execution.Target{{
Type: domain.ExecutionTargetTypeTarget,
Target: "target",
}},
},
resourceOwner: "instance",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "instance",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore(t),
ActionFunctionExisting: tt.fields.actionFunctionExists,
}
details, err := c.SetExecutionFunction(tt.args.ctx, tt.args.cond, tt.args.set, 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 mockExecutionIncludesCache(cache map[string][]string) includeCacheFunc {
return func(ctx context.Context, id string, resourceOwner string) ([]string, error) {
included, ok := cache[id]
if !ok {
return nil, zerrors.ThrowPreconditionFailed(nil, "", "cache failed")
}
return included, nil
}
}
func TestCommands_checkForIncludeCircular(t *testing.T) {
type args struct {
ctx context.Context
id string
resourceOwner string
includes []string
cache map[string][]string
}
type res struct {
err func(error) bool
}
tests := []struct {
name string
args args
res res
}{
{
"not found, error",
args{
ctx: context.Background(),
id: "id",
resourceOwner: "",
includes: []string{"notexistent"},
cache: map[string][]string{},
},
res{
err: zerrors.IsPreconditionFailed,
},
},
{
"single, ok",
args{
ctx: context.Background(),
id: "id1",
resourceOwner: "",
includes: []string{"id2"},
cache: map[string][]string{
"id2": {},
},
},
res{},
},
{
"single, circular",
args{
ctx: context.Background(),
id: "id1",
resourceOwner: "",
includes: []string{"id1"},
cache: map[string][]string{},
},
res{
err: zerrors.IsPreconditionFailed,
},
},
{
"multi 3, ok",
args{
ctx: context.Background(),
id: "id1",
resourceOwner: "",
includes: []string{"id2"},
cache: map[string][]string{
"id2": {"id3"},
"id3": {},
},
},
res{},
},
{
"multi 3, circular",
args{
ctx: context.Background(),
id: "id1",
resourceOwner: "",
includes: []string{"id2"},
cache: map[string][]string{
"id2": {"id3"},
"id3": {"id1"},
},
},
res{
err: zerrors.IsPreconditionFailed,
},
},
{
"multi 5, ok",
args{
ctx: context.Background(),
id: "id1",
resourceOwner: "",
includes: []string{"id11", "id12"},
cache: map[string][]string{
"id11": {"id21", "id23"},
"id12": {"id22"},
"id21": {},
"id22": {},
"id23": {},
},
},
res{},
},
{
"multi 5, circular",
args{
ctx: context.Background(),
id: "id1",
resourceOwner: "",
includes: []string{"id11", "id12"},
cache: map[string][]string{
"id11": {"id21", "id23"},
"id12": {"id22"},
"id21": {},
"id22": {},
"id23": {"id1"},
},
},
res{
err: zerrors.IsPreconditionFailed,
},
},
{
"multi 5, circular",
args{
ctx: context.Background(),
id: "id1",
resourceOwner: "",
includes: []string{"id11", "id12"},
cache: map[string][]string{
"id11": {"id21", "id23"},
"id12": {"id22"},
"id21": {},
"id22": {},
"id23": {"id11"},
},
},
res{
err: zerrors.IsPreconditionFailed,
},
},
{
"multi 5, circular",
args{
ctx: context.Background(),
id: "id1",
resourceOwner: "",
includes: []string{"id11", "id12"},
cache: map[string][]string{
"id11": {"id21", "id23"},
"id12": {"id22"},
"id21": {"id11"},
"id22": {},
"id23": {},
},
},
res{
err: zerrors.IsPreconditionFailed,
},
},
{
"multi 5, circular",
args{
ctx: context.Background(),
id: "id1",
resourceOwner: "",
includes: []string{"id11", "id12"},
cache: map[string][]string{
"id11": {"id21", "id23"},
"id12": {"id22"},
"id21": {},
"id22": {"id12"},
"id23": {},
},
},
res{
err: zerrors.IsPreconditionFailed,
},
},
{
"multi 3, maxlevel",
args{
ctx: context.Background(),
id: "id1",
resourceOwner: "",
includes: []string{"id2"},
cache: map[string][]string{
"id2": {"id3"},
"id3": {},
},
},
res{},
},
{
"multi 4, over maxlevel",
args{
ctx: context.Background(),
id: "id1",
resourceOwner: "",
includes: []string{"id2"},
cache: map[string][]string{
"id2": {"id3"},
"id3": {"id4"},
"id4": {},
},
},
res{
err: zerrors.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f := mockExecutionIncludesCache(tt.args.cache)
err := checkForIncludeCircular(tt.args.ctx, tt.args.id, tt.args.resourceOwner, tt.args.includes, f, 3)
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)
}
})
}
}
func mockExecutionIncludesCacheFuncs(cache map[string][]string) (func(string) ([]string, bool), func(string, []string)) {
return func(s string) ([]string, bool) {
includes, ok := cache[s]
return includes, ok
}, func(s string, strings []string) {
cache[s] = strings
}
}
func TestCommands_getExecutionIncludes(t *testing.T) {
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
cache map[string][]string
id string
resourceOwner string
}
type res struct {
includes []string
cache map[string][]string
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"new empty, ok",
fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("request/include", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
),
},
args{
ctx: context.Background(),
cache: map[string][]string{},
id: "id",
resourceOwner: "instance",
},
res{
includes: []string{},
cache: map[string][]string{"id": {}},
},
},
{
"new includes, ok",
fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("request/include", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
},
),
),
),
),
},
args{
ctx: context.Background(),
cache: map[string][]string{},
id: "id",
resourceOwner: "instance",
},
res{
includes: []string{"include"},
cache: map[string][]string{"id": {"include"}},
},
},
{
"found, ok",
fields{
eventstore: expectEventstore(),
},
args{
ctx: context.Background(),
cache: map[string][]string{"id": nil},
id: "id",
resourceOwner: "instance",
},
res{
includes: nil,
cache: map[string][]string{"id": nil},
},
},
{
"found includes, ok",
fields{
eventstore: expectEventstore(),
},
args{
ctx: context.Background(),
cache: map[string][]string{"id": {"include1", "include2", "include3"}},
id: "id",
resourceOwner: "instance",
},
res{
includes: []string{"include1", "include2", "include3"},
cache: map[string][]string{"id": {"include1", "include2", "include3"}},
},
},
{
"found multiple, ok",
fields{
eventstore: expectEventstore(),
},
args{
ctx: context.Background(),
cache: map[string][]string{
"id1": {"include1", "include2", "include3"},
"id2": {"include1", "include2", "include3"},
"id3": {"include1", "include2", "include3"},
},
id: "id2",
resourceOwner: "instance",
},
res{
includes: []string{"include1", "include2", "include3"},
cache: map[string][]string{
"id1": {"include1", "include2", "include3"},
"id2": {"include1", "include2", "include3"},
"id3": {"include1", "include2", "include3"},
},
},
},
{
"new multiple, ok",
fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("request/include", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
},
),
),
),
),
},
args{
ctx: context.Background(),
cache: map[string][]string{
"id1": {"include1", "include2", "include3"},
"id2": {"include1", "include2", "include3"},
"id3": {"include1", "include2", "include3"},
},
id: "id",
resourceOwner: "instance",
},
res{
includes: []string{},
cache: map[string][]string{
"id1": {"include1", "include2", "include3"},
"id2": {"include1", "include2", "include3"},
"id3": {"include1", "include2", "include3"},
"id": {},
},
},
},
{
"new multiple includes, ok",
fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
execution.NewSetEventV2(context.Background(),
execution.NewAggregate("request/include", "instance"),
[]*execution.Target{
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
},
),
),
),
),
},
args{
ctx: context.Background(),
cache: map[string][]string{
"id1": {"include1", "include2", "include3"},
"id2": {"include1", "include2", "include3"},
"id3": {"include1", "include2", "include3"},
},
id: "id",
resourceOwner: "instance",
},
res{
includes: []string{"include"},
cache: map[string][]string{
"id1": {"include1", "include2", "include3"},
"id2": {"include1", "include2", "include3"},
"id3": {"include1", "include2", "include3"},
"id": {"include"},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore(t),
}
includes, err := c.getExecutionIncludes(mockExecutionIncludesCacheFuncs(tt.args.cache))(tt.args.ctx, tt.args.id, 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.cache, tt.args.cache)
assert.Equal(t, tt.res.includes, includes)
}
})
}
}