mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-11 16:45:13 +00:00
feat: add action v2 execution on requests and responses (#7637)
* feat: add execution of targets to grpc calls * feat: add execution of targets to grpc calls * feat: add execution of targets to grpc calls * feat: add execution of targets to grpc calls * feat: add execution of targets to grpc calls * feat: add execution of targets to grpc calls * feat: add execution of targets to grpc calls * feat: split request and response logic to handle the different context information * feat: split request and response logic to handle the different context information * fix: integration test * fix: import alias * fix: refactor execution package * fix: refactor execution interceptor integration and unit tests * fix: refactor execution interceptor integration and unit tests * fix: refactor execution interceptor integration and unit tests * fix: refactor execution interceptor integration and unit tests * fix: refactor execution interceptor integration and unit tests * docs: basic documentation for executions and targets * fix: change order for interceptors * fix: merge back origin/main * fix: change target definition command and query side (#7735) * fix: change target definition command and query side * fix: correct refactoring name changes * fix: correct refactoring name changes * fix: changing execution defintion with target list and type * fix: changing execution definition with target list and type * fix: add back search queries for target and include * fix: projections change for execution with targets suffix table * fix: projections change for execution with targets suffix table * fix: projections change for execution with targets suffix table * fix: projections change for execution with targets suffix table * fix: projections change for execution with targets suffix table * fix: projections change for execution with targets suffix table * fix: projections change for execution with targets suffix table * docs: add example to actions v2 * docs: add example to actions v2 * fix: correct integration tests on query for executions * fix: add separate event for execution v2 as content changed * fix: add separate event for execution v2 as content changed * fix: added review comment changes * fix: added review comment changes * fix: added review comment changes --------- Co-authored-by: adlerhurst <[email protected]> * fix: added review comment changes * fix: added review comment changes * Update internal/api/grpc/server/middleware/execution_interceptor.go Co-authored-by: Silvan <[email protected]> * fix: added review comment changes * fix: added review comment changes * fix: added review comment changes * fix: added review comment changes * fix: added review comment changes * fix: added review comment changes --------- Co-authored-by: adlerhurst <[email protected]> Co-authored-by: Elio Bischof <[email protected]>
This commit is contained in:
co-authored by
adlerhurst
Elio Bischof
parent
7e345444bf
commit
1c5ecba42a
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/repository/execution"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
||||
)
|
||||
|
||||
@@ -33,46 +34,46 @@ func (s *Server) SetExecution(ctx context.Context, req *action.SetExecutionReque
|
||||
return nil, err
|
||||
}
|
||||
|
||||
targets := make([]*execution.Target, len(req.Targets))
|
||||
for i, target := range req.Targets {
|
||||
switch t := target.GetType().(type) {
|
||||
case *action.ExecutionTargetType_Include:
|
||||
include, err := conditionToInclude(t.Include)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
targets[i] = &execution.Target{Type: domain.ExecutionTargetTypeInclude, Target: include}
|
||||
case *action.ExecutionTargetType_Target:
|
||||
targets[i] = &execution.Target{Type: domain.ExecutionTargetTypeTarget, Target: t.Target}
|
||||
}
|
||||
}
|
||||
set := &command.SetExecution{
|
||||
Targets: req.GetTargets(),
|
||||
Includes: req.GetIncludes(),
|
||||
Targets: targets,
|
||||
}
|
||||
|
||||
var err error
|
||||
var details *domain.ObjectDetails
|
||||
switch t := req.GetCondition().GetConditionType().(type) {
|
||||
case *action.Condition_Request:
|
||||
cond := &command.ExecutionAPICondition{
|
||||
Method: t.Request.GetMethod(),
|
||||
Service: t.Request.GetService(),
|
||||
All: t.Request.GetAll(),
|
||||
}
|
||||
cond := executionConditionFromRequest(t.Request)
|
||||
details, err = s.command.SetExecutionRequest(ctx, cond, set, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *action.Condition_Response:
|
||||
cond := &command.ExecutionAPICondition{
|
||||
Method: t.Response.GetMethod(),
|
||||
Service: t.Response.GetService(),
|
||||
All: t.Response.GetAll(),
|
||||
}
|
||||
cond := executionConditionFromResponse(t.Response)
|
||||
details, err = s.command.SetExecutionResponse(ctx, cond, set, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *action.Condition_Event:
|
||||
cond := &command.ExecutionEventCondition{
|
||||
Event: t.Event.GetEvent(),
|
||||
Group: t.Event.GetGroup(),
|
||||
All: t.Event.GetAll(),
|
||||
}
|
||||
cond := executionConditionFromEvent(t.Event)
|
||||
details, err = s.command.SetExecutionEvent(ctx, cond, set, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *action.Condition_Function:
|
||||
details, err = s.command.SetExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function), set, authz.GetInstance(ctx).InstanceID())
|
||||
details, err = s.command.SetExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function.GetName()), set, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -82,6 +83,36 @@ func (s *Server) SetExecution(ctx context.Context, req *action.SetExecutionReque
|
||||
}, nil
|
||||
}
|
||||
|
||||
func conditionToInclude(cond *action.Condition) (string, error) {
|
||||
switch t := cond.GetConditionType().(type) {
|
||||
case *action.Condition_Request:
|
||||
cond := executionConditionFromRequest(t.Request)
|
||||
if err := cond.IsValid(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return cond.ID(domain.ExecutionTypeRequest), nil
|
||||
case *action.Condition_Response:
|
||||
cond := executionConditionFromResponse(t.Response)
|
||||
if err := cond.IsValid(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return cond.ID(domain.ExecutionTypeRequest), nil
|
||||
case *action.Condition_Event:
|
||||
cond := executionConditionFromEvent(t.Event)
|
||||
if err := cond.IsValid(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return cond.ID(), nil
|
||||
case *action.Condition_Function:
|
||||
cond := command.ExecutionFunctionCondition(t.Function.GetName())
|
||||
if err := cond.IsValid(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return cond.ID(), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteExecution(ctx context.Context, req *action.DeleteExecutionRequest) (*action.DeleteExecutionResponse, error) {
|
||||
if err := checkExecutionEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
@@ -91,37 +122,25 @@ func (s *Server) DeleteExecution(ctx context.Context, req *action.DeleteExecutio
|
||||
var details *domain.ObjectDetails
|
||||
switch t := req.GetCondition().GetConditionType().(type) {
|
||||
case *action.Condition_Request:
|
||||
cond := &command.ExecutionAPICondition{
|
||||
Method: t.Request.GetMethod(),
|
||||
Service: t.Request.GetService(),
|
||||
All: t.Request.GetAll(),
|
||||
}
|
||||
cond := executionConditionFromRequest(t.Request)
|
||||
details, err = s.command.DeleteExecutionRequest(ctx, cond, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *action.Condition_Response:
|
||||
cond := &command.ExecutionAPICondition{
|
||||
Method: t.Response.GetMethod(),
|
||||
Service: t.Response.GetService(),
|
||||
All: t.Response.GetAll(),
|
||||
}
|
||||
cond := executionConditionFromResponse(t.Response)
|
||||
details, err = s.command.DeleteExecutionResponse(ctx, cond, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *action.Condition_Event:
|
||||
cond := &command.ExecutionEventCondition{
|
||||
Event: t.Event.GetEvent(),
|
||||
Group: t.Event.GetGroup(),
|
||||
All: t.Event.GetAll(),
|
||||
}
|
||||
cond := executionConditionFromEvent(t.Event)
|
||||
details, err = s.command.DeleteExecutionEvent(ctx, cond, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *action.Condition_Function:
|
||||
details, err = s.command.DeleteExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function), authz.GetInstance(ctx).InstanceID())
|
||||
details, err = s.command.DeleteExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function.GetName()), authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -130,3 +149,27 @@ func (s *Server) DeleteExecution(ctx context.Context, req *action.DeleteExecutio
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func executionConditionFromRequest(request *action.RequestExecution) *command.ExecutionAPICondition {
|
||||
return &command.ExecutionAPICondition{
|
||||
Method: request.GetMethod(),
|
||||
Service: request.GetService(),
|
||||
All: request.GetAll(),
|
||||
}
|
||||
}
|
||||
|
||||
func executionConditionFromResponse(response *action.ResponseExecution) *command.ExecutionAPICondition {
|
||||
return &command.ExecutionAPICondition{
|
||||
Method: response.GetMethod(),
|
||||
Service: response.GetService(),
|
||||
All: response.GetAll(),
|
||||
}
|
||||
}
|
||||
|
||||
func executionConditionFromEvent(event *action.EventExecution) *command.ExecutionEventCondition {
|
||||
return &command.ExecutionEventCondition{
|
||||
Event: event.GetEvent(),
|
||||
Group: event.GetGroup(),
|
||||
All: event.GetAll(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,14 +9,23 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
||||
)
|
||||
|
||||
func executionTargetsSingleTarget(id string) []*action.ExecutionTargetType {
|
||||
return []*action.ExecutionTargetType{{Type: &action.ExecutionTargetType_Target{Target: id}}}
|
||||
}
|
||||
|
||||
func executionTargetsSingleInclude(include *action.Condition) []*action.ExecutionTargetType {
|
||||
return []*action.ExecutionTargetType{{Type: &action.ExecutionTargetType_Include{Include: include}}}
|
||||
}
|
||||
|
||||
func TestServer_SetExecution_Request(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
targetResp := Tester.CreateTarget(CTX, t)
|
||||
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -48,7 +57,7 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
Request: &action.RequestExecution{},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -65,7 +74,7 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -82,7 +91,7 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &object.Details{
|
||||
@@ -104,7 +113,7 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -121,7 +130,7 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &object.Details{
|
||||
@@ -143,7 +152,7 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &object.Details{
|
||||
@@ -163,27 +172,28 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertDetails(t, tt.want, got)
|
||||
|
||||
// cleanup to not impact other requests
|
||||
Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
|
||||
targetResp := Tester.CreateTarget(CTX, t)
|
||||
executionCond := "request"
|
||||
Tester.SetExecution(CTX, t,
|
||||
&action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_All{
|
||||
All: true,
|
||||
},
|
||||
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
executionCond := &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_All{
|
||||
All: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
[]string{targetResp.GetId()},
|
||||
[]string{},
|
||||
}
|
||||
Tester.SetExecution(CTX, t,
|
||||
executionCond,
|
||||
executionTargetsSingleTarget(targetResp.GetId()),
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
@@ -206,7 +216,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Includes: []string{executionCond},
|
||||
Targets: executionTargetsSingleInclude(executionCond),
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &object.Details{
|
||||
@@ -228,7 +238,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Includes: []string{executionCond},
|
||||
Targets: executionTargetsSingleInclude(executionCond),
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &object.Details{
|
||||
@@ -237,6 +247,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
/* circular
|
||||
{
|
||||
name: "all, ok",
|
||||
ctx: CTX,
|
||||
@@ -250,7 +261,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Includes: []string{executionCond},
|
||||
Targets: executionTargetsSingleInclude(executionCond),
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &object.Details{
|
||||
@@ -259,6 +270,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
*/
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -270,13 +282,16 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertDetails(t, tt.want, got)
|
||||
|
||||
// cleanup to not impact other requests
|
||||
Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_DeleteExecution_Request(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
targetResp := Tester.CreateTarget(CTX, t)
|
||||
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -332,7 +347,7 @@ func TestServer_DeleteExecution_Request(t *testing.T) {
|
||||
name: "method, ok",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
||||
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
|
||||
Tester.SetExecution(ctx, t, request.GetCondition(), executionTargetsSingleTarget(targetResp.GetId()))
|
||||
return nil
|
||||
},
|
||||
req: &action.DeleteExecutionRequest{
|
||||
@@ -373,7 +388,7 @@ func TestServer_DeleteExecution_Request(t *testing.T) {
|
||||
name: "service, ok",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
||||
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
|
||||
Tester.SetExecution(ctx, t, request.GetCondition(), executionTargetsSingleTarget(targetResp.GetId()))
|
||||
return nil
|
||||
},
|
||||
req: &action.DeleteExecutionRequest{
|
||||
@@ -398,7 +413,7 @@ func TestServer_DeleteExecution_Request(t *testing.T) {
|
||||
name: "all, ok",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
||||
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
|
||||
Tester.SetExecution(ctx, t, request.GetCondition(), executionTargetsSingleTarget(targetResp.GetId()))
|
||||
return nil
|
||||
},
|
||||
req: &action.DeleteExecutionRequest{
|
||||
@@ -441,7 +456,7 @@ func TestServer_DeleteExecution_Request(t *testing.T) {
|
||||
|
||||
func TestServer_SetExecution_Response(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
targetResp := Tester.CreateTarget(CTX, t)
|
||||
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -473,7 +488,7 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
Response: &action.ResponseExecution{},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -490,7 +505,7 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -507,7 +522,7 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &object.Details{
|
||||
@@ -529,7 +544,7 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -546,7 +561,7 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &object.Details{
|
||||
@@ -568,7 +583,7 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &object.Details{
|
||||
@@ -588,13 +603,16 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertDetails(t, tt.want, got)
|
||||
|
||||
// cleanup to not impact other requests
|
||||
Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_DeleteExecution_Response(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
targetResp := Tester.CreateTarget(CTX, t)
|
||||
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -652,7 +670,7 @@ func TestServer_DeleteExecution_Response(t *testing.T) {
|
||||
name: "method, ok",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
||||
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
|
||||
Tester.SetExecution(ctx, t, request.GetCondition(), executionTargetsSingleTarget(targetResp.GetId()))
|
||||
return nil
|
||||
},
|
||||
req: &action.DeleteExecutionRequest{
|
||||
@@ -693,7 +711,7 @@ func TestServer_DeleteExecution_Response(t *testing.T) {
|
||||
name: "service, ok",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
||||
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
|
||||
Tester.SetExecution(ctx, t, request.GetCondition(), executionTargetsSingleTarget(targetResp.GetId()))
|
||||
return nil
|
||||
},
|
||||
req: &action.DeleteExecutionRequest{
|
||||
@@ -718,7 +736,7 @@ func TestServer_DeleteExecution_Response(t *testing.T) {
|
||||
name: "all, ok",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
||||
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
|
||||
Tester.SetExecution(ctx, t, request.GetCondition(), executionTargetsSingleTarget(targetResp.GetId()))
|
||||
return nil
|
||||
},
|
||||
req: &action.DeleteExecutionRequest{
|
||||
@@ -761,7 +779,7 @@ func TestServer_DeleteExecution_Response(t *testing.T) {
|
||||
|
||||
func TestServer_SetExecution_Event(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
targetResp := Tester.CreateTarget(CTX, t)
|
||||
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -795,7 +813,7 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
||||
Event: &action.EventExecution{},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -833,7 +851,7 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &object.Details{
|
||||
@@ -876,7 +894,7 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &object.Details{
|
||||
@@ -898,7 +916,7 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &object.Details{
|
||||
@@ -918,13 +936,16 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertDetails(t, tt.want, got)
|
||||
|
||||
// cleanup to not impact other requests
|
||||
Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_DeleteExecution_Event(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
targetResp := Tester.CreateTarget(CTX, t)
|
||||
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -985,7 +1006,7 @@ func TestServer_DeleteExecution_Event(t *testing.T) {
|
||||
name: "event, ok",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
||||
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
|
||||
Tester.SetExecution(ctx, t, request.GetCondition(), executionTargetsSingleTarget(targetResp.GetId()))
|
||||
return nil
|
||||
},
|
||||
req: &action.DeleteExecutionRequest{
|
||||
@@ -1026,7 +1047,7 @@ func TestServer_DeleteExecution_Event(t *testing.T) {
|
||||
name: "group, ok",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
||||
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
|
||||
Tester.SetExecution(ctx, t, request.GetCondition(), executionTargetsSingleTarget(targetResp.GetId()))
|
||||
return nil
|
||||
},
|
||||
req: &action.DeleteExecutionRequest{
|
||||
@@ -1061,18 +1082,13 @@ func TestServer_DeleteExecution_Event(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &action.DeleteExecutionResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "all, ok",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
||||
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
|
||||
Tester.SetExecution(ctx, t, request.GetCondition(), executionTargetsSingleTarget(targetResp.GetId()))
|
||||
return nil
|
||||
},
|
||||
req: &action.DeleteExecutionRequest{
|
||||
@@ -1115,7 +1131,7 @@ func TestServer_DeleteExecution_Event(t *testing.T) {
|
||||
|
||||
func TestServer_SetExecution_Function(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
targetResp := Tester.CreateTarget(CTX, t)
|
||||
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -1147,7 +1163,7 @@ func TestServer_SetExecution_Function(t *testing.T) {
|
||||
Response: &action.ResponseExecution{},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -1157,10 +1173,10 @@ func TestServer_SetExecution_Function(t *testing.T) {
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Function{
|
||||
Function: "xxx",
|
||||
Function: &action.FunctionExecution{Name: "xxx"},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -1170,10 +1186,10 @@ func TestServer_SetExecution_Function(t *testing.T) {
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Function{
|
||||
Function: "Action.Flow.Type.ExternalAuthentication.Action.TriggerType.PostAuthentication",
|
||||
Function: &action.FunctionExecution{Name: "Action.Flow.Type.ExternalAuthentication.Action.TriggerType.PostAuthentication"},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &object.Details{
|
||||
@@ -1193,13 +1209,16 @@ func TestServer_SetExecution_Function(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertDetails(t, tt.want, got)
|
||||
|
||||
// cleanup to not impact other requests
|
||||
Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_DeleteExecution_Function(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
targetResp := Tester.CreateTarget(CTX, t)
|
||||
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -1243,7 +1262,7 @@ func TestServer_DeleteExecution_Function(t *testing.T) {
|
||||
req: &action.DeleteExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Function{
|
||||
Function: "xxx",
|
||||
Function: &action.FunctionExecution{Name: "xxx"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1253,13 +1272,13 @@ func TestServer_DeleteExecution_Function(t *testing.T) {
|
||||
name: "function, ok",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
||||
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
|
||||
Tester.SetExecution(ctx, t, request.GetCondition(), executionTargetsSingleTarget(targetResp.GetId()))
|
||||
return nil
|
||||
},
|
||||
req: &action.DeleteExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Function{
|
||||
Function: "Action.Flow.Type.ExternalAuthentication.Action.TriggerType.PostAuthentication",
|
||||
Function: &action.FunctionExecution{Name: "Action.Flow.Type.ExternalAuthentication.Action.TriggerType.PostAuthentication"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
//go:build integration
|
||||
|
||||
package action_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server/middleware"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
||||
)
|
||||
|
||||
func TestServer_ExecutionTarget(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
|
||||
fullMethod := "/zitadel.action.v3alpha.ActionService/GetTargetByID"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
dep func(context.Context, *action.GetTargetByIDRequest, *action.GetTargetByIDResponse) (func(), error)
|
||||
clean func(context.Context)
|
||||
req *action.GetTargetByIDRequest
|
||||
want *action.GetTargetByIDResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "GetTargetByID, request and response, ok",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) (func(), error) {
|
||||
|
||||
instanceID := Tester.Instance.InstanceID()
|
||||
orgID := Tester.Organisation.ID
|
||||
projectID := ""
|
||||
userID := Tester.Users.Get(integration.FirstInstanceUsersKey, integration.IAMOwner).ID
|
||||
|
||||
// create target for target changes
|
||||
targetCreatedName := fmt.Sprint("GetTargetByID", time.Now().UnixNano()+1)
|
||||
targetCreatedURL := "https://nonexistent"
|
||||
|
||||
targetCreated := Tester.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, domain.TargetTypeCall, false)
|
||||
|
||||
// request received by target
|
||||
wantRequest := &middleware.ContextInfoRequest{FullMethod: fullMethod, InstanceID: instanceID, OrgID: orgID, ProjectID: projectID, UserID: userID, Request: request}
|
||||
changedRequest := &action.GetTargetByIDRequest{TargetId: targetCreated.GetId()}
|
||||
// replace original request with different targetID
|
||||
urlRequest, closeRequest := testServerCall(wantRequest, 0, http.StatusOK, changedRequest)
|
||||
targetRequest := Tester.CreateTarget(ctx, t, "", urlRequest, domain.TargetTypeCall, false)
|
||||
Tester.SetExecution(ctx, t, conditionRequestFullMethod(fullMethod), executionTargetsSingleTarget(targetRequest.GetId()))
|
||||
// GetTargetByID with used target
|
||||
request.TargetId = targetRequest.GetId()
|
||||
|
||||
// expected response from the GetTargetByID
|
||||
expectedResponse := &action.GetTargetByIDResponse{
|
||||
Target: &action.Target{
|
||||
TargetId: targetCreated.GetId(),
|
||||
Details: targetCreated.GetDetails(),
|
||||
Name: targetCreatedName,
|
||||
Endpoint: targetCreatedURL,
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
}
|
||||
// has to be set separately because of the pointers
|
||||
response.Target = &action.Target{
|
||||
TargetId: targetCreated.GetId(),
|
||||
Details: targetCreated.GetDetails(),
|
||||
Name: targetCreatedName,
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
}
|
||||
|
||||
// content for partial update
|
||||
changedResponse := &action.GetTargetByIDResponse{
|
||||
Target: &action.Target{
|
||||
TargetId: "changed",
|
||||
},
|
||||
}
|
||||
// change partial updated content on returned response
|
||||
response.Target.TargetId = changedResponse.Target.TargetId
|
||||
|
||||
// response received by target
|
||||
wantResponse := &middleware.ContextInfoResponse{
|
||||
FullMethod: fullMethod,
|
||||
InstanceID: instanceID,
|
||||
OrgID: orgID,
|
||||
ProjectID: projectID,
|
||||
UserID: userID,
|
||||
Request: changedRequest,
|
||||
Response: expectedResponse,
|
||||
}
|
||||
// after request with different targetID, return changed response
|
||||
targetResponseURL, closeResponse := testServerCall(wantResponse, 0, http.StatusOK, changedResponse)
|
||||
targetResponse := Tester.CreateTarget(ctx, t, "", targetResponseURL, domain.TargetTypeCall, false)
|
||||
Tester.SetExecution(ctx, t, conditionResponseFullMethod(fullMethod), executionTargetsSingleTarget(targetResponse.GetId()))
|
||||
|
||||
return func() {
|
||||
closeRequest()
|
||||
closeResponse()
|
||||
}, nil
|
||||
},
|
||||
clean: func(ctx context.Context) {
|
||||
Tester.DeleteExecution(ctx, t, conditionRequestFullMethod(fullMethod))
|
||||
Tester.DeleteExecution(ctx, t, conditionResponseFullMethod(fullMethod))
|
||||
},
|
||||
req: &action.GetTargetByIDRequest{},
|
||||
want: &action.GetTargetByIDResponse{},
|
||||
},
|
||||
/*{
|
||||
name: "GetTargetByID, request, interrupt",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) (func(), error) {
|
||||
|
||||
fullMethod := "/zitadel.action.v3alpha.ActionService/GetTargetByID"
|
||||
instanceID := Tester.Instance.InstanceID()
|
||||
orgID := Tester.Organisation.ID
|
||||
projectID := ""
|
||||
userID := Tester.Users.Get(integration.FirstInstanceUsersKey, integration.IAMOwner).ID
|
||||
|
||||
// request received by target
|
||||
wantRequest := &middleware.ContextInfoRequest{FullMethod: fullMethod, InstanceID: instanceID, OrgID: orgID, ProjectID: projectID, UserID: userID, Request: request}
|
||||
urlRequest, closeRequest := testServerCall(wantRequest, 0, http.StatusInternalServerError, &action.GetTargetByIDRequest{TargetId: "notchanged"})
|
||||
|
||||
targetRequest := Tester.CreateTarget(ctx, t, "", urlRequest, domain.TargetTypeCall, true)
|
||||
Tester.SetExecution(ctx, t, conditionRequestFullMethod(fullMethod), executionTargetsSingleTarget(targetRequest.GetId()))
|
||||
// GetTargetByID with used target
|
||||
request.TargetId = targetRequest.GetId()
|
||||
|
||||
return func() {
|
||||
closeRequest()
|
||||
}, nil
|
||||
},
|
||||
clean: func(ctx context.Context) {
|
||||
Tester.DeleteExecution(ctx, t, conditionRequestFullMethod(fullMethod))
|
||||
},
|
||||
req: &action.GetTargetByIDRequest{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "GetTargetByID, response, interrupt",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) (func(), error) {
|
||||
|
||||
fullMethod := "/zitadel.action.v3alpha.ActionService/GetTargetByID"
|
||||
instanceID := Tester.Instance.InstanceID()
|
||||
orgID := Tester.Organisation.ID
|
||||
projectID := ""
|
||||
userID := Tester.Users.Get(integration.FirstInstanceUsersKey, integration.IAMOwner).ID
|
||||
|
||||
// create target for target changes
|
||||
targetCreatedName := fmt.Sprint("GetTargetByID", time.Now().UnixNano()+1)
|
||||
targetCreatedURL := "https://nonexistent"
|
||||
|
||||
targetCreated := Tester.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, domain.TargetTypeCall, false)
|
||||
|
||||
// GetTargetByID with used target
|
||||
request.TargetId = targetCreated.GetId()
|
||||
|
||||
// expected response from the GetTargetByID
|
||||
expectedResponse := &action.GetTargetByIDResponse{
|
||||
Target: &action.Target{
|
||||
TargetId: targetCreated.GetId(),
|
||||
Details: targetCreated.GetDetails(),
|
||||
Name: targetCreatedName,
|
||||
Endpoint: targetCreatedURL,
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
}
|
||||
|
||||
// content for partial update
|
||||
changedResponse := &action.GetTargetByIDResponse{
|
||||
Target: &action.Target{
|
||||
TargetId: "changed",
|
||||
},
|
||||
}
|
||||
|
||||
// response received by target
|
||||
wantResponse := &middleware.ContextInfoResponse{
|
||||
FullMethod: fullMethod,
|
||||
InstanceID: instanceID,
|
||||
OrgID: orgID,
|
||||
ProjectID: projectID,
|
||||
UserID: userID,
|
||||
Request: request,
|
||||
Response: expectedResponse,
|
||||
}
|
||||
// after request with different targetID, return changed response
|
||||
targetResponseURL, closeResponse := testServerCall(wantResponse, 0, http.StatusInternalServerError, changedResponse)
|
||||
targetResponse := Tester.CreateTarget(ctx, t, "", targetResponseURL, domain.TargetTypeCall, true)
|
||||
Tester.SetExecution(ctx, t, conditionResponseFullMethod(fullMethod), executionTargetsSingleTarget(targetResponse.GetId()))
|
||||
|
||||
return func() {
|
||||
closeResponse()
|
||||
}, nil
|
||||
},
|
||||
clean: func(ctx context.Context) {
|
||||
Tester.DeleteExecution(ctx, t, conditionResponseFullMethod(fullMethod))
|
||||
},
|
||||
req: &action.GetTargetByIDRequest{},
|
||||
wantErr: true,
|
||||
},*/
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.dep != nil {
|
||||
close, err := tt.dep(tt.ctx, tt.req, tt.want)
|
||||
require.NoError(t, err)
|
||||
defer close()
|
||||
}
|
||||
|
||||
got, err := Client.GetTargetByID(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertDetails(t, tt.want.GetTarget(), got.GetTarget())
|
||||
|
||||
assert.Equal(t, tt.want.Target.TargetId, got.Target.TargetId)
|
||||
|
||||
if tt.clean != nil {
|
||||
tt.clean(tt.ctx)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func conditionRequestFullMethod(fullMethod string) *action.Condition {
|
||||
return &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Method{
|
||||
Method: fullMethod,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func conditionResponseFullMethod(fullMethod string) *action.Condition {
|
||||
return &action.Condition{
|
||||
ConditionType: &action.Condition_Response{
|
||||
Response: &action.ResponseExecution{
|
||||
Condition: &action.ResponseExecution_Method{
|
||||
Method: fullMethod,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func testServerCall(
|
||||
reqBody interface{},
|
||||
sleep time.Duration,
|
||||
statusCode int,
|
||||
respBody interface{},
|
||||
) (string, func()) {
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
http.Error(w, "error, marshall: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
sentBody, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "error, read body: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(data, sentBody) {
|
||||
http.Error(w, "error, equal:\n"+string(data)+"\nsent:\n"+string(sentBody), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if statusCode != http.StatusOK {
|
||||
http.Error(w, "error, statusCode", statusCode)
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(sleep)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
resp, err := json.Marshal(respBody)
|
||||
if err != nil {
|
||||
http.Error(w, "error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if _, err := io.WriteString(w, string(resp)); err != nil {
|
||||
http.Error(w, "error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(handler))
|
||||
|
||||
return server.URL, server.Close
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
@@ -67,8 +68,6 @@ func targetFieldNameToSortingColumn(field action.TargetFieldName) query.Column {
|
||||
return query.TargetColumnURL
|
||||
case action.TargetFieldName_FIELD_NAME_TIMEOUT:
|
||||
return query.TargetColumnTimeout
|
||||
case action.TargetFieldName_FIELD_NAME_ASYNC:
|
||||
return query.TargetColumnAsync
|
||||
case action.TargetFieldName_FIELD_NAME_INTERRUPT_ON_ERROR:
|
||||
return query.TargetColumnInterruptOnError
|
||||
default:
|
||||
@@ -134,19 +133,16 @@ func targetToPb(t *query.Target) *action.Target {
|
||||
TargetId: t.ID,
|
||||
Name: t.Name,
|
||||
Timeout: durationpb.New(t.Timeout),
|
||||
}
|
||||
if t.Async {
|
||||
target.ExecutionType = &action.Target_IsAsync{IsAsync: t.Async}
|
||||
}
|
||||
if t.InterruptOnError {
|
||||
target.ExecutionType = &action.Target_InterruptOnError{InterruptOnError: t.InterruptOnError}
|
||||
Endpoint: t.Endpoint,
|
||||
}
|
||||
|
||||
switch t.TargetType {
|
||||
case domain.TargetTypeWebhook:
|
||||
target.TargetType = &action.Target_RestWebhook{RestWebhook: &action.SetRESTWebhook{Url: t.URL}}
|
||||
case domain.TargetTypeRequestResponse:
|
||||
target.TargetType = &action.Target_RestRequestResponse{RestRequestResponse: &action.SetRESTRequestResponse{Url: t.URL}}
|
||||
target.TargetType = &action.Target_RestWebhook{RestWebhook: &action.SetRESTWebhook{InterruptOnError: t.InterruptOnError}}
|
||||
case domain.TargetTypeCall:
|
||||
target.TargetType = &action.Target_RestCall{RestCall: &action.SetRESTCall{InterruptOnError: t.InterruptOnError}}
|
||||
case domain.TargetTypeAsync:
|
||||
target.TargetType = &action.Target_RestAsync{RestAsync: &action.SetRESTAsync{}}
|
||||
default:
|
||||
target.TargetType = nil
|
||||
}
|
||||
@@ -205,10 +201,14 @@ func executionQueryToQuery(searchQuery *action.SearchQuery) (query.SearchQuery,
|
||||
return inConditionsQueryToQuery(q.InConditionsQuery)
|
||||
case *action.SearchQuery_ExecutionTypeQuery:
|
||||
return executionTypeToQuery(q.ExecutionTypeQuery)
|
||||
case *action.SearchQuery_TargetQuery:
|
||||
return query.NewExecutionTargetSearchQuery(q.TargetQuery.GetTargetId())
|
||||
case *action.SearchQuery_IncludeQuery:
|
||||
return query.NewExecutionIncludeSearchQuery(q.IncludeQuery.GetInclude())
|
||||
include, err := conditionToInclude(q.IncludeQuery.GetInclude())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return query.NewIncludeSearchQuery(include)
|
||||
case *action.SearchQuery_TargetQuery:
|
||||
return query.NewTargetSearchQuery(q.TargetQuery.GetTargetId())
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
|
||||
}
|
||||
@@ -267,7 +267,7 @@ func conditionToID(q *action.Condition) (string, error) {
|
||||
}
|
||||
return cond.ID(), nil
|
||||
case *action.Condition_Function:
|
||||
return t.Function, nil
|
||||
return command.ExecutionFunctionCondition(t.Function.GetName()).ID(), nil
|
||||
default:
|
||||
return "", zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
|
||||
}
|
||||
@@ -282,17 +282,83 @@ func executionsToPb(executions []*query.Execution) []*action.Execution {
|
||||
}
|
||||
|
||||
func executionToPb(e *query.Execution) *action.Execution {
|
||||
var targets, includes []string
|
||||
if len(e.Targets) > 0 {
|
||||
targets = e.Targets
|
||||
}
|
||||
if len(e.Includes) > 0 {
|
||||
includes = e.Includes
|
||||
targets := make([]*action.ExecutionTargetType, len(e.Targets))
|
||||
for i := range e.Targets {
|
||||
switch e.Targets[i].Type {
|
||||
case domain.ExecutionTargetTypeInclude:
|
||||
targets[i] = &action.ExecutionTargetType{Type: &action.ExecutionTargetType_Include{Include: executionIDToCondition(e.Targets[i].Target)}}
|
||||
case domain.ExecutionTargetTypeTarget:
|
||||
targets[i] = &action.ExecutionTargetType{Type: &action.ExecutionTargetType_Target{Target: e.Targets[i].Target}}
|
||||
case domain.ExecutionTargetTypeUnspecified:
|
||||
continue
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return &action.Execution{
|
||||
Details: object.DomainToDetailsPb(&e.ObjectDetails),
|
||||
ExecutionId: e.ID,
|
||||
Targets: targets,
|
||||
Includes: includes,
|
||||
Details: object.DomainToDetailsPb(&e.ObjectDetails),
|
||||
Condition: executionIDToCondition(e.ID),
|
||||
Targets: targets,
|
||||
}
|
||||
}
|
||||
|
||||
func executionIDToCondition(include string) *action.Condition {
|
||||
if strings.HasPrefix(include, domain.ExecutionTypeRequest.String()) {
|
||||
return includeRequestToCondition(strings.TrimPrefix(include, domain.ExecutionTypeRequest.String()))
|
||||
}
|
||||
if strings.HasPrefix(include, domain.ExecutionTypeResponse.String()) {
|
||||
return includeResponseToCondition(strings.TrimPrefix(include, domain.ExecutionTypeResponse.String()))
|
||||
}
|
||||
if strings.HasPrefix(include, domain.ExecutionTypeEvent.String()) {
|
||||
return includeEventToCondition(strings.TrimPrefix(include, domain.ExecutionTypeEvent.String()))
|
||||
}
|
||||
if strings.HasPrefix(include, domain.ExecutionTypeFunction.String()) {
|
||||
return includeFunctionToCondition(strings.TrimPrefix(include, domain.ExecutionTypeFunction.String()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func includeRequestToCondition(id string) *action.Condition {
|
||||
switch strings.Count(id, "/") {
|
||||
case 2:
|
||||
return &action.Condition{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Method{Method: id}}}}
|
||||
case 1:
|
||||
return &action.Condition{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Service{Service: strings.TrimPrefix(id, "/")}}}}
|
||||
case 0:
|
||||
return &action.Condition{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_All{All: true}}}}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
func includeResponseToCondition(id string) *action.Condition {
|
||||
switch strings.Count(id, "/") {
|
||||
case 2:
|
||||
return &action.Condition{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Method{Method: id}}}}
|
||||
case 1:
|
||||
return &action.Condition{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Service{Service: strings.TrimPrefix(id, "/")}}}}
|
||||
case 0:
|
||||
return &action.Condition{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_All{All: true}}}}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func includeEventToCondition(id string) *action.Condition {
|
||||
switch strings.Count(id, "/") {
|
||||
case 1:
|
||||
if strings.HasSuffix(id, command.EventGroupSuffix) {
|
||||
return &action.Condition{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_Group{Group: strings.TrimSuffix(strings.TrimPrefix(id, "/"), command.EventGroupSuffix)}}}}
|
||||
} else {
|
||||
return &action.Condition{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_Event{Event: strings.TrimPrefix(id, "/")}}}}
|
||||
}
|
||||
case 0:
|
||||
return &action.Condition{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_All{All: true}}}}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func includeFunctionToCondition(id string) *action.Condition {
|
||||
return &action.Condition{ConditionType: &action.Condition_Function{Function: &action.FunctionExecution{Name: strings.TrimPrefix(id, "/")}}}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package action_test
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -12,6 +13,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
||||
@@ -52,7 +54,7 @@ func TestServer_GetTargetByID(t *testing.T) {
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) error {
|
||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||
resp := Tester.CreateTargetWithNameAndType(ctx, t, name, false, false)
|
||||
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false)
|
||||
request.TargetId = resp.GetId()
|
||||
|
||||
response.Target.TargetId = resp.GetId()
|
||||
@@ -69,10 +71,9 @@ func TestServer_GetTargetByID(t *testing.T) {
|
||||
Details: &object.Details{
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
Url: "https://example.com",
|
||||
},
|
||||
RestWebhook: &action.SetRESTWebhook{},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
@@ -84,7 +85,7 @@ func TestServer_GetTargetByID(t *testing.T) {
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) error {
|
||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||
resp := Tester.CreateTargetWithNameAndType(ctx, t, name, true, false)
|
||||
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeAsync, false)
|
||||
request.TargetId = resp.GetId()
|
||||
|
||||
response.Target.TargetId = resp.GetId()
|
||||
@@ -101,23 +102,21 @@ func TestServer_GetTargetByID(t *testing.T) {
|
||||
Details: &object.Details{
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
Url: "https://example.com",
|
||||
},
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestAsync{
|
||||
RestAsync: &action.SetRESTAsync{},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
ExecutionType: &action.Target_IsAsync{IsAsync: true},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get, interruptOnError, ok",
|
||||
name: "get, webhook interruptOnError, ok",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) error {
|
||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||
resp := Tester.CreateTargetWithNameAndType(ctx, t, name, false, true)
|
||||
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, true)
|
||||
request.TargetId = resp.GetId()
|
||||
|
||||
response.Target.TargetId = resp.GetId()
|
||||
@@ -134,13 +133,79 @@ func TestServer_GetTargetByID(t *testing.T) {
|
||||
Details: &object.Details{
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
Url: "https://example.com",
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
ExecutionType: &action.Target_InterruptOnError{InterruptOnError: true},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get, call, ok",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) error {
|
||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeCall, false)
|
||||
request.TargetId = resp.GetId()
|
||||
|
||||
response.Target.TargetId = resp.GetId()
|
||||
response.Target.Name = name
|
||||
response.Target.Details.ResourceOwner = resp.GetDetails().GetResourceOwner()
|
||||
response.Target.Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
||||
response.Target.Details.Sequence = resp.GetDetails().GetSequence()
|
||||
return nil
|
||||
},
|
||||
req: &action.GetTargetByIDRequest{},
|
||||
},
|
||||
want: &action.GetTargetByIDResponse{
|
||||
Target: &action.Target{
|
||||
Details: &object.Details{
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get, call interruptOnError, ok",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) error {
|
||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeCall, true)
|
||||
request.TargetId = resp.GetId()
|
||||
|
||||
response.Target.TargetId = resp.GetId()
|
||||
response.Target.Name = name
|
||||
response.Target.Details.ResourceOwner = resp.GetDetails().GetResourceOwner()
|
||||
response.Target.Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
||||
response.Target.Details.Sequence = resp.GetDetails().GetSequence()
|
||||
return nil
|
||||
},
|
||||
req: &action.GetTargetByIDRequest{},
|
||||
},
|
||||
want: &action.GetTargetByIDResponse{
|
||||
Target: &action.Target{
|
||||
Details: &object.Details{
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -163,15 +228,11 @@ func TestServer_GetTargetByID(t *testing.T) {
|
||||
assert.Error(ttt, getErr, "Error: "+getErr.Error())
|
||||
} else {
|
||||
assert.NoError(ttt, getErr)
|
||||
}
|
||||
if getErr != nil {
|
||||
fmt.Println("Error: " + getErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
integration.AssertDetails(t, tt.want.GetTarget(), got.GetTarget())
|
||||
integration.AssertDetails(t, tt.want.GetTarget(), got.GetTarget())
|
||||
|
||||
assert.Equal(t, tt.want.Target, got.Target)
|
||||
assert.Equal(t, tt.want.Target, got.Target)
|
||||
}
|
||||
|
||||
}, retryDuration, time.Millisecond*100, "timeout waiting for expected execution result")
|
||||
})
|
||||
@@ -227,14 +288,14 @@ func TestServer_ListTargets(t *testing.T) {
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.ListTargetsRequest, response *action.ListTargetsResponse) error {
|
||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||
resp := Tester.CreateTargetWithNameAndType(ctx, t, name, false, false)
|
||||
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false)
|
||||
request.Queries[0].Query = &action.TargetSearchQuery_InTargetIdsQuery{
|
||||
InTargetIdsQuery: &action.InTargetIDsQuery{
|
||||
TargetIds: []string{resp.GetId()},
|
||||
},
|
||||
}
|
||||
response.Details.Timestamp = resp.GetDetails().GetChangeDate()
|
||||
response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
|
||||
//response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
|
||||
|
||||
response.Result[0].Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
||||
response.Result[0].Details.Sequence = resp.GetDetails().GetSequence()
|
||||
@@ -255,9 +316,10 @@ func TestServer_ListTargets(t *testing.T) {
|
||||
Details: &object.Details{
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
Url: "https://example.com",
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
@@ -270,7 +332,7 @@ func TestServer_ListTargets(t *testing.T) {
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.ListTargetsRequest, response *action.ListTargetsResponse) error {
|
||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||
resp := Tester.CreateTargetWithNameAndType(ctx, t, name, false, false)
|
||||
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false)
|
||||
request.Queries[0].Query = &action.TargetSearchQuery_TargetNameQuery{
|
||||
TargetNameQuery: &action.TargetNameQuery{
|
||||
TargetName: name,
|
||||
@@ -298,9 +360,10 @@ func TestServer_ListTargets(t *testing.T) {
|
||||
Details: &object.Details{
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
Url: "https://example.com",
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
@@ -316,9 +379,9 @@ func TestServer_ListTargets(t *testing.T) {
|
||||
name1 := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||
name2 := fmt.Sprint(time.Now().UnixNano() + 3)
|
||||
name3 := fmt.Sprint(time.Now().UnixNano() + 5)
|
||||
resp1 := Tester.CreateTargetWithNameAndType(ctx, t, name1, false, false)
|
||||
resp2 := Tester.CreateTargetWithNameAndType(ctx, t, name2, true, false)
|
||||
resp3 := Tester.CreateTargetWithNameAndType(ctx, t, name3, false, true)
|
||||
resp1 := Tester.CreateTarget(ctx, t, name1, "https://example.com", domain.TargetTypeWebhook, false)
|
||||
resp2 := Tester.CreateTarget(ctx, t, name2, "https://example.com", domain.TargetTypeCall, true)
|
||||
resp3 := Tester.CreateTarget(ctx, t, name3, "https://example.com", domain.TargetTypeAsync, false)
|
||||
request.Queries[0].Query = &action.TargetSearchQuery_InTargetIdsQuery{
|
||||
InTargetIdsQuery: &action.InTargetIDsQuery{
|
||||
TargetIds: []string{resp1.GetId(), resp2.GetId(), resp3.GetId()},
|
||||
@@ -354,9 +417,10 @@ func TestServer_ListTargets(t *testing.T) {
|
||||
Details: &object.Details{
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
Url: "https://example.com",
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
@@ -365,25 +429,23 @@ func TestServer_ListTargets(t *testing.T) {
|
||||
Details: &object.Details{
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
Url: "https://example.com",
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
ExecutionType: &action.Target_IsAsync{IsAsync: true},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
{
|
||||
Details: &object.Details{
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
Url: "https://example.com",
|
||||
},
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestAsync{
|
||||
RestAsync: &action.SetRESTAsync{},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
ExecutionType: &action.Target_InterruptOnError{InterruptOnError: true},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -422,9 +484,9 @@ func TestServer_ListTargets(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ListExecutions_Request(t *testing.T) {
|
||||
func TestServer_ListExecutions(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
targetResp := Tester.CreateTarget(CTX, t)
|
||||
targetResp := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -446,17 +508,20 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "list single condition",
|
||||
name: "list request single condition",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error {
|
||||
resp := Tester.SetExecution(ctx, t, request.Queries[0].GetInConditionsQuery().GetConditions()[0], []string{targetResp.GetId()}, []string{})
|
||||
cond := request.Queries[0].GetInConditionsQuery().GetConditions()[0]
|
||||
resp := Tester.SetExecution(ctx, t, cond, executionTargetsSingleTarget(targetResp.GetId()))
|
||||
|
||||
response.Details.Timestamp = resp.GetDetails().GetChangeDate()
|
||||
response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
|
||||
// response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
|
||||
|
||||
// Set expected response with used values for SetExecution
|
||||
response.Result[0].Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
||||
response.Result[0].Details.Sequence = resp.GetDetails().GetSequence()
|
||||
response.Result[0].Condition = cond
|
||||
return nil
|
||||
},
|
||||
req: &action.ListExecutionsRequest{
|
||||
@@ -471,8 +536,7 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -487,18 +551,26 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
||||
Details: &object.Details{
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
ExecutionId: "request./zitadel.session.v2beta.SessionService/GetSession",
|
||||
Targets: []string{targetResp.GetId()},
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Method{
|
||||
Method: "/zitadel.session.v2beta.SessionService/GetSession",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single target",
|
||||
name: "list request single target",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error {
|
||||
target := Tester.CreateTarget(ctx, t)
|
||||
target := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false)
|
||||
// add target as query to the request
|
||||
request.Queries[0] = &action.SearchQuery{
|
||||
Query: &action.SearchQuery_TargetQuery{
|
||||
@@ -507,7 +579,7 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
resp := Tester.SetExecution(ctx, t, &action.Condition{
|
||||
cond := &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Method{
|
||||
@@ -515,14 +587,17 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
}, []string{target.GetId()}, []string{})
|
||||
}
|
||||
targets := executionTargetsSingleTarget(target.GetId())
|
||||
resp := Tester.SetExecution(ctx, t, cond, targets)
|
||||
|
||||
response.Details.Timestamp = resp.GetDetails().GetChangeDate()
|
||||
response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
|
||||
|
||||
response.Result[0].Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
||||
response.Result[0].Details.Sequence = resp.GetDetails().GetSequence()
|
||||
response.Result[0].Targets[0] = target.GetId()
|
||||
response.Result[0].Condition = cond
|
||||
response.Result[0].Targets = targets
|
||||
return nil
|
||||
},
|
||||
req: &action.ListExecutionsRequest{
|
||||
@@ -538,17 +613,17 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
||||
Details: &object.Details{
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
ExecutionId: "request./zitadel.management.v1.ManagementService/UpdateAction",
|
||||
Targets: []string{""},
|
||||
Condition: &action.Condition{},
|
||||
Targets: executionTargetsSingleTarget(""),
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "list single include",
|
||||
name: "list request single include",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error {
|
||||
Tester.SetExecution(ctx, t, &action.Condition{
|
||||
cond := &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Method{
|
||||
@@ -556,8 +631,11 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
}, []string{targetResp.GetId()}, []string{})
|
||||
resp2 := Tester.SetExecution(ctx, t, &action.Condition{
|
||||
}
|
||||
Tester.SetExecution(ctx, t, cond, executionTargetsSingleTarget(targetResp.GetId()))
|
||||
request.Queries[0].GetIncludeQuery().Include = cond
|
||||
|
||||
includeCond := &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Method{
|
||||
@@ -565,19 +643,23 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
}, []string{}, []string{"request./zitadel.management.v1.ManagementService/GetAction"})
|
||||
}
|
||||
includeTargets := executionTargetsSingleInclude(cond)
|
||||
resp2 := Tester.SetExecution(ctx, t, includeCond, includeTargets)
|
||||
|
||||
response.Details.Timestamp = resp2.GetDetails().GetChangeDate()
|
||||
response.Details.ProcessedSequence = resp2.GetDetails().GetSequence()
|
||||
|
||||
response.Result[0].Details.ChangeDate = resp2.GetDetails().GetChangeDate()
|
||||
response.Result[0].Details.Sequence = resp2.GetDetails().GetSequence()
|
||||
response.Result[0].Condition = includeCond
|
||||
response.Result[0].Targets = includeTargets
|
||||
return nil
|
||||
},
|
||||
req: &action.ListExecutionsRequest{
|
||||
Queries: []*action.SearchQuery{{
|
||||
Query: &action.SearchQuery_IncludeQuery{
|
||||
IncludeQuery: &action.IncludeQuery{Include: "request./zitadel.management.v1.ManagementService/GetAction"},
|
||||
IncludeQuery: &action.IncludeQuery{},
|
||||
},
|
||||
}},
|
||||
},
|
||||
@@ -591,8 +673,6 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
||||
Details: &object.Details{
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
ExecutionId: "request./zitadel.management.v1.ManagementService/ListActions",
|
||||
Includes: []string{"request./zitadel.management.v1.ManagementService/GetAction"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -603,19 +683,32 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error {
|
||||
|
||||
resp1 := Tester.SetExecution(ctx, t, request.Queries[0].GetInConditionsQuery().GetConditions()[0], []string{targetResp.GetId()}, []string{})
|
||||
cond1 := request.Queries[0].GetInConditionsQuery().GetConditions()[0]
|
||||
targets1 := executionTargetsSingleTarget(targetResp.GetId())
|
||||
resp1 := Tester.SetExecution(ctx, t, cond1, targets1)
|
||||
response.Result[0].Details.ChangeDate = resp1.GetDetails().GetChangeDate()
|
||||
response.Result[0].Details.Sequence = resp1.GetDetails().GetSequence()
|
||||
response.Result[0].Condition = cond1
|
||||
response.Result[0].Targets = targets1
|
||||
|
||||
resp2 := Tester.SetExecution(ctx, t, request.Queries[0].GetInConditionsQuery().GetConditions()[1], []string{targetResp.GetId()}, []string{})
|
||||
cond2 := request.Queries[0].GetInConditionsQuery().GetConditions()[1]
|
||||
targets2 := executionTargetsSingleTarget(targetResp.GetId())
|
||||
resp2 := Tester.SetExecution(ctx, t, cond2, targets2)
|
||||
response.Result[1].Details.ChangeDate = resp2.GetDetails().GetChangeDate()
|
||||
response.Result[1].Details.Sequence = resp2.GetDetails().GetSequence()
|
||||
response.Result[1].Condition = cond2
|
||||
response.Result[1].Targets = targets2
|
||||
|
||||
resp3 := Tester.SetExecution(ctx, t, request.Queries[0].GetInConditionsQuery().GetConditions()[2], []string{targetResp.GetId()}, []string{})
|
||||
response.Details.Timestamp = resp3.GetDetails().GetChangeDate()
|
||||
response.Details.ProcessedSequence = resp3.GetDetails().GetSequence()
|
||||
cond3 := request.Queries[0].GetInConditionsQuery().GetConditions()[2]
|
||||
targets3 := executionTargetsSingleTarget(targetResp.GetId())
|
||||
resp3 := Tester.SetExecution(ctx, t, cond3, targets3)
|
||||
response.Result[2].Details.ChangeDate = resp3.GetDetails().GetChangeDate()
|
||||
response.Result[2].Details.Sequence = resp3.GetDetails().GetSequence()
|
||||
response.Result[2].Condition = cond3
|
||||
response.Result[2].Targets = targets3
|
||||
|
||||
response.Details.Timestamp = resp3.GetDetails().GetChangeDate()
|
||||
response.Details.ProcessedSequence = resp3.GetDetails().GetSequence()
|
||||
return nil
|
||||
},
|
||||
req: &action.ListExecutionsRequest{
|
||||
@@ -665,24 +758,77 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
||||
Details: &object.Details{
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
ExecutionId: "request./zitadel.session.v2beta.SessionService/GetSession",
|
||||
Targets: []string{targetResp.GetId()},
|
||||
}, {
|
||||
Details: &object.Details{
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
ExecutionId: "request./zitadel.session.v2beta.SessionService/CreateSession",
|
||||
Targets: []string{targetResp.GetId()},
|
||||
}, {
|
||||
Details: &object.Details{
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
ExecutionId: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||
Targets: []string{targetResp.GetId()},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list multiple conditions all types",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error {
|
||||
targets := executionTargetsSingleTarget(targetResp.GetId())
|
||||
for i, cond := range request.Queries[0].GetInConditionsQuery().GetConditions() {
|
||||
resp := Tester.SetExecution(ctx, t, cond, targets)
|
||||
response.Result[i].Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
||||
response.Result[i].Details.Sequence = resp.GetDetails().GetSequence()
|
||||
response.Result[i].Condition = cond
|
||||
response.Result[i].Targets = targets
|
||||
|
||||
// filled with info of last sequence
|
||||
response.Details.Timestamp = resp.GetDetails().GetChangeDate()
|
||||
response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
req: &action.ListExecutionsRequest{
|
||||
Queries: []*action.SearchQuery{{
|
||||
Query: &action.SearchQuery_InConditionsQuery{
|
||||
InConditionsQuery: &action.InConditionsQuery{
|
||||
Conditions: []*action.Condition{
|
||||
{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Method{Method: "/zitadel.session.v2beta.SessionService/GetSession"}}}},
|
||||
{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Service{Service: "zitadel.session.v2beta.SessionService"}}}},
|
||||
{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_All{All: true}}}},
|
||||
{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Method{Method: "/zitadel.session.v2beta.SessionService/GetSession"}}}},
|
||||
{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Service{Service: "zitadel.session.v2beta.SessionService"}}}},
|
||||
{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_All{All: true}}}},
|
||||
{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_Event{Event: "user.added"}}}},
|
||||
{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_Group{Group: "user"}}}},
|
||||
{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_All{All: true}}}},
|
||||
{ConditionType: &action.Condition_Function{Function: &action.FunctionExecution{Name: "Action.Flow.Type.ExternalAuthentication.Action.TriggerType.PostAuthentication"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
want: &action.ListExecutionsResponse{
|
||||
Details: &object.ListDetails{
|
||||
TotalResult: 10,
|
||||
},
|
||||
Result: []*action.Execution{
|
||||
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
||||
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
||||
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
||||
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
||||
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
||||
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
||||
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
||||
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
||||
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
||||
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -699,20 +845,33 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
got, listErr := Client.ListExecutions(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(ttt, listErr, "Error: "+listErr.Error())
|
||||
assert.Error(t, listErr, "Error: "+listErr.Error())
|
||||
} else {
|
||||
assert.NoError(ttt, listErr)
|
||||
assert.NoError(t, listErr)
|
||||
}
|
||||
if listErr != nil {
|
||||
return
|
||||
}
|
||||
// always first check length, otherwise its failed anyway
|
||||
assert.Len(ttt, got.Result, len(tt.want.Result))
|
||||
assert.Len(t, got.Result, len(tt.want.Result))
|
||||
for i := range tt.want.Result {
|
||||
assert.Contains(ttt, got.Result, tt.want.Result[i])
|
||||
// as not sorted, all elements have to be checked
|
||||
// workaround as oneof elements can only be checked with assert.EqualExportedValues()
|
||||
if j, found := containExecution(got.Result, tt.want.Result[i]); found {
|
||||
assert.EqualExportedValues(t, tt.want.Result[i], got.Result[j])
|
||||
}
|
||||
}
|
||||
integration.AssertListDetails(t, tt.want, got)
|
||||
}, retryDuration, time.Millisecond*100, "timeout waiting for expected execution result")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func containExecution(executionList []*action.Execution, execution *action.Execution) (int, bool) {
|
||||
for i, exec := range executionList {
|
||||
if reflect.DeepEqual(exec.Details, execution.Details) {
|
||||
return i, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
@@ -66,5 +66,5 @@ func checkExecutionEnabled(ctx context.Context) error {
|
||||
if authz.GetInstance(ctx).Features().Actions {
|
||||
return nil
|
||||
}
|
||||
return zerrors.ThrowPreconditionFailed(nil, "SCHEMA-141bwx3lef", "Errors.action.NotEnabled")
|
||||
return zerrors.ThrowPreconditionFailed(nil, "ACTION-8o6pvqfjhs", "Errors.Action.NotEnabled")
|
||||
}
|
||||
|
||||
@@ -58,23 +58,26 @@ func (s *Server) DeleteTarget(ctx context.Context, req *action.DeleteTargetReque
|
||||
}
|
||||
|
||||
func createTargetToCommand(req *action.CreateTargetRequest) *command.AddTarget {
|
||||
var targetType domain.TargetType
|
||||
var url string
|
||||
var (
|
||||
targetType domain.TargetType
|
||||
interruptOnError bool
|
||||
)
|
||||
switch t := req.GetTargetType().(type) {
|
||||
case *action.CreateTargetRequest_RestWebhook:
|
||||
targetType = domain.TargetTypeWebhook
|
||||
url = t.RestWebhook.GetUrl()
|
||||
case *action.CreateTargetRequest_RestRequestResponse:
|
||||
targetType = domain.TargetTypeRequestResponse
|
||||
url = t.RestRequestResponse.GetUrl()
|
||||
interruptOnError = t.RestWebhook.InterruptOnError
|
||||
case *action.CreateTargetRequest_RestCall:
|
||||
targetType = domain.TargetTypeCall
|
||||
interruptOnError = t.RestCall.InterruptOnError
|
||||
case *action.CreateTargetRequest_RestAsync:
|
||||
targetType = domain.TargetTypeAsync
|
||||
}
|
||||
return &command.AddTarget{
|
||||
Name: req.GetName(),
|
||||
TargetType: targetType,
|
||||
URL: url,
|
||||
Endpoint: req.GetEndpoint(),
|
||||
Timeout: req.GetTimeout().AsDuration(),
|
||||
Async: req.GetIsAsync(),
|
||||
InterruptOnError: req.GetInterruptOnError(),
|
||||
InterruptOnError: interruptOnError,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,22 +89,24 @@ func updateTargetToCommand(req *action.UpdateTargetRequest) *command.ChangeTarge
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: req.GetTargetId(),
|
||||
},
|
||||
Name: req.Name,
|
||||
Name: req.Name,
|
||||
Endpoint: req.Endpoint,
|
||||
}
|
||||
switch t := req.GetTargetType().(type) {
|
||||
case *action.UpdateTargetRequest_RestWebhook:
|
||||
target.TargetType = gu.Ptr(domain.TargetTypeWebhook)
|
||||
target.URL = gu.Ptr(t.RestWebhook.GetUrl())
|
||||
case *action.UpdateTargetRequest_RestRequestResponse:
|
||||
target.TargetType = gu.Ptr(domain.TargetTypeRequestResponse)
|
||||
target.URL = gu.Ptr(t.RestRequestResponse.GetUrl())
|
||||
if req.TargetType != nil {
|
||||
switch t := req.GetTargetType().(type) {
|
||||
case *action.UpdateTargetRequest_RestWebhook:
|
||||
target.TargetType = gu.Ptr(domain.TargetTypeWebhook)
|
||||
target.InterruptOnError = gu.Ptr(t.RestWebhook.InterruptOnError)
|
||||
case *action.UpdateTargetRequest_RestCall:
|
||||
target.TargetType = gu.Ptr(domain.TargetTypeCall)
|
||||
target.InterruptOnError = gu.Ptr(t.RestCall.InterruptOnError)
|
||||
case *action.UpdateTargetRequest_RestAsync:
|
||||
target.TargetType = gu.Ptr(domain.TargetTypeAsync)
|
||||
target.InterruptOnError = gu.Ptr(false)
|
||||
}
|
||||
}
|
||||
if req.Timeout != nil {
|
||||
target.Timeout = gu.Ptr(req.GetTimeout().AsDuration())
|
||||
}
|
||||
if req.ExecutionType != nil {
|
||||
target.Async = gu.Ptr(req.GetIsAsync())
|
||||
target.InterruptOnError = gu.Ptr(req.GetInterruptOnError())
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
||||
@@ -69,8 +70,8 @@ func TestServer_CreateTarget(t *testing.T) {
|
||||
ctx: CTX,
|
||||
req: &action.CreateTargetRequest{
|
||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
TargetType: &action.CreateTargetRequest_RestRequestResponse{
|
||||
RestRequestResponse: &action.SetRESTRequestResponse{},
|
||||
TargetType: &action.CreateTargetRequest_RestCall{
|
||||
RestCall: &action.SetRESTCall{},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
@@ -79,29 +80,25 @@ func TestServer_CreateTarget(t *testing.T) {
|
||||
name: "empty timeout",
|
||||
ctx: CTX,
|
||||
req: &action.CreateTargetRequest{
|
||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
Url: "https://example.com",
|
||||
},
|
||||
RestWebhook: &action.SetRESTWebhook{},
|
||||
},
|
||||
Timeout: nil,
|
||||
ExecutionType: nil,
|
||||
Timeout: nil,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty execution type, ok",
|
||||
name: "async, ok",
|
||||
ctx: CTX,
|
||||
req: &action.CreateTargetRequest{
|
||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
Url: "https://example.com",
|
||||
},
|
||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.CreateTargetRequest_RestAsync{
|
||||
RestAsync: &action.SetRESTAsync{},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
ExecutionType: nil,
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: &action.CreateTargetResponse{
|
||||
Details: &object.Details{
|
||||
@@ -111,19 +108,17 @@ func TestServer_CreateTarget(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "async execution, ok",
|
||||
name: "webhook, ok",
|
||||
ctx: CTX,
|
||||
req: &action.CreateTargetRequest{
|
||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
Url: "https://example.com",
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
ExecutionType: &action.CreateTargetRequest_IsAsync{
|
||||
IsAsync: true,
|
||||
},
|
||||
},
|
||||
want: &action.CreateTargetResponse{
|
||||
Details: &object.Details{
|
||||
@@ -133,20 +128,59 @@ func TestServer_CreateTarget(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "interrupt on error execution, ok",
|
||||
name: "webhook, interrupt on error, ok",
|
||||
ctx: CTX,
|
||||
req: &action.CreateTargetRequest{
|
||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
Url: "https://example.com",
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
ExecutionType: &action.CreateTargetRequest_InterruptOnError{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
want: &action.CreateTargetResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "call, ok",
|
||||
ctx: CTX,
|
||||
req: &action.CreateTargetRequest{
|
||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.CreateTargetRequest_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: &action.CreateTargetResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "call, interruptOnError, ok",
|
||||
ctx: CTX,
|
||||
req: &action.CreateTargetRequest{
|
||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.CreateTargetRequest_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: &action.CreateTargetResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
@@ -186,7 +220,7 @@ func TestServer_UpdateTarget(t *testing.T) {
|
||||
{
|
||||
name: "missing permission",
|
||||
prepare: func(request *action.UpdateTargetRequest) error {
|
||||
targetID := Tester.CreateTarget(CTX, t).GetId()
|
||||
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||
request.TargetId = targetID
|
||||
return nil
|
||||
},
|
||||
@@ -215,7 +249,7 @@ func TestServer_UpdateTarget(t *testing.T) {
|
||||
{
|
||||
name: "change name, ok",
|
||||
prepare: func(request *action.UpdateTargetRequest) error {
|
||||
targetID := Tester.CreateTarget(CTX, t).GetId()
|
||||
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||
request.TargetId = targetID
|
||||
return nil
|
||||
},
|
||||
@@ -235,16 +269,16 @@ func TestServer_UpdateTarget(t *testing.T) {
|
||||
{
|
||||
name: "change type, ok",
|
||||
prepare: func(request *action.UpdateTargetRequest) error {
|
||||
targetID := Tester.CreateTarget(CTX, t).GetId()
|
||||
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||
request.TargetId = targetID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &action.UpdateTargetRequest{
|
||||
TargetType: &action.UpdateTargetRequest_RestRequestResponse{
|
||||
RestRequestResponse: &action.SetRESTRequestResponse{
|
||||
Url: "https://example.com",
|
||||
TargetType: &action.UpdateTargetRequest_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -259,18 +293,14 @@ func TestServer_UpdateTarget(t *testing.T) {
|
||||
{
|
||||
name: "change url, ok",
|
||||
prepare: func(request *action.UpdateTargetRequest) error {
|
||||
targetID := Tester.CreateTarget(CTX, t).GetId()
|
||||
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||
request.TargetId = targetID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &action.UpdateTargetRequest{
|
||||
TargetType: &action.UpdateTargetRequest_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
Url: "https://example.com/hooks/new",
|
||||
},
|
||||
},
|
||||
Endpoint: gu.Ptr("https://example.com/hooks/new"),
|
||||
},
|
||||
},
|
||||
want: &action.UpdateTargetResponse{
|
||||
@@ -283,7 +313,7 @@ func TestServer_UpdateTarget(t *testing.T) {
|
||||
{
|
||||
name: "change timeout, ok",
|
||||
prepare: func(request *action.UpdateTargetRequest) error {
|
||||
targetID := Tester.CreateTarget(CTX, t).GetId()
|
||||
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||
request.TargetId = targetID
|
||||
return nil
|
||||
},
|
||||
@@ -301,17 +331,17 @@ func TestServer_UpdateTarget(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change execution type, ok",
|
||||
name: "change type async, ok",
|
||||
prepare: func(request *action.UpdateTargetRequest) error {
|
||||
targetID := Tester.CreateTarget(CTX, t).GetId()
|
||||
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeAsync, false).GetId()
|
||||
request.TargetId = targetID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &action.UpdateTargetRequest{
|
||||
ExecutionType: &action.UpdateTargetRequest_IsAsync{
|
||||
IsAsync: true,
|
||||
TargetType: &action.UpdateTargetRequest_RestAsync{
|
||||
RestAsync: &action.SetRESTAsync{},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -341,7 +371,7 @@ func TestServer_UpdateTarget(t *testing.T) {
|
||||
|
||||
func TestServer_DeleteTarget(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
target := Tester.CreateTarget(CTX, t)
|
||||
target := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false)
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
|
||||
@@ -27,55 +27,64 @@ func Test_createTargetToCommand(t *testing.T) {
|
||||
args: args{nil},
|
||||
want: &command.AddTarget{
|
||||
Name: "",
|
||||
URL: "",
|
||||
Endpoint: "",
|
||||
Timeout: 0,
|
||||
Async: false,
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all fields (async webhook)",
|
||||
name: "all fields (webhook)",
|
||||
args: args{&action.CreateTargetRequest{
|
||||
Name: "target 1",
|
||||
Name: "target 1",
|
||||
Endpoint: "https://example.com/hooks/1",
|
||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
Url: "https://example.com/hooks/1",
|
||||
},
|
||||
RestWebhook: &action.SetRESTWebhook{},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
ExecutionType: &action.CreateTargetRequest_IsAsync{
|
||||
IsAsync: true,
|
||||
},
|
||||
}},
|
||||
want: &command.AddTarget{
|
||||
Name: "target 1",
|
||||
TargetType: domain.TargetTypeWebhook,
|
||||
URL: "https://example.com/hooks/1",
|
||||
Endpoint: "https://example.com/hooks/1",
|
||||
Timeout: 10 * time.Second,
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all fields (async)",
|
||||
args: args{&action.CreateTargetRequest{
|
||||
Name: "target 1",
|
||||
Endpoint: "https://example.com/hooks/1",
|
||||
TargetType: &action.CreateTargetRequest_RestAsync{
|
||||
RestAsync: &action.SetRESTAsync{},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
}},
|
||||
want: &command.AddTarget{
|
||||
Name: "target 1",
|
||||
TargetType: domain.TargetTypeAsync,
|
||||
Endpoint: "https://example.com/hooks/1",
|
||||
Timeout: 10 * time.Second,
|
||||
Async: true,
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all fields (interrupting response)",
|
||||
args: args{&action.CreateTargetRequest{
|
||||
Name: "target 1",
|
||||
TargetType: &action.CreateTargetRequest_RestRequestResponse{
|
||||
RestRequestResponse: &action.SetRESTRequestResponse{
|
||||
Url: "https://example.com/hooks/1",
|
||||
Name: "target 1",
|
||||
Endpoint: "https://example.com/hooks/1",
|
||||
TargetType: &action.CreateTargetRequest_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
ExecutionType: &action.CreateTargetRequest_InterruptOnError{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
}},
|
||||
want: &command.AddTarget{
|
||||
Name: "target 1",
|
||||
TargetType: domain.TargetTypeRequestResponse,
|
||||
URL: "https://example.com/hooks/1",
|
||||
TargetType: domain.TargetTypeCall,
|
||||
Endpoint: "https://example.com/hooks/1",
|
||||
Timeout: 10 * time.Second,
|
||||
Async: false,
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
@@ -105,80 +114,108 @@ func Test_updateTargetToCommand(t *testing.T) {
|
||||
{
|
||||
name: "all fields nil",
|
||||
args: args{&action.UpdateTargetRequest{
|
||||
Name: nil,
|
||||
TargetType: nil,
|
||||
Timeout: nil,
|
||||
ExecutionType: nil,
|
||||
Name: nil,
|
||||
TargetType: nil,
|
||||
Timeout: nil,
|
||||
}},
|
||||
want: &command.ChangeTarget{
|
||||
Name: nil,
|
||||
TargetType: nil,
|
||||
URL: nil,
|
||||
Endpoint: nil,
|
||||
Timeout: nil,
|
||||
Async: nil,
|
||||
InterruptOnError: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all fields empty",
|
||||
args: args{&action.UpdateTargetRequest{
|
||||
Name: gu.Ptr(""),
|
||||
TargetType: nil,
|
||||
Timeout: durationpb.New(0),
|
||||
ExecutionType: nil,
|
||||
Name: gu.Ptr(""),
|
||||
TargetType: nil,
|
||||
Timeout: durationpb.New(0),
|
||||
}},
|
||||
want: &command.ChangeTarget{
|
||||
Name: gu.Ptr(""),
|
||||
TargetType: nil,
|
||||
URL: nil,
|
||||
Endpoint: nil,
|
||||
Timeout: gu.Ptr(0 * time.Second),
|
||||
Async: nil,
|
||||
InterruptOnError: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all fields (async webhook)",
|
||||
name: "all fields (webhook)",
|
||||
args: args{&action.UpdateTargetRequest{
|
||||
Name: gu.Ptr("target 1"),
|
||||
Name: gu.Ptr("target 1"),
|
||||
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||
TargetType: &action.UpdateTargetRequest_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
Url: "https://example.com/hooks/1",
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
ExecutionType: &action.UpdateTargetRequest_IsAsync{
|
||||
IsAsync: true,
|
||||
},
|
||||
}},
|
||||
want: &command.ChangeTarget{
|
||||
Name: gu.Ptr("target 1"),
|
||||
TargetType: gu.Ptr(domain.TargetTypeWebhook),
|
||||
URL: gu.Ptr("https://example.com/hooks/1"),
|
||||
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||
Timeout: gu.Ptr(10 * time.Second),
|
||||
InterruptOnError: gu.Ptr(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all fields (webhook interrupt)",
|
||||
args: args{&action.UpdateTargetRequest{
|
||||
Name: gu.Ptr("target 1"),
|
||||
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||
TargetType: &action.UpdateTargetRequest_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
}},
|
||||
want: &command.ChangeTarget{
|
||||
Name: gu.Ptr("target 1"),
|
||||
TargetType: gu.Ptr(domain.TargetTypeWebhook),
|
||||
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||
Timeout: gu.Ptr(10 * time.Second),
|
||||
InterruptOnError: gu.Ptr(true),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all fields (async)",
|
||||
args: args{&action.UpdateTargetRequest{
|
||||
Name: gu.Ptr("target 1"),
|
||||
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||
TargetType: &action.UpdateTargetRequest_RestAsync{
|
||||
RestAsync: &action.SetRESTAsync{},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
}},
|
||||
want: &command.ChangeTarget{
|
||||
Name: gu.Ptr("target 1"),
|
||||
TargetType: gu.Ptr(domain.TargetTypeAsync),
|
||||
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||
Timeout: gu.Ptr(10 * time.Second),
|
||||
Async: gu.Ptr(true),
|
||||
InterruptOnError: gu.Ptr(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all fields (interrupting response)",
|
||||
args: args{&action.UpdateTargetRequest{
|
||||
Name: gu.Ptr("target 1"),
|
||||
TargetType: &action.UpdateTargetRequest_RestRequestResponse{
|
||||
RestRequestResponse: &action.SetRESTRequestResponse{
|
||||
Url: "https://example.com/hooks/1",
|
||||
Name: gu.Ptr("target 1"),
|
||||
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||
TargetType: &action.UpdateTargetRequest_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
ExecutionType: &action.UpdateTargetRequest_InterruptOnError{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
}},
|
||||
want: &command.ChangeTarget{
|
||||
Name: gu.Ptr("target 1"),
|
||||
TargetType: gu.Ptr(domain.TargetTypeRequestResponse),
|
||||
URL: gu.Ptr("https://example.com/hooks/1"),
|
||||
TargetType: gu.Ptr(domain.TargetTypeCall),
|
||||
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||
Timeout: gu.Ptr(10 * time.Second),
|
||||
Async: gu.Ptr(false),
|
||||
InterruptOnError: gu.Ptr(true),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/execution"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
exec_repo "github.com/zitadel/zitadel/internal/repository/execution"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
|
||||
func ExecutionHandler(queries *query.Queries) grpc.UnaryServerInterceptor {
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
requestTargets, responseTargets := queryTargets(ctx, queries, info.FullMethod)
|
||||
|
||||
// call targets otherwise return req
|
||||
handledReq, err := executeTargetsForRequest(ctx, requestTargets, info.FullMethod, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := handler(ctx, handledReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return executeTargetsForResponse(ctx, responseTargets, info.FullMethod, handledReq, response)
|
||||
}
|
||||
}
|
||||
|
||||
func executeTargetsForRequest(ctx context.Context, targets []execution.Target, fullMethod string, req interface{}) (_ interface{}, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer span.EndWithError(err)
|
||||
|
||||
// if no targets are found, return without any calls
|
||||
if len(targets) == 0 {
|
||||
return req, nil
|
||||
}
|
||||
|
||||
ctxData := authz.GetCtxData(ctx)
|
||||
info := &ContextInfoRequest{
|
||||
FullMethod: fullMethod,
|
||||
InstanceID: authz.GetInstance(ctx).InstanceID(),
|
||||
ProjectID: ctxData.ProjectID,
|
||||
OrgID: ctxData.OrgID,
|
||||
UserID: ctxData.UserID,
|
||||
Request: req,
|
||||
}
|
||||
|
||||
return execution.CallTargets(ctx, targets, info)
|
||||
}
|
||||
|
||||
func executeTargetsForResponse(ctx context.Context, targets []execution.Target, fullMethod string, req, resp interface{}) (_ interface{}, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer span.EndWithError(err)
|
||||
|
||||
// if no targets are found, return without any calls
|
||||
if len(targets) == 0 {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
ctxData := authz.GetCtxData(ctx)
|
||||
info := &ContextInfoResponse{
|
||||
FullMethod: fullMethod,
|
||||
InstanceID: authz.GetInstance(ctx).InstanceID(),
|
||||
ProjectID: ctxData.ProjectID,
|
||||
OrgID: ctxData.OrgID,
|
||||
UserID: ctxData.UserID,
|
||||
Request: req,
|
||||
Response: resp,
|
||||
}
|
||||
|
||||
return execution.CallTargets(ctx, targets, info)
|
||||
}
|
||||
|
||||
type ExecutionQueries interface {
|
||||
TargetsByExecutionIDs(ctx context.Context, ids1, ids2 []string) (execution []*query.ExecutionTarget, err error)
|
||||
}
|
||||
|
||||
func queryTargets(
|
||||
ctx context.Context,
|
||||
queries ExecutionQueries,
|
||||
fullMethod string,
|
||||
) ([]execution.Target, []execution.Target) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer span.End()
|
||||
|
||||
targets, err := queries.TargetsByExecutionIDs(ctx,
|
||||
idsForFullMethod(fullMethod, domain.ExecutionTypeRequest),
|
||||
idsForFullMethod(fullMethod, domain.ExecutionTypeResponse),
|
||||
)
|
||||
requestTargets := make([]execution.Target, 0, len(targets))
|
||||
responseTargets := make([]execution.Target, 0, len(targets))
|
||||
if err != nil {
|
||||
logging.WithFields("fullMethod", fullMethod).WithError(err).Info("unable to query targets")
|
||||
return requestTargets, responseTargets
|
||||
}
|
||||
|
||||
for _, target := range targets {
|
||||
if strings.HasPrefix(target.GetExecutionID(), exec_repo.IDAll(domain.ExecutionTypeRequest)) {
|
||||
requestTargets = append(requestTargets, target)
|
||||
} else if strings.HasPrefix(target.GetExecutionID(), exec_repo.IDAll(domain.ExecutionTypeResponse)) {
|
||||
responseTargets = append(responseTargets, target)
|
||||
}
|
||||
}
|
||||
|
||||
return requestTargets, responseTargets
|
||||
}
|
||||
|
||||
func idsForFullMethod(fullMethod string, executionType domain.ExecutionType) []string {
|
||||
return []string{exec_repo.ID(executionType, fullMethod), exec_repo.ID(executionType, serviceFromFullMethod(fullMethod)), exec_repo.IDAll(executionType)}
|
||||
}
|
||||
|
||||
func serviceFromFullMethod(s string) string {
|
||||
parts := strings.Split(s, "/")
|
||||
return parts[1]
|
||||
}
|
||||
|
||||
var _ execution.ContextInfo = &ContextInfoRequest{}
|
||||
|
||||
type ContextInfoRequest struct {
|
||||
FullMethod string `json:"fullMethod,omitempty"`
|
||||
InstanceID string `json:"instanceID,omitempty"`
|
||||
OrgID string `json:"orgID,omitempty"`
|
||||
ProjectID string `json:"projectID,omitempty"`
|
||||
UserID string `json:"userID,omitempty"`
|
||||
Request interface{} `json:"request,omitempty"`
|
||||
}
|
||||
|
||||
func (c *ContextInfoRequest) GetHTTPRequestBody() []byte {
|
||||
data, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (c *ContextInfoRequest) SetHTTPResponseBody(resp []byte) error {
|
||||
return json.Unmarshal(resp, c.Request)
|
||||
}
|
||||
|
||||
func (c *ContextInfoRequest) GetContent() interface{} {
|
||||
return c.Request
|
||||
}
|
||||
|
||||
var _ execution.ContextInfo = &ContextInfoResponse{}
|
||||
|
||||
type ContextInfoResponse struct {
|
||||
FullMethod string `json:"fullMethod,omitempty"`
|
||||
InstanceID string `json:"instanceID,omitempty"`
|
||||
OrgID string `json:"orgID,omitempty"`
|
||||
ProjectID string `json:"projectID,omitempty"`
|
||||
UserID string `json:"userID,omitempty"`
|
||||
Request interface{} `json:"request,omitempty"`
|
||||
Response interface{} `json:"response,omitempty"`
|
||||
}
|
||||
|
||||
func (c *ContextInfoResponse) GetHTTPRequestBody() []byte {
|
||||
data, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (c *ContextInfoResponse) SetHTTPResponseBody(resp []byte) error {
|
||||
return json.Unmarshal(resp, c.Response)
|
||||
}
|
||||
|
||||
func (c *ContextInfoResponse) GetContent() interface{} {
|
||||
return c.Response
|
||||
}
|
||||
@@ -0,0 +1,778 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/execution"
|
||||
)
|
||||
|
||||
var _ execution.Target = &mockExecutionTarget{}
|
||||
|
||||
type mockExecutionTarget struct {
|
||||
InstanceID string
|
||||
ExecutionID string
|
||||
TargetID string
|
||||
TargetType domain.TargetType
|
||||
Endpoint string
|
||||
Timeout time.Duration
|
||||
InterruptOnError bool
|
||||
}
|
||||
|
||||
func (e *mockExecutionTarget) SetEndpoint(endpoint string) {
|
||||
e.Endpoint = endpoint
|
||||
}
|
||||
func (e *mockExecutionTarget) IsInterruptOnError() bool {
|
||||
return e.InterruptOnError
|
||||
}
|
||||
func (e *mockExecutionTarget) GetEndpoint() string {
|
||||
return e.Endpoint
|
||||
}
|
||||
func (e *mockExecutionTarget) GetTargetType() domain.TargetType {
|
||||
return e.TargetType
|
||||
}
|
||||
func (e *mockExecutionTarget) GetTimeout() time.Duration {
|
||||
return e.Timeout
|
||||
}
|
||||
func (e *mockExecutionTarget) GetTargetID() string {
|
||||
return e.TargetID
|
||||
}
|
||||
func (e *mockExecutionTarget) GetExecutionID() string {
|
||||
return e.ExecutionID
|
||||
}
|
||||
|
||||
type mockContentRequest struct {
|
||||
Content string
|
||||
}
|
||||
|
||||
func newMockContentRequest(content string) *mockContentRequest {
|
||||
return &mockContentRequest{
|
||||
Content: content,
|
||||
}
|
||||
}
|
||||
|
||||
func newMockContextInfoRequest(fullMethod, request string) *ContextInfoRequest {
|
||||
return &ContextInfoRequest{
|
||||
FullMethod: fullMethod,
|
||||
Request: newMockContentRequest(request),
|
||||
}
|
||||
}
|
||||
|
||||
func newMockContextInfoResponse(fullMethod, request, response string) *ContextInfoResponse {
|
||||
return &ContextInfoResponse{
|
||||
FullMethod: fullMethod,
|
||||
Request: newMockContentRequest(request),
|
||||
Response: newMockContentRequest(response),
|
||||
}
|
||||
}
|
||||
|
||||
func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) {
|
||||
type target struct {
|
||||
reqBody execution.ContextInfo
|
||||
sleep time.Duration
|
||||
statusCode int
|
||||
respBody interface{}
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
|
||||
executionTargets []execution.Target
|
||||
targets []target
|
||||
fullMethod string
|
||||
req interface{}
|
||||
}
|
||||
type res struct {
|
||||
want interface{}
|
||||
wantErr bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"target, executionTargets nil",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
fullMethod: "/service/method",
|
||||
executionTargets: nil,
|
||||
req: newMockContentRequest("request"),
|
||||
},
|
||||
res{
|
||||
want: newMockContentRequest("request"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"target, executionTargets empty",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
fullMethod: "/service/method",
|
||||
executionTargets: []execution.Target{},
|
||||
req: newMockContentRequest("request"),
|
||||
},
|
||||
res{
|
||||
want: newMockContentRequest("request"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"target, not reachable",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
fullMethod: "/service/method",
|
||||
executionTargets: []execution.Target{
|
||||
&mockExecutionTarget{
|
||||
InstanceID: "instance",
|
||||
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||
TargetID: "target",
|
||||
TargetType: domain.TargetTypeCall,
|
||||
Timeout: time.Minute,
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
targets: []target{},
|
||||
req: newMockContentRequest("content"),
|
||||
},
|
||||
res{
|
||||
wantErr: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"target, error without interrupt",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
fullMethod: "/service/method",
|
||||
executionTargets: []execution.Target{
|
||||
&mockExecutionTarget{
|
||||
InstanceID: "instance",
|
||||
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||
TargetID: "target",
|
||||
TargetType: domain.TargetTypeCall,
|
||||
Timeout: time.Minute,
|
||||
},
|
||||
},
|
||||
targets: []target{
|
||||
{
|
||||
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||
respBody: newMockContentRequest("content1"),
|
||||
sleep: 0,
|
||||
statusCode: http.StatusBadRequest,
|
||||
},
|
||||
},
|
||||
req: newMockContentRequest("content"),
|
||||
},
|
||||
res{
|
||||
want: newMockContentRequest("content"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"target, interruptOnError",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
fullMethod: "/service/method",
|
||||
executionTargets: []execution.Target{
|
||||
&mockExecutionTarget{
|
||||
InstanceID: "instance",
|
||||
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||
TargetID: "target",
|
||||
TargetType: domain.TargetTypeCall,
|
||||
Timeout: time.Minute,
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
|
||||
targets: []target{
|
||||
{
|
||||
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||
respBody: newMockContentRequest("content1"),
|
||||
sleep: 0,
|
||||
statusCode: http.StatusBadRequest,
|
||||
},
|
||||
},
|
||||
req: newMockContentRequest("content"),
|
||||
},
|
||||
res{
|
||||
wantErr: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"target, timeout",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
fullMethod: "/service/method",
|
||||
executionTargets: []execution.Target{
|
||||
&mockExecutionTarget{
|
||||
InstanceID: "instance",
|
||||
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||
TargetID: "target",
|
||||
TargetType: domain.TargetTypeCall,
|
||||
Timeout: time.Second,
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
targets: []target{
|
||||
{
|
||||
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||
respBody: newMockContentRequest("content1"),
|
||||
sleep: 5 * time.Second,
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
},
|
||||
req: newMockContentRequest("content"),
|
||||
},
|
||||
res{
|
||||
wantErr: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"target, wrong request",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
fullMethod: "/service/method",
|
||||
executionTargets: []execution.Target{
|
||||
&mockExecutionTarget{
|
||||
InstanceID: "instance",
|
||||
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||
TargetID: "target",
|
||||
TargetType: domain.TargetTypeCall,
|
||||
Timeout: time.Second,
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
targets: []target{
|
||||
{reqBody: newMockContextInfoRequest("/service/method", "wrong")},
|
||||
},
|
||||
req: newMockContentRequest("content"),
|
||||
},
|
||||
res{
|
||||
wantErr: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"target, ok",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
fullMethod: "/service/method",
|
||||
executionTargets: []execution.Target{
|
||||
&mockExecutionTarget{
|
||||
InstanceID: "instance",
|
||||
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||
TargetID: "target",
|
||||
TargetType: domain.TargetTypeCall,
|
||||
Timeout: time.Minute,
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
targets: []target{
|
||||
{
|
||||
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||
respBody: newMockContentRequest("content1"),
|
||||
sleep: 0,
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
},
|
||||
req: newMockContentRequest("content"),
|
||||
},
|
||||
res{
|
||||
want: newMockContentRequest("content1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"target async, timeout",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
fullMethod: "/service/method",
|
||||
executionTargets: []execution.Target{
|
||||
&mockExecutionTarget{
|
||||
InstanceID: "instance",
|
||||
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||
TargetID: "target",
|
||||
TargetType: domain.TargetTypeAsync,
|
||||
Timeout: time.Second,
|
||||
},
|
||||
},
|
||||
targets: []target{
|
||||
{
|
||||
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||
respBody: newMockContentRequest("content1"),
|
||||
sleep: 5 * time.Second,
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
},
|
||||
req: newMockContentRequest("content"),
|
||||
},
|
||||
res{
|
||||
want: newMockContentRequest("content"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"target async, ok",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
fullMethod: "/service/method",
|
||||
executionTargets: []execution.Target{
|
||||
&mockExecutionTarget{
|
||||
InstanceID: "instance",
|
||||
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||
TargetID: "target",
|
||||
TargetType: domain.TargetTypeAsync,
|
||||
Timeout: time.Minute,
|
||||
},
|
||||
},
|
||||
targets: []target{
|
||||
{
|
||||
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||
respBody: newMockContentRequest("content1"),
|
||||
sleep: 0,
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
},
|
||||
req: newMockContentRequest("content"),
|
||||
},
|
||||
res{
|
||||
want: newMockContentRequest("content"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"webhook, error",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
fullMethod: "/service/method",
|
||||
executionTargets: []execution.Target{
|
||||
&mockExecutionTarget{
|
||||
InstanceID: "instance",
|
||||
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||
TargetID: "target",
|
||||
TargetType: domain.TargetTypeWebhook,
|
||||
Timeout: time.Minute,
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
targets: []target{
|
||||
{
|
||||
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||
sleep: 0,
|
||||
statusCode: http.StatusInternalServerError,
|
||||
},
|
||||
},
|
||||
req: newMockContentRequest("content"),
|
||||
},
|
||||
res{
|
||||
wantErr: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"webhook, timeout",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
fullMethod: "/service/method",
|
||||
executionTargets: []execution.Target{
|
||||
&mockExecutionTarget{
|
||||
InstanceID: "instance",
|
||||
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||
TargetID: "target",
|
||||
TargetType: domain.TargetTypeWebhook,
|
||||
Timeout: time.Second,
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
targets: []target{
|
||||
{
|
||||
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||
respBody: newMockContentRequest("content1"),
|
||||
sleep: 5 * time.Second,
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
},
|
||||
req: newMockContentRequest("content"),
|
||||
},
|
||||
res{
|
||||
wantErr: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"webhook, ok",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
fullMethod: "/service/method",
|
||||
executionTargets: []execution.Target{
|
||||
&mockExecutionTarget{
|
||||
InstanceID: "instance",
|
||||
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||
TargetID: "target",
|
||||
TargetType: domain.TargetTypeWebhook,
|
||||
Timeout: time.Minute,
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
targets: []target{
|
||||
{
|
||||
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||
respBody: newMockContentRequest("content1"),
|
||||
sleep: 0,
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
},
|
||||
req: newMockContentRequest("content"),
|
||||
},
|
||||
res{
|
||||
want: newMockContentRequest("content"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"with includes, interruptOnError",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
fullMethod: "/service/method",
|
||||
executionTargets: []execution.Target{
|
||||
&mockExecutionTarget{
|
||||
InstanceID: "instance",
|
||||
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||
TargetID: "target1",
|
||||
TargetType: domain.TargetTypeCall,
|
||||
Timeout: time.Minute,
|
||||
InterruptOnError: true,
|
||||
},
|
||||
&mockExecutionTarget{
|
||||
InstanceID: "instance",
|
||||
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||
TargetID: "target2",
|
||||
TargetType: domain.TargetTypeCall,
|
||||
Timeout: time.Minute,
|
||||
InterruptOnError: true,
|
||||
},
|
||||
&mockExecutionTarget{
|
||||
InstanceID: "instance",
|
||||
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||
TargetID: "target3",
|
||||
TargetType: domain.TargetTypeCall,
|
||||
Timeout: time.Minute,
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
|
||||
targets: []target{
|
||||
{
|
||||
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||
respBody: newMockContentRequest("content1"),
|
||||
sleep: 0,
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
reqBody: newMockContextInfoRequest("/service/method", "content1"),
|
||||
respBody: newMockContentRequest("content2"),
|
||||
sleep: 0,
|
||||
statusCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
reqBody: newMockContextInfoRequest("/service/method", "content2"),
|
||||
respBody: newMockContentRequest("content3"),
|
||||
sleep: 0,
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
},
|
||||
req: newMockContentRequest("content"),
|
||||
},
|
||||
res{
|
||||
wantErr: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"with includes, timeout",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
fullMethod: "/service/method",
|
||||
executionTargets: []execution.Target{
|
||||
&mockExecutionTarget{
|
||||
InstanceID: "instance",
|
||||
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||
TargetID: "target1",
|
||||
TargetType: domain.TargetTypeCall,
|
||||
Timeout: time.Minute,
|
||||
InterruptOnError: true,
|
||||
},
|
||||
&mockExecutionTarget{
|
||||
InstanceID: "instance",
|
||||
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||
TargetID: "target2",
|
||||
TargetType: domain.TargetTypeCall,
|
||||
Timeout: time.Second,
|
||||
InterruptOnError: true,
|
||||
},
|
||||
&mockExecutionTarget{
|
||||
InstanceID: "instance",
|
||||
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||
TargetID: "target3",
|
||||
TargetType: domain.TargetTypeCall,
|
||||
Timeout: time.Second,
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
targets: []target{
|
||||
{
|
||||
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||
respBody: newMockContentRequest("content1"),
|
||||
sleep: 0,
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
reqBody: newMockContextInfoRequest("/service/method", "content1"),
|
||||
respBody: newMockContentRequest("content2"),
|
||||
sleep: 5 * time.Second,
|
||||
statusCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
reqBody: newMockContextInfoRequest("/service/method", "content2"),
|
||||
respBody: newMockContentRequest("content3"),
|
||||
sleep: 5 * time.Second,
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
},
|
||||
req: newMockContentRequest("content"),
|
||||
},
|
||||
res{
|
||||
wantErr: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
closeFuncs := make([]func(), len(tt.args.targets))
|
||||
for i, target := range tt.args.targets {
|
||||
url, closeF := testServerCall(
|
||||
target.reqBody,
|
||||
target.sleep,
|
||||
target.statusCode,
|
||||
target.respBody,
|
||||
)
|
||||
|
||||
et := tt.args.executionTargets[i].(*mockExecutionTarget)
|
||||
et.SetEndpoint(url)
|
||||
closeFuncs[i] = closeF
|
||||
}
|
||||
|
||||
resp, err := executeTargetsForRequest(
|
||||
tt.args.ctx,
|
||||
tt.args.executionTargets,
|
||||
tt.args.fullMethod,
|
||||
tt.args.req,
|
||||
)
|
||||
|
||||
if tt.res.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, tt.res.want, resp)
|
||||
|
||||
for _, closeF := range closeFuncs {
|
||||
closeF()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testServerCall(
|
||||
reqBody interface{},
|
||||
sleep time.Duration,
|
||||
statusCode int,
|
||||
respBody interface{},
|
||||
) (string, func()) {
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
http.Error(w, "error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
sentBody, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(data, sentBody) {
|
||||
http.Error(w, "error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if statusCode != http.StatusOK {
|
||||
http.Error(w, "error", statusCode)
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(sleep)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
resp, err := json.Marshal(respBody)
|
||||
if err != nil {
|
||||
http.Error(w, "error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if _, err := io.WriteString(w, string(resp)); err != nil {
|
||||
http.Error(w, "error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(handler))
|
||||
|
||||
return server.URL, server.Close
|
||||
}
|
||||
|
||||
func Test_executeTargetsForGRPCFullMethod_response(t *testing.T) {
|
||||
type target struct {
|
||||
reqBody execution.ContextInfo
|
||||
sleep time.Duration
|
||||
statusCode int
|
||||
respBody interface{}
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
|
||||
executionTargets []execution.Target
|
||||
targets []target
|
||||
fullMethod string
|
||||
req interface{}
|
||||
resp interface{}
|
||||
}
|
||||
type res struct {
|
||||
want interface{}
|
||||
wantErr bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"target, executionTargets nil",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
fullMethod: "/service/method",
|
||||
executionTargets: nil,
|
||||
req: newMockContentRequest("request"),
|
||||
resp: newMockContentRequest("response"),
|
||||
},
|
||||
res{
|
||||
want: newMockContentRequest("response"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"target, executionTargets empty",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
fullMethod: "/service/method",
|
||||
executionTargets: []execution.Target{},
|
||||
req: newMockContentRequest("request"),
|
||||
resp: newMockContentRequest("response"),
|
||||
},
|
||||
res{
|
||||
want: newMockContentRequest("response"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"target, empty response",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
fullMethod: "/service/method",
|
||||
executionTargets: []execution.Target{
|
||||
&mockExecutionTarget{
|
||||
InstanceID: "instance",
|
||||
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||
TargetID: "target",
|
||||
TargetType: domain.TargetTypeCall,
|
||||
Timeout: time.Minute,
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
targets: []target{
|
||||
{
|
||||
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||
respBody: newMockContentRequest(""),
|
||||
sleep: 0,
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
},
|
||||
req: []byte{},
|
||||
},
|
||||
res{
|
||||
wantErr: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"target, ok",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
fullMethod: "/service/method",
|
||||
executionTargets: []execution.Target{
|
||||
&mockExecutionTarget{
|
||||
InstanceID: "instance",
|
||||
ExecutionID: "response./zitadel.session.v2beta.SessionService/SetSession",
|
||||
TargetID: "target",
|
||||
TargetType: domain.TargetTypeCall,
|
||||
Timeout: time.Minute,
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
targets: []target{
|
||||
{
|
||||
reqBody: newMockContextInfoResponse("/service/method", "request", "response"),
|
||||
respBody: newMockContentRequest("response1"),
|
||||
sleep: 0,
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
},
|
||||
req: newMockContentRequest("request"),
|
||||
resp: newMockContentRequest("response"),
|
||||
},
|
||||
res{
|
||||
want: newMockContentRequest("response1"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
closeFuncs := make([]func(), len(tt.args.targets))
|
||||
for i, target := range tt.args.targets {
|
||||
url, closeF := testServerCall(
|
||||
target.reqBody,
|
||||
target.sleep,
|
||||
target.statusCode,
|
||||
target.respBody,
|
||||
)
|
||||
|
||||
et := tt.args.executionTargets[i].(*mockExecutionTarget)
|
||||
et.SetEndpoint(url)
|
||||
closeFuncs[i] = closeF
|
||||
}
|
||||
|
||||
resp, err := executeTargetsForResponse(
|
||||
tt.args.ctx,
|
||||
tt.args.executionTargets,
|
||||
tt.args.fullMethod,
|
||||
tt.args.req,
|
||||
tt.args.resp,
|
||||
)
|
||||
|
||||
if tt.res.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, tt.res.want, resp)
|
||||
|
||||
for _, closeF := range closeFuncs {
|
||||
closeF()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,7 @@ func CreateServer(
|
||||
middleware.AuthorizationInterceptor(verifier, authConfig),
|
||||
middleware.TranslationHandler(),
|
||||
middleware.QuotaExhaustedInterceptor(accessSvc, system_pb.SystemService_ServiceDesc.ServiceName),
|
||||
middleware.ExecutionHandler(queries),
|
||||
middleware.ValidationHandler(),
|
||||
middleware.ServiceHandler(),
|
||||
middleware.ActivityInterceptor(),
|
||||
|
||||
@@ -2,6 +2,7 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
@@ -9,6 +10,10 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
const (
|
||||
EventGroupSuffix = ".*"
|
||||
)
|
||||
|
||||
type ExecutionAPICondition struct {
|
||||
Method string
|
||||
Service string
|
||||
@@ -134,7 +139,11 @@ func (e *ExecutionEventCondition) ID() string {
|
||||
return execution.ID(domain.ExecutionTypeEvent, e.Event)
|
||||
}
|
||||
if e.Group != "" {
|
||||
return execution.ID(domain.ExecutionTypeEvent, e.Group)
|
||||
group := e.Group
|
||||
if !strings.HasSuffix(e.Group, EventGroupSuffix) {
|
||||
group += EventGroupSuffix
|
||||
}
|
||||
return execution.ID(domain.ExecutionTypeEvent, group)
|
||||
}
|
||||
if e.All {
|
||||
return execution.IDAll(domain.ExecutionTypeEvent)
|
||||
@@ -168,25 +177,43 @@ func (c *Commands) SetExecutionEvent(ctx context.Context, cond *ExecutionEventCo
|
||||
type SetExecution struct {
|
||||
models.ObjectRoot
|
||||
|
||||
Targets []string
|
||||
Includes []string
|
||||
Targets []*execution.Target
|
||||
}
|
||||
|
||||
func (t SetExecution) GetIncludes() []string {
|
||||
includes := make([]string, 0)
|
||||
for i := range t.Targets {
|
||||
if t.Targets[i].Type == domain.ExecutionTargetTypeInclude {
|
||||
includes = append(includes, t.Targets[i].Target)
|
||||
}
|
||||
}
|
||||
return includes
|
||||
}
|
||||
|
||||
func (t SetExecution) GetTargets() []string {
|
||||
targets := make([]string, 0)
|
||||
for i := range t.Targets {
|
||||
if t.Targets[i].Type == domain.ExecutionTargetTypeTarget {
|
||||
targets = append(targets, t.Targets[i].Target)
|
||||
}
|
||||
}
|
||||
return targets
|
||||
}
|
||||
|
||||
func (e *SetExecution) IsValid() error {
|
||||
if len(e.Targets) == 0 && len(e.Includes) == 0 {
|
||||
if len(e.Targets) == 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-56bteot2uj", "Errors.Execution.NoTargets")
|
||||
}
|
||||
if len(e.Targets) > 0 && len(e.Includes) > 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-5zleae34r1", "Errors.Execution.Invalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *SetExecution) Existing(c *Commands, ctx context.Context, resourceOwner string) error {
|
||||
if len(e.Targets) > 0 && !c.existsTargetsByIDs(ctx, e.Targets, resourceOwner) {
|
||||
targets := e.GetTargets()
|
||||
if len(targets) > 0 && !c.existsTargetsByIDs(ctx, targets, resourceOwner) {
|
||||
return zerrors.ThrowNotFound(nil, "COMMAND-17e8fq1ggk", "Errors.Target.NotFound")
|
||||
}
|
||||
if len(e.Includes) > 0 && !c.existsExecutionsByIDs(ctx, e.Includes, resourceOwner) {
|
||||
includes := e.GetIncludes()
|
||||
if len(includes) > 0 && !c.existsExecutionsByIDs(ctx, includes, resourceOwner) {
|
||||
return zerrors.ThrowNotFound(nil, "COMMAND-slgj0l4cdz", "Errors.Execution.IncludeNotFound")
|
||||
}
|
||||
return nil
|
||||
@@ -206,11 +233,10 @@ func (c *Commands) setExecution(ctx context.Context, set *SetExecution, resource
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.pushAppendAndReduce(ctx, wm, execution.NewSetEvent(
|
||||
if err := c.pushAppendAndReduce(ctx, wm, execution.NewSetEventV2(
|
||||
ctx,
|
||||
ExecutionAggregateFromWriteModel(&wm.WriteModel),
|
||||
set.Targets,
|
||||
set.Includes,
|
||||
)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -10,12 +10,13 @@ import (
|
||||
type ExecutionWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
Targets []string
|
||||
Includes []string
|
||||
Targets []string
|
||||
Includes []string
|
||||
ExecutionTargets []*execution.Target
|
||||
}
|
||||
|
||||
func (e *ExecutionWriteModel) Exists() bool {
|
||||
return len(e.Targets) > 0 || len(e.Includes) > 0
|
||||
return len(e.ExecutionTargets) > 0 || len(e.Includes) > 0 || len(e.Targets) > 0
|
||||
}
|
||||
|
||||
func NewExecutionWriteModel(id string, resourceOwner string) *ExecutionWriteModel {
|
||||
@@ -34,9 +35,12 @@ func (wm *ExecutionWriteModel) Reduce() error {
|
||||
case *execution.SetEvent:
|
||||
wm.Targets = e.Targets
|
||||
wm.Includes = e.Includes
|
||||
case *execution.SetEventV2:
|
||||
wm.ExecutionTargets = e.Targets
|
||||
case *execution.RemovedEvent:
|
||||
wm.Targets = nil
|
||||
wm.Includes = nil
|
||||
wm.ExecutionTargets = nil
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
@@ -49,6 +53,7 @@ func (wm *ExecutionWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
AggregateTypes(execution.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(execution.SetEventType,
|
||||
execution.SetEventV2Type,
|
||||
execution.RemovedEventType).
|
||||
Builder()
|
||||
}
|
||||
@@ -91,6 +96,10 @@ func (wm *ExecutionsExistWriteModel) Reduce() error {
|
||||
if !slices.Contains(wm.existingIDs, e.Aggregate().ID) {
|
||||
wm.existingIDs = append(wm.existingIDs, e.Aggregate().ID)
|
||||
}
|
||||
case *execution.SetEventV2:
|
||||
if !slices.Contains(wm.existingIDs, e.Aggregate().ID) {
|
||||
wm.existingIDs = append(wm.existingIDs, e.Aggregate().ID)
|
||||
}
|
||||
case *execution.RemovedEvent:
|
||||
i := slices.Index(wm.existingIDs, e.Aggregate().ID)
|
||||
if i >= 0 {
|
||||
@@ -108,6 +117,7 @@ func (wm *ExecutionsExistWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
AggregateTypes(execution.AggregateType).
|
||||
AggregateIDs(wm.ids...).
|
||||
EventTypes(execution.SetEventType,
|
||||
execution.SetEventV2Type,
|
||||
execution.RemovedEventType).
|
||||
Builder()
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/execution"
|
||||
)
|
||||
@@ -32,10 +33,12 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -53,10 +56,12 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
@@ -65,10 +70,12 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -91,10 +98,12 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -112,10 +121,12 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
@@ -138,24 +149,30 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution1", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution2", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution3", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -174,17 +191,21 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution1", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution2", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
@@ -193,10 +214,12 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution3", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -214,17 +237,21 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution1", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution2", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
@@ -233,10 +260,12 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution3", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -254,24 +283,30 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution1", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution2", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution3", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
@@ -299,24 +334,30 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution1", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution2", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution3", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -334,10 +375,12 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution1", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
@@ -346,17 +389,21 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution2", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution3", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
@@ -385,10 +432,12 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution1", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
@@ -397,17 +446,21 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution2", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
execution.NewSetEvent(context.Background(),
|
||||
execution.NewSetEventV2(context.Background(),
|
||||
execution.NewAggregate("execution3", "org1"),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
[]*execution.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,9 +16,8 @@ type AddTarget struct {
|
||||
|
||||
Name string
|
||||
TargetType domain.TargetType
|
||||
URL string
|
||||
Endpoint string
|
||||
Timeout time.Duration
|
||||
Async bool
|
||||
InterruptOnError bool
|
||||
}
|
||||
|
||||
@@ -29,9 +28,9 @@ func (a *AddTarget) IsValid() error {
|
||||
if a.Timeout == 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-39f35d8uri", "Errors.Target.NoTimeout")
|
||||
}
|
||||
_, err := url.Parse(a.URL)
|
||||
if err != nil || a.URL == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-1r2k6qo6wg", "Errors.Target.InvalidURL")
|
||||
_, err := url.Parse(a.Endpoint)
|
||||
if err != nil || a.Endpoint == "" {
|
||||
return zerrors.ThrowInvalidArgument(err, "COMMAND-1r2k6qo6wg", "Errors.Target.InvalidURL")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -65,9 +64,8 @@ func (c *Commands) AddTarget(ctx context.Context, add *AddTarget, resourceOwner
|
||||
TargetAggregateFromWriteModel(&wm.WriteModel),
|
||||
add.Name,
|
||||
add.TargetType,
|
||||
add.URL,
|
||||
add.Endpoint,
|
||||
add.Timeout,
|
||||
add.Async,
|
||||
add.InterruptOnError,
|
||||
))
|
||||
if err != nil {
|
||||
@@ -84,9 +82,8 @@ type ChangeTarget struct {
|
||||
|
||||
Name *string
|
||||
TargetType *domain.TargetType
|
||||
URL *string
|
||||
Endpoint *string
|
||||
Timeout *time.Duration
|
||||
Async *bool
|
||||
InterruptOnError *bool
|
||||
}
|
||||
|
||||
@@ -100,10 +97,10 @@ func (a *ChangeTarget) IsValid() error {
|
||||
if a.Timeout != nil && *a.Timeout == 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-08b39vdi57", "Errors.Target.NoTimeout")
|
||||
}
|
||||
if a.URL != nil {
|
||||
_, err := url.Parse(*a.URL)
|
||||
if err != nil || *a.URL == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-jsbaera7b6", "Errors.Target.InvalidURL")
|
||||
if a.Endpoint != nil {
|
||||
_, err := url.Parse(*a.Endpoint)
|
||||
if err != nil || *a.Endpoint == "" {
|
||||
return zerrors.ThrowInvalidArgument(err, "COMMAND-jsbaera7b6", "Errors.Target.InvalidURL")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -130,9 +127,8 @@ func (c *Commands) ChangeTarget(ctx context.Context, change *ChangeTarget, resou
|
||||
TargetAggregateFromWriteModel(&existing.WriteModel),
|
||||
change.Name,
|
||||
change.TargetType,
|
||||
change.URL,
|
||||
change.Endpoint,
|
||||
change.Timeout,
|
||||
change.Async,
|
||||
change.InterruptOnError)
|
||||
if changedEvent == nil {
|
||||
return writeModelToObjectDetails(&existing.WriteModel), nil
|
||||
|
||||
@@ -15,9 +15,8 @@ type TargetWriteModel struct {
|
||||
|
||||
Name string
|
||||
TargetType domain.TargetType
|
||||
URL string
|
||||
Endpoint string
|
||||
Timeout time.Duration
|
||||
Async bool
|
||||
InterruptOnError bool
|
||||
|
||||
State domain.TargetState
|
||||
@@ -39,9 +38,8 @@ func (wm *TargetWriteModel) Reduce() error {
|
||||
case *target.AddedEvent:
|
||||
wm.Name = e.Name
|
||||
wm.TargetType = e.TargetType
|
||||
wm.URL = e.URL
|
||||
wm.Endpoint = e.Endpoint
|
||||
wm.Timeout = e.Timeout
|
||||
wm.Async = e.Async
|
||||
wm.State = domain.TargetActive
|
||||
case *target.ChangedEvent:
|
||||
if e.Name != nil {
|
||||
@@ -50,15 +48,12 @@ func (wm *TargetWriteModel) Reduce() error {
|
||||
if e.TargetType != nil {
|
||||
wm.TargetType = *e.TargetType
|
||||
}
|
||||
if e.URL != nil {
|
||||
wm.URL = *e.URL
|
||||
if e.Endpoint != nil {
|
||||
wm.Endpoint = *e.Endpoint
|
||||
}
|
||||
if e.Timeout != nil {
|
||||
wm.Timeout = *e.Timeout
|
||||
}
|
||||
if e.Async != nil {
|
||||
wm.Async = *e.Async
|
||||
}
|
||||
if e.InterruptOnError != nil {
|
||||
wm.InterruptOnError = *e.InterruptOnError
|
||||
}
|
||||
@@ -86,9 +81,8 @@ func (wm *TargetWriteModel) NewChangedEvent(
|
||||
agg *eventstore.Aggregate,
|
||||
name *string,
|
||||
targetType *domain.TargetType,
|
||||
url *string,
|
||||
endpoint *string,
|
||||
timeout *time.Duration,
|
||||
async *bool,
|
||||
interruptOnError *bool,
|
||||
) *target.ChangedEvent {
|
||||
changes := make([]target.Changes, 0)
|
||||
@@ -98,15 +92,12 @@ func (wm *TargetWriteModel) NewChangedEvent(
|
||||
if targetType != nil && wm.TargetType != *targetType {
|
||||
changes = append(changes, target.ChangeTargetType(*targetType))
|
||||
}
|
||||
if url != nil && wm.URL != *url {
|
||||
changes = append(changes, target.ChangeURL(*url))
|
||||
if endpoint != nil && wm.Endpoint != *endpoint {
|
||||
changes = append(changes, target.ChangeEndpoint(*endpoint))
|
||||
}
|
||||
if timeout != nil && wm.Timeout != *timeout {
|
||||
changes = append(changes, target.ChangeTimeout(*timeout))
|
||||
}
|
||||
if async != nil && wm.Async != *async {
|
||||
changes = append(changes, target.ChangeAsync(*async))
|
||||
}
|
||||
if interruptOnError != nil && wm.InterruptOnError != *interruptOnError {
|
||||
changes = append(changes, target.ChangeInterruptOnError(*interruptOnError))
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ func targetAddEvent(aggID, resourceOwner string) *target.AddedEvent {
|
||||
"https://example.com",
|
||||
time.Second,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
|
||||
func TestCommands_AddTarget(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
}
|
||||
type args struct {
|
||||
@@ -41,7 +41,7 @@ func TestCommands_AddTarget(t *testing.T) {
|
||||
{
|
||||
"no resourceowner, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
@@ -55,12 +55,12 @@ func TestCommands_AddTarget(t *testing.T) {
|
||||
{
|
||||
"no name, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
add: &AddTarget{},
|
||||
resourceOwner: "org1",
|
||||
resourceOwner: "instance",
|
||||
},
|
||||
res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -69,50 +69,50 @@ func TestCommands_AddTarget(t *testing.T) {
|
||||
{
|
||||
"no timeout, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
add: &AddTarget{
|
||||
Name: "name",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
resourceOwner: "instance",
|
||||
},
|
||||
res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
"no url, error",
|
||||
"no Endpoint, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
add: &AddTarget{
|
||||
Name: "name",
|
||||
Timeout: time.Second,
|
||||
URL: "",
|
||||
Name: "name",
|
||||
Timeout: time.Second,
|
||||
Endpoint: "",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
resourceOwner: "instance",
|
||||
},
|
||||
res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
"no parsable url, error",
|
||||
"no parsable Endpoint, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
add: &AddTarget{
|
||||
Name: "name",
|
||||
Timeout: time.Second,
|
||||
URL: "://",
|
||||
Name: "name",
|
||||
Timeout: time.Second,
|
||||
Endpoint: "://",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
resourceOwner: "instance",
|
||||
},
|
||||
res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -121,18 +121,17 @@ func TestCommands_AddTarget(t *testing.T) {
|
||||
{
|
||||
"unique constraint failed, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectPushFailed(
|
||||
zerrors.ThrowPreconditionFailed(nil, "id", "name already exists"),
|
||||
target.NewAddedEvent(context.Background(),
|
||||
target.NewAggregate("id1", "org1"),
|
||||
target.NewAggregate("id1", "instance"),
|
||||
"name",
|
||||
domain.TargetTypeWebhook,
|
||||
"https://example.com",
|
||||
time.Second,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -142,11 +141,11 @@ func TestCommands_AddTarget(t *testing.T) {
|
||||
ctx: context.Background(),
|
||||
add: &AddTarget{
|
||||
Name: "name",
|
||||
URL: "https://example.com",
|
||||
Endpoint: "https://example.com",
|
||||
Timeout: time.Second,
|
||||
TargetType: domain.TargetTypeWebhook,
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
resourceOwner: "instance",
|
||||
},
|
||||
res{
|
||||
err: zerrors.IsPreconditionFailed,
|
||||
@@ -155,16 +154,10 @@ func TestCommands_AddTarget(t *testing.T) {
|
||||
{
|
||||
"already existing",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
target.NewAddedEvent(context.Background(),
|
||||
target.NewAggregate("id1", "org1"),
|
||||
"name",
|
||||
domain.TargetTypeWebhook,
|
||||
"https://example.com",
|
||||
time.Second,
|
||||
false,
|
||||
false,
|
||||
eventFromEventPusher(
|
||||
targetAddEvent("target", "instance"),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -176,9 +169,9 @@ func TestCommands_AddTarget(t *testing.T) {
|
||||
Name: "name",
|
||||
TargetType: domain.TargetTypeWebhook,
|
||||
Timeout: time.Second,
|
||||
URL: "https://example.com",
|
||||
Endpoint: "https://example.com",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
resourceOwner: "instance",
|
||||
},
|
||||
res{
|
||||
err: zerrors.IsErrorAlreadyExists,
|
||||
@@ -187,18 +180,10 @@ func TestCommands_AddTarget(t *testing.T) {
|
||||
{
|
||||
"push ok",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
target.NewAddedEvent(context.Background(),
|
||||
target.NewAggregate("id1", "org1"),
|
||||
"name",
|
||||
domain.TargetTypeWebhook,
|
||||
"https://example.com",
|
||||
time.Second,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
targetAddEvent("id1", "instance"),
|
||||
),
|
||||
),
|
||||
idGenerator: mock.ExpectID(t, "id1"),
|
||||
@@ -209,32 +194,28 @@ func TestCommands_AddTarget(t *testing.T) {
|
||||
Name: "name",
|
||||
TargetType: domain.TargetTypeWebhook,
|
||||
Timeout: time.Second,
|
||||
URL: "https://example.com",
|
||||
Endpoint: "https://example.com",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
resourceOwner: "instance",
|
||||
},
|
||||
res{
|
||||
id: "id1",
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
ResourceOwner: "instance",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"push full ok",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
target.NewAddedEvent(context.Background(),
|
||||
target.NewAggregate("id1", "org1"),
|
||||
"name",
|
||||
domain.TargetTypeWebhook,
|
||||
"https://example.com",
|
||||
time.Second,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
func() eventstore.Command {
|
||||
event := targetAddEvent("id1", "instance")
|
||||
event.InterruptOnError = true
|
||||
return event
|
||||
}(),
|
||||
),
|
||||
),
|
||||
idGenerator: mock.ExpectID(t, "id1"),
|
||||
@@ -244,17 +225,16 @@ func TestCommands_AddTarget(t *testing.T) {
|
||||
add: &AddTarget{
|
||||
Name: "name",
|
||||
TargetType: domain.TargetTypeWebhook,
|
||||
URL: "https://example.com",
|
||||
Endpoint: "https://example.com",
|
||||
Timeout: time.Second,
|
||||
Async: true,
|
||||
InterruptOnError: true,
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
resourceOwner: "instance",
|
||||
},
|
||||
res{
|
||||
id: "id1",
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
ResourceOwner: "instance",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -262,7 +242,7 @@ func TestCommands_AddTarget(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
}
|
||||
details, err := c.AddTarget(tt.args.ctx, tt.args.add, tt.args.resourceOwner)
|
||||
@@ -282,7 +262,7 @@ func TestCommands_AddTarget(t *testing.T) {
|
||||
|
||||
func TestCommands_ChangeTarget(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -302,7 +282,7 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
||||
{
|
||||
"resourceowner missing, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
@@ -316,12 +296,12 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
||||
{
|
||||
"id missing, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
change: &ChangeTarget{},
|
||||
resourceOwner: "org1",
|
||||
resourceOwner: "instance",
|
||||
},
|
||||
res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -330,14 +310,14 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
||||
{
|
||||
"name empty, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
change: &ChangeTarget{
|
||||
Name: gu.Ptr(""),
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
resourceOwner: "instance",
|
||||
},
|
||||
res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -346,46 +326,46 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
||||
{
|
||||
"timeout empty, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
change: &ChangeTarget{
|
||||
Timeout: gu.Ptr(time.Duration(0)),
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
resourceOwner: "instance",
|
||||
},
|
||||
res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
"url empty, error",
|
||||
"Endpoint empty, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
change: &ChangeTarget{
|
||||
URL: gu.Ptr(""),
|
||||
Endpoint: gu.Ptr(""),
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
resourceOwner: "instance",
|
||||
},
|
||||
res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
"url not parsable, error",
|
||||
"Endpoint not parsable, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
change: &ChangeTarget{
|
||||
URL: gu.Ptr("://"),
|
||||
Endpoint: gu.Ptr("://"),
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
resourceOwner: "instance",
|
||||
},
|
||||
res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -394,7 +374,7 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
||||
{
|
||||
"not found, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@@ -406,7 +386,7 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
||||
},
|
||||
Name: gu.Ptr("name"),
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
resourceOwner: "instance",
|
||||
},
|
||||
res{
|
||||
err: zerrors.IsNotFound,
|
||||
@@ -415,18 +395,10 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
||||
{
|
||||
"no changes",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
target.NewAddedEvent(context.Background(),
|
||||
target.NewAggregate("id1", "org1"),
|
||||
"name",
|
||||
domain.TargetTypeWebhook,
|
||||
"https://example.com",
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
targetAddEvent("target", "instance"),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -439,35 +411,27 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
||||
},
|
||||
TargetType: gu.Ptr(domain.TargetTypeWebhook),
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
resourceOwner: "instance",
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
ResourceOwner: "instance",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"unique constraint failed, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
target.NewAddedEvent(context.Background(),
|
||||
target.NewAggregate("id1", "org1"),
|
||||
"name",
|
||||
domain.TargetTypeWebhook,
|
||||
"https://example.com",
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
targetAddEvent("target", "instance"),
|
||||
),
|
||||
),
|
||||
expectPushFailed(
|
||||
zerrors.ThrowPreconditionFailed(nil, "id", "name already exists"),
|
||||
target.NewChangedEvent(context.Background(),
|
||||
target.NewAggregate("id1", "org1"),
|
||||
target.NewAggregate("id1", "instance"),
|
||||
[]target.Changes{
|
||||
target.ChangeName("name", "name2"),
|
||||
},
|
||||
@@ -483,7 +447,7 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
||||
},
|
||||
Name: gu.Ptr("name2"),
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
resourceOwner: "instance",
|
||||
},
|
||||
res{
|
||||
err: zerrors.IsPreconditionFailed,
|
||||
@@ -492,23 +456,15 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
||||
{
|
||||
"push ok",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
target.NewAddedEvent(context.Background(),
|
||||
target.NewAggregate("id1", "org1"),
|
||||
"name",
|
||||
domain.TargetTypeWebhook,
|
||||
"https://example.com",
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
targetAddEvent("id1", "instance"),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
target.NewChangedEvent(context.Background(),
|
||||
target.NewAggregate("id1", "org1"),
|
||||
target.NewAggregate("id1", "instance"),
|
||||
[]target.Changes{
|
||||
target.ChangeName("name", "name2"),
|
||||
},
|
||||
@@ -524,40 +480,31 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
||||
},
|
||||
Name: gu.Ptr("name2"),
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
resourceOwner: "instance",
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
ResourceOwner: "instance",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"push full ok",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
target.NewAddedEvent(context.Background(),
|
||||
target.NewAggregate("id1", "org1"),
|
||||
"name",
|
||||
domain.TargetTypeWebhook,
|
||||
"https://example.com",
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
targetAddEvent("id1", "instance"),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
target.NewChangedEvent(context.Background(),
|
||||
target.NewAggregate("id1", "org1"),
|
||||
target.NewAggregate("id1", "instance"),
|
||||
[]target.Changes{
|
||||
target.ChangeName("name", "name2"),
|
||||
target.ChangeURL("https://example2.com"),
|
||||
target.ChangeTargetType(domain.TargetTypeRequestResponse),
|
||||
target.ChangeTimeout(time.Second),
|
||||
target.ChangeAsync(true),
|
||||
target.ChangeEndpoint("https://example2.com"),
|
||||
target.ChangeTargetType(domain.TargetTypeCall),
|
||||
target.ChangeTimeout(10 * time.Second),
|
||||
target.ChangeInterruptOnError(true),
|
||||
},
|
||||
),
|
||||
@@ -571,17 +518,16 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
||||
AggregateID: "id1",
|
||||
},
|
||||
Name: gu.Ptr("name2"),
|
||||
URL: gu.Ptr("https://example2.com"),
|
||||
TargetType: gu.Ptr(domain.TargetTypeRequestResponse),
|
||||
Timeout: gu.Ptr(time.Second),
|
||||
Async: gu.Ptr(true),
|
||||
Endpoint: gu.Ptr("https://example2.com"),
|
||||
TargetType: gu.Ptr(domain.TargetTypeCall),
|
||||
Timeout: gu.Ptr(10 * time.Second),
|
||||
InterruptOnError: gu.Ptr(true),
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
resourceOwner: "instance",
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
ResourceOwner: "instance",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -589,7 +535,7 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
details, err := c.ChangeTarget(tt.args.ctx, tt.args.change, tt.args.resourceOwner)
|
||||
if tt.res.err == nil {
|
||||
@@ -607,7 +553,7 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
||||
|
||||
func TestCommands_DeleteTarget(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -627,12 +573,12 @@ func TestCommands_DeleteTarget(t *testing.T) {
|
||||
{
|
||||
"id missing, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
id: "",
|
||||
resourceOwner: "org1",
|
||||
resourceOwner: "instance",
|
||||
},
|
||||
res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -641,14 +587,14 @@ func TestCommands_DeleteTarget(t *testing.T) {
|
||||
{
|
||||
"not found, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
id: "id1",
|
||||
resourceOwner: "org1",
|
||||
resourceOwner: "instance",
|
||||
},
|
||||
res{
|
||||
err: zerrors.IsNotFound,
|
||||
@@ -657,23 +603,15 @@ func TestCommands_DeleteTarget(t *testing.T) {
|
||||
{
|
||||
"remove ok",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
target.NewAddedEvent(context.Background(),
|
||||
target.NewAggregate("id1", "org1"),
|
||||
"name",
|
||||
domain.TargetTypeWebhook,
|
||||
"https://example.com",
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
targetAddEvent("id1", "instance"),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
target.NewRemovedEvent(context.Background(),
|
||||
target.NewAggregate("id1", "org1"),
|
||||
target.NewAggregate("id1", "instance"),
|
||||
"name",
|
||||
),
|
||||
),
|
||||
@@ -682,11 +620,11 @@ func TestCommands_DeleteTarget(t *testing.T) {
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
id: "id1",
|
||||
resourceOwner: "org1",
|
||||
resourceOwner: "instance",
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
ResourceOwner: "instance",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -694,7 +632,7 @@ func TestCommands_DeleteTarget(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
details, err := c.DeleteTarget(tt.args.ctx, tt.args.id, tt.args.resourceOwner)
|
||||
if tt.res.err == nil {
|
||||
|
||||
@@ -31,3 +31,17 @@ func (e ExecutionType) String() string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ExecutionTargetType uint
|
||||
|
||||
func (s ExecutionTargetType) Valid() bool {
|
||||
return s < executionTargetTypeStateCount
|
||||
}
|
||||
|
||||
const (
|
||||
ExecutionTargetTypeUnspecified ExecutionTargetType = iota
|
||||
ExecutionTargetTypeInclude
|
||||
ExecutionTargetTypeTarget
|
||||
|
||||
executionTargetTypeStateCount
|
||||
)
|
||||
|
||||
@@ -4,7 +4,8 @@ type TargetType uint
|
||||
|
||||
const (
|
||||
TargetTypeWebhook TargetType = iota
|
||||
TargetTypeRequestResponse
|
||||
TargetTypeCall
|
||||
TargetTypeAsync
|
||||
)
|
||||
|
||||
type TargetState int32
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package execution
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
type ContextInfo interface {
|
||||
GetHTTPRequestBody() []byte
|
||||
GetContent() interface{}
|
||||
SetHTTPResponseBody([]byte) error
|
||||
}
|
||||
|
||||
type Target interface {
|
||||
GetTargetID() string
|
||||
IsInterruptOnError() bool
|
||||
GetEndpoint() string
|
||||
GetTargetType() domain.TargetType
|
||||
GetTimeout() time.Duration
|
||||
}
|
||||
|
||||
// CallTargets call a list of targets in order with handling of error and responses
|
||||
func CallTargets(
|
||||
ctx context.Context,
|
||||
targets []Target,
|
||||
info ContextInfo,
|
||||
) (_ interface{}, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer span.EndWithError(err)
|
||||
|
||||
for _, target := range targets {
|
||||
// call the type of target
|
||||
resp, err := CallTarget(ctx, target, info)
|
||||
// handle error if interrupt is set
|
||||
if err != nil && target.IsInterruptOnError() {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp) > 0 {
|
||||
// error in unmarshalling
|
||||
if err := info.SetHTTPResponseBody(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return info.GetContent(), nil
|
||||
}
|
||||
|
||||
type ContextInfoRequest interface {
|
||||
GetHTTPRequestBody() []byte
|
||||
}
|
||||
|
||||
// CallTarget call the desired type of target with handling of responses
|
||||
func CallTarget(
|
||||
ctx context.Context,
|
||||
target Target,
|
||||
info ContextInfoRequest,
|
||||
) (res []byte, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer span.EndWithError(err)
|
||||
|
||||
switch target.GetTargetType() {
|
||||
// get request, ignore response and return request and error for handling in list of targets
|
||||
case domain.TargetTypeWebhook:
|
||||
return nil, webhook(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody())
|
||||
// get request, return response and error
|
||||
case domain.TargetTypeCall:
|
||||
return call(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody())
|
||||
case domain.TargetTypeAsync:
|
||||
go func(target Target, info ContextInfoRequest) {
|
||||
if _, err := call(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody()); err != nil {
|
||||
logging.WithFields("target", target.GetTargetID()).OnError(err).Info(err)
|
||||
}
|
||||
}(target, info)
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, zerrors.ThrowInternal(nil, "EXEC-auqnansr2m", "Errors.Execution.Unknown")
|
||||
}
|
||||
}
|
||||
|
||||
// webhook call a webhook, ignore the response but return the errror
|
||||
func webhook(ctx context.Context, url string, timeout time.Duration, body []byte) error {
|
||||
_, err := call(ctx, url, timeout, body)
|
||||
return err
|
||||
}
|
||||
|
||||
// call function to do a post HTTP request to a desired url with timeout
|
||||
func call(ctx context.Context, url string, timeout time.Duration, body []byte) (_ []byte, err error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() {
|
||||
cancel()
|
||||
span.EndWithError(err)
|
||||
}()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := http.DefaultClient
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Check for success between 200 and 299, redirect 300 to 399 is handled by the client, return error with statusCode >= 400
|
||||
if resp.StatusCode >= 200 && resp.StatusCode <= 299 {
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
return nil, zerrors.ThrowUnknown(nil, "EXEC-dra6yamk98", "Errors.Execution.Failed")
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
package execution
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
)
|
||||
|
||||
var _ Target = &mockTarget{}
|
||||
|
||||
type mockTarget struct {
|
||||
InstanceID string
|
||||
ExecutionID string
|
||||
TargetID string
|
||||
TargetType domain.TargetType
|
||||
Endpoint string
|
||||
Timeout time.Duration
|
||||
InterruptOnError bool
|
||||
}
|
||||
|
||||
func (e *mockTarget) GetTargetID() string {
|
||||
return e.TargetID
|
||||
}
|
||||
func (e *mockTarget) IsInterruptOnError() bool {
|
||||
return e.InterruptOnError
|
||||
}
|
||||
func (e *mockTarget) GetEndpoint() string {
|
||||
return e.Endpoint
|
||||
}
|
||||
func (e *mockTarget) GetTargetType() domain.TargetType {
|
||||
return e.TargetType
|
||||
}
|
||||
func (e *mockTarget) GetTimeout() time.Duration {
|
||||
return e.Timeout
|
||||
}
|
||||
|
||||
func Test_Call(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
timeout time.Duration
|
||||
sleep time.Duration
|
||||
method string
|
||||
body []byte
|
||||
respBody []byte
|
||||
statusCode int
|
||||
}
|
||||
type res struct {
|
||||
body []byte
|
||||
wantErr bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"not ok status",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
timeout: time.Minute,
|
||||
sleep: time.Second,
|
||||
method: http.MethodPost,
|
||||
body: []byte("{\"request\": \"values\"}"),
|
||||
respBody: []byte("{\"response\": \"values\"}"),
|
||||
statusCode: http.StatusBadRequest,
|
||||
},
|
||||
res{
|
||||
wantErr: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"timeout",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
timeout: time.Second,
|
||||
sleep: time.Second,
|
||||
method: http.MethodPost,
|
||||
body: []byte("{\"request\": \"values\"}"),
|
||||
respBody: []byte("{\"response\": \"values\"}"),
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
res{
|
||||
wantErr: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"ok",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
timeout: time.Minute,
|
||||
sleep: time.Second,
|
||||
method: http.MethodPost,
|
||||
body: []byte("{\"request\": \"values\"}"),
|
||||
respBody: []byte("{\"response\": \"values\"}"),
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
res{
|
||||
body: []byte("{\"response\": \"values\"}"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
respBody, err := testServerCall(t,
|
||||
tt.args.method,
|
||||
tt.args.body,
|
||||
tt.args.sleep,
|
||||
tt.args.statusCode,
|
||||
tt.args.respBody,
|
||||
testCall(tt.args.ctx, tt.args.timeout, tt.args.body),
|
||||
)
|
||||
if tt.res.wantErr {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, respBody)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.res.body, respBody)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testCall(ctx context.Context, timeout time.Duration, body []byte) func(string) ([]byte, error) {
|
||||
return func(url string) ([]byte, error) {
|
||||
return call(ctx, url, timeout, body)
|
||||
}
|
||||
}
|
||||
|
||||
func testCallTarget(ctx context.Context,
|
||||
target *mockTarget,
|
||||
info ContextInfoRequest,
|
||||
) func(string) ([]byte, error) {
|
||||
return func(url string) (r []byte, err error) {
|
||||
target.Endpoint = url
|
||||
return CallTarget(ctx, target, info)
|
||||
}
|
||||
}
|
||||
|
||||
func testServerCall(
|
||||
t *testing.T,
|
||||
method string,
|
||||
body []byte,
|
||||
timeout time.Duration,
|
||||
statusCode int,
|
||||
respBody []byte,
|
||||
call func(string) ([]byte, error),
|
||||
) ([]byte, error) {
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
checkRequest(t, r, method, body)
|
||||
|
||||
if statusCode != http.StatusOK {
|
||||
http.Error(w, "error", statusCode)
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(timeout)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if _, err := io.WriteString(w, string(respBody)); err != nil {
|
||||
http.Error(w, "error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(handler))
|
||||
defer server.Close()
|
||||
|
||||
return call(server.URL)
|
||||
}
|
||||
|
||||
func checkRequest(t *testing.T, sent *http.Request, method string, expectedBody []byte) {
|
||||
sentBody, err := io.ReadAll(sent.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedBody, sentBody)
|
||||
require.Equal(t, method, sent.Method)
|
||||
}
|
||||
|
||||
var _ ContextInfoRequest = &mockContextInfoRequest{}
|
||||
|
||||
type request struct {
|
||||
Request string `json:"request"`
|
||||
}
|
||||
|
||||
type mockContextInfoRequest struct {
|
||||
Request *request `json:"request"`
|
||||
}
|
||||
|
||||
func newMockContextInfoRequest(s string) *mockContextInfoRequest {
|
||||
return &mockContextInfoRequest{&request{s}}
|
||||
}
|
||||
|
||||
func (c *mockContextInfoRequest) GetHTTPRequestBody() []byte {
|
||||
data, _ := json.Marshal(c)
|
||||
return data
|
||||
}
|
||||
|
||||
func (c *mockContextInfoRequest) GetContent() []byte {
|
||||
data, _ := json.Marshal(c.Request)
|
||||
return data
|
||||
}
|
||||
|
||||
func Test_CallTarget(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
target *mockTarget
|
||||
sleep time.Duration
|
||||
|
||||
info ContextInfoRequest
|
||||
|
||||
method string
|
||||
body []byte
|
||||
|
||||
respBody []byte
|
||||
statusCode int
|
||||
}
|
||||
type res struct {
|
||||
body []byte
|
||||
wantErr bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"unknown targettype, error",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
sleep: time.Second,
|
||||
method: http.MethodPost,
|
||||
info: newMockContextInfoRequest("content1"),
|
||||
target: &mockTarget{
|
||||
TargetType: 4,
|
||||
},
|
||||
body: []byte("{\"request\":{\"request\":\"content1\"}}"),
|
||||
respBody: []byte("{\"request\":\"content2\"}"),
|
||||
statusCode: http.StatusInternalServerError,
|
||||
},
|
||||
res{
|
||||
wantErr: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"webhook, error",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
sleep: time.Second,
|
||||
method: http.MethodPost,
|
||||
info: newMockContextInfoRequest("content1"),
|
||||
target: &mockTarget{
|
||||
TargetType: domain.TargetTypeWebhook,
|
||||
Timeout: time.Minute,
|
||||
},
|
||||
body: []byte("{\"request\":{\"request\":\"content1\"}}"),
|
||||
respBody: []byte("{\"request\":\"content2\"}"),
|
||||
statusCode: http.StatusInternalServerError,
|
||||
},
|
||||
res{
|
||||
wantErr: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"webhook, ok",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
sleep: time.Second,
|
||||
method: http.MethodPost,
|
||||
info: newMockContextInfoRequest("content1"),
|
||||
target: &mockTarget{
|
||||
TargetType: domain.TargetTypeWebhook,
|
||||
Timeout: time.Minute,
|
||||
},
|
||||
body: []byte("{\"request\":{\"request\":\"content1\"}}"),
|
||||
respBody: []byte("{\"request\":\"content2\"}"),
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
res{
|
||||
body: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
"request response, error",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
sleep: time.Second,
|
||||
method: http.MethodPost,
|
||||
info: newMockContextInfoRequest("content1"),
|
||||
target: &mockTarget{
|
||||
TargetType: domain.TargetTypeCall,
|
||||
Timeout: time.Minute,
|
||||
},
|
||||
body: []byte("{\"request\":{\"request\":\"content1\"}}"),
|
||||
respBody: []byte("{\"request\":\"content2\"}"),
|
||||
statusCode: http.StatusInternalServerError,
|
||||
},
|
||||
res{
|
||||
wantErr: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"request response, ok",
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
sleep: time.Second,
|
||||
method: http.MethodPost,
|
||||
info: newMockContextInfoRequest("content1"),
|
||||
target: &mockTarget{
|
||||
TargetType: domain.TargetTypeCall,
|
||||
Timeout: time.Minute,
|
||||
},
|
||||
body: []byte("{\"request\":{\"request\":\"content1\"}}"),
|
||||
respBody: []byte("{\"request\":\"content2\"}"),
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
res{
|
||||
body: []byte("{\"request\":\"content2\"}"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
respBody, err := testServerCall(t,
|
||||
tt.args.method,
|
||||
tt.args.body,
|
||||
tt.args.sleep,
|
||||
tt.args.statusCode,
|
||||
tt.args.respBody,
|
||||
testCallTarget(tt.args.ctx, tt.args.target, tt.args.info),
|
||||
)
|
||||
if tt.res.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, tt.res.body, respBody)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,9 @@ func AssertListDetails[D ListDetailsMsg](t testing.TB, expected, actual D) {
|
||||
|
||||
assert.Equal(t, wantDetails.GetTotalResult(), gotDetails.GetTotalResult())
|
||||
|
||||
gotCD := gotDetails.GetTimestamp().AsTime()
|
||||
wantCD := time.Now()
|
||||
assert.WithinRange(t, gotCD, wantCD.Add(-time.Minute), wantCD.Add(time.Minute))
|
||||
if wantDetails.GetTimestamp() != nil {
|
||||
gotCD := gotDetails.GetTimestamp().AsTime()
|
||||
wantCD := time.Now()
|
||||
assert.WithinRange(t, gotCD, wantCD.Add(-time.Minute), wantCD.Add(time.Minute))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,39 +522,32 @@ func (s *Tester) CreateProjectMembership(t *testing.T, ctx context.Context, proj
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func (s *Tester) CreateTarget(ctx context.Context, t *testing.T) *action.CreateTargetResponse {
|
||||
req := &action.CreateTargetRequest{
|
||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
Url: "https://example.com",
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
func (s *Tester) CreateTarget(ctx context.Context, t *testing.T, name, endpoint string, ty domain.TargetType, interrupt bool) *action.CreateTargetResponse {
|
||||
nameSet := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||
if name != "" {
|
||||
nameSet = name
|
||||
}
|
||||
target, err := s.Client.ActionV3.CreateTarget(ctx, req)
|
||||
require.NoError(t, err)
|
||||
return target
|
||||
}
|
||||
|
||||
func (s *Tester) CreateTargetWithNameAndType(ctx context.Context, t *testing.T, name string, async bool, interrupt bool) *action.CreateTargetResponse {
|
||||
req := &action.CreateTargetRequest{
|
||||
Name: name,
|
||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
Url: "https://example.com",
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
Name: nameSet,
|
||||
Endpoint: endpoint,
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
}
|
||||
if async {
|
||||
req.ExecutionType = &action.CreateTargetRequest_IsAsync{
|
||||
IsAsync: true,
|
||||
switch ty {
|
||||
case domain.TargetTypeWebhook:
|
||||
req.TargetType = &action.CreateTargetRequest_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
InterruptOnError: interrupt,
|
||||
},
|
||||
}
|
||||
}
|
||||
if interrupt {
|
||||
req.ExecutionType = &action.CreateTargetRequest_InterruptOnError{
|
||||
InterruptOnError: true,
|
||||
case domain.TargetTypeCall:
|
||||
req.TargetType = &action.CreateTargetRequest_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: interrupt,
|
||||
},
|
||||
}
|
||||
case domain.TargetTypeAsync:
|
||||
req.TargetType = &action.CreateTargetRequest_RestAsync{
|
||||
RestAsync: &action.SetRESTAsync{},
|
||||
}
|
||||
}
|
||||
target, err := s.Client.ActionV3.CreateTarget(ctx, req)
|
||||
@@ -562,16 +555,22 @@ func (s *Tester) CreateTargetWithNameAndType(ctx context.Context, t *testing.T,
|
||||
return target
|
||||
}
|
||||
|
||||
func (s *Tester) SetExecution(ctx context.Context, t *testing.T, cond *action.Condition, targets []string, includes []string) *action.SetExecutionResponse {
|
||||
func (s *Tester) SetExecution(ctx context.Context, t *testing.T, cond *action.Condition, targets []*action.ExecutionTargetType) *action.SetExecutionResponse {
|
||||
target, err := s.Client.ActionV3.SetExecution(ctx, &action.SetExecutionRequest{
|
||||
Condition: cond,
|
||||
Targets: targets,
|
||||
Includes: includes,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return target
|
||||
}
|
||||
|
||||
func (s *Tester) DeleteExecution(ctx context.Context, t *testing.T, cond *action.Condition) {
|
||||
_, err := s.Client.ActionV3.DeleteExecution(ctx, &action.DeleteExecutionRequest{
|
||||
Condition: cond,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func (s *Tester) CreateUserSchema(ctx context.Context, t *testing.T) *schema.CreateUserSchemaResponse {
|
||||
return s.CreateUserSchemaWithType(ctx, t, fmt.Sprint(time.Now().UnixNano()+1))
|
||||
}
|
||||
|
||||
+311
-79
@@ -3,7 +3,10 @@ package query
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
|
||||
@@ -11,6 +14,8 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query/projection"
|
||||
exec "github.com/zitadel/zitadel/internal/repository/execution"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
@@ -23,18 +28,10 @@ var (
|
||||
name: projection.ExecutionIDCol,
|
||||
table: executionTable,
|
||||
}
|
||||
ExecutionColumnCreationDate = Column{
|
||||
name: projection.ExecutionCreationDateCol,
|
||||
table: executionTable,
|
||||
}
|
||||
ExecutionColumnChangeDate = Column{
|
||||
name: projection.ExecutionChangeDateCol,
|
||||
table: executionTable,
|
||||
}
|
||||
ExecutionColumnResourceOwner = Column{
|
||||
name: projection.ExecutionResourceOwnerCol,
|
||||
table: executionTable,
|
||||
}
|
||||
ExecutionColumnInstanceID = Column{
|
||||
name: projection.ExecutionInstanceIDCol,
|
||||
table: executionTable,
|
||||
@@ -43,14 +40,33 @@ var (
|
||||
name: projection.ExecutionSequenceCol,
|
||||
table: executionTable,
|
||||
}
|
||||
ExecutionColumnTargets = Column{
|
||||
name: projection.ExecutionTargetsCol,
|
||||
table: executionTable,
|
||||
|
||||
executionTargetsTable = table{
|
||||
name: projection.ExecutionTable + "_" + projection.ExecutionTargetSuffix,
|
||||
instanceIDCol: projection.ExecutionTargetInstanceIDCol,
|
||||
}
|
||||
ExecutionColumnIncludes = Column{
|
||||
name: projection.ExecutionIncludesCol,
|
||||
table: executionTable,
|
||||
executionTargetsTableAlias = executionTargetsTable.setAlias("execution_targets")
|
||||
ExecutionTargetsColumnInstanceID = Column{
|
||||
name: projection.ExecutionTargetInstanceIDCol,
|
||||
table: executionTargetsTableAlias,
|
||||
}
|
||||
ExecutionTargetsColumnExecutionID = Column{
|
||||
name: projection.ExecutionTargetExecutionIDCol,
|
||||
table: executionTargetsTableAlias,
|
||||
}
|
||||
executionTargetsListCol = Column{
|
||||
name: "targets",
|
||||
table: executionTargetsTableAlias,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed execution_targets.sql
|
||||
executionTargetsQuery string
|
||||
//go:embed targets_by_execution_id.sql
|
||||
TargetsByExecutionIDQuery string
|
||||
//go:embed targets_by_execution_ids.sql
|
||||
TargetsByExecutionIDsQuery string
|
||||
)
|
||||
|
||||
type Executions struct {
|
||||
@@ -66,8 +82,7 @@ type Execution struct {
|
||||
ID string
|
||||
domain.ObjectDetails
|
||||
|
||||
Targets database.TextArray[string]
|
||||
Includes database.TextArray[string]
|
||||
Targets []*exec.Target
|
||||
}
|
||||
|
||||
type ExecutionSearchQueries struct {
|
||||
@@ -108,84 +123,301 @@ func NewExecutionTypeSearchQuery(t domain.ExecutionType) (SearchQuery, error) {
|
||||
return NewTextQuery(ExecutionColumnID, t.String(), TextStartsWith)
|
||||
}
|
||||
|
||||
func NewExecutionTargetSearchQuery(value string) (SearchQuery, error) {
|
||||
return NewTextQuery(ExecutionColumnTargets, value, TextListContains)
|
||||
func NewTargetSearchQuery(target string) (SearchQuery, error) {
|
||||
data, err := targetItemJSONB(domain.ExecutionTargetTypeTarget, target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewListContains(executionTargetsListCol, data)
|
||||
}
|
||||
|
||||
func NewExecutionIncludeSearchQuery(value string) (SearchQuery, error) {
|
||||
return NewTextQuery(ExecutionColumnIncludes, value, TextListContains)
|
||||
func NewIncludeSearchQuery(include string) (SearchQuery, error) {
|
||||
data, err := targetItemJSONB(domain.ExecutionTargetTypeInclude, include)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewListContains(executionTargetsListCol, data)
|
||||
}
|
||||
|
||||
func prepareExecutionsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(rows *sql.Rows) (*Executions, error)) {
|
||||
return sq.Select(
|
||||
ExecutionColumnID.identifier(),
|
||||
ExecutionColumnChangeDate.identifier(),
|
||||
ExecutionColumnResourceOwner.identifier(),
|
||||
ExecutionColumnSequence.identifier(),
|
||||
ExecutionColumnTargets.identifier(),
|
||||
ExecutionColumnIncludes.identifier(),
|
||||
countColumn.identifier(),
|
||||
).From(executionTable.identifier()).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
func(rows *sql.Rows) (*Executions, error) {
|
||||
executions := make([]*Execution, 0)
|
||||
var count uint64
|
||||
for rows.Next() {
|
||||
execution := new(Execution)
|
||||
err := rows.Scan(
|
||||
&execution.ID,
|
||||
&execution.EventDate,
|
||||
&execution.ResourceOwner,
|
||||
&execution.Sequence,
|
||||
&execution.Targets,
|
||||
&execution.Includes,
|
||||
&count,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
executions = append(executions, execution)
|
||||
}
|
||||
// marshall executionTargets into the same JSONB structure as in the SQL queries
|
||||
func targetItemJSONB(t domain.ExecutionTargetType, targetItem string) ([]byte, error) {
|
||||
var target *executionTarget
|
||||
switch t {
|
||||
case domain.ExecutionTargetTypeTarget:
|
||||
target = &executionTarget{Target: targetItem}
|
||||
case domain.ExecutionTargetTypeInclude:
|
||||
target = &executionTarget{Include: targetItem}
|
||||
case domain.ExecutionTargetTypeUnspecified:
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
return json.Marshal([]*executionTarget{target})
|
||||
}
|
||||
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-72xfx5jlj7", "Errors.Query.CloseRows")
|
||||
}
|
||||
// TargetsByExecutionID query list of targets for best match of a list of IDs, for example:
|
||||
// [ "request/zitadel.action.v3alpha.ActionService/GetTargetByID",
|
||||
// "request/zitadel.action.v3alpha.ActionService",
|
||||
// "request" ]
|
||||
func (q *Queries) TargetsByExecutionID(ctx context.Context, ids []string) (execution []*ExecutionTarget, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.End() }()
|
||||
|
||||
return &Executions{
|
||||
Executions: executions,
|
||||
SearchResponse: SearchResponse{
|
||||
Count: count,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||
if instanceID == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err = q.client.QueryContext(ctx,
|
||||
func(rows *sql.Rows) error {
|
||||
execution, err = scanExecutionTargets(rows)
|
||||
return err
|
||||
},
|
||||
TargetsByExecutionIDQuery,
|
||||
instanceID,
|
||||
database.TextArray[string](ids),
|
||||
)
|
||||
return execution, err
|
||||
}
|
||||
|
||||
// TargetsByExecutionIDs query list of targets for best matches of 2 separate lists of IDs, combined for performance, for example:
|
||||
// [ "request/zitadel.action.v3alpha.ActionService/GetTargetByID",
|
||||
// "request/zitadel.action.v3alpha.ActionService",
|
||||
// "request" ]
|
||||
// and
|
||||
// [ "response/zitadel.action.v3alpha.ActionService/GetTargetByID",
|
||||
// "response/zitadel.action.v3alpha.ActionService",
|
||||
// "response" ]
|
||||
func (q *Queries) TargetsByExecutionIDs(ctx context.Context, ids1, ids2 []string) (execution []*ExecutionTarget, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.End() }()
|
||||
|
||||
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||
if instanceID == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err = q.client.QueryContext(ctx,
|
||||
func(rows *sql.Rows) error {
|
||||
execution, err = scanExecutionTargets(rows)
|
||||
return err
|
||||
},
|
||||
TargetsByExecutionIDsQuery,
|
||||
instanceID,
|
||||
database.TextArray[string](ids1),
|
||||
database.TextArray[string](ids2),
|
||||
)
|
||||
return execution, err
|
||||
}
|
||||
|
||||
func prepareExecutionQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(row *sql.Row) (*Execution, error)) {
|
||||
return sq.Select(
|
||||
ExecutionColumnInstanceID.identifier(),
|
||||
ExecutionColumnID.identifier(),
|
||||
ExecutionColumnChangeDate.identifier(),
|
||||
ExecutionColumnResourceOwner.identifier(),
|
||||
ExecutionColumnSequence.identifier(),
|
||||
ExecutionColumnTargets.identifier(),
|
||||
ExecutionColumnIncludes.identifier(),
|
||||
executionTargetsListCol.identifier(),
|
||||
).From(executionTable.identifier()).
|
||||
Join("(" + executionTargetsQuery + ") AS " + executionTargetsTableAlias.alias + " ON " +
|
||||
ExecutionTargetsColumnInstanceID.identifier() + " = " + ExecutionColumnInstanceID.identifier() + " AND " +
|
||||
ExecutionTargetsColumnExecutionID.identifier() + " = " + ExecutionColumnID.identifier(),
|
||||
).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
func(row *sql.Row) (*Execution, error) {
|
||||
execution := new(Execution)
|
||||
err := row.Scan(
|
||||
&execution.ID,
|
||||
&execution.EventDate,
|
||||
&execution.ResourceOwner,
|
||||
&execution.Sequence,
|
||||
&execution.Targets,
|
||||
&execution.Includes,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, zerrors.ThrowNotFound(err, "QUERY-qzn1xycesh", "Errors.Execution.NotFound")
|
||||
}
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-f8sjvm4tb8", "Errors.Internal")
|
||||
scanExecution
|
||||
}
|
||||
|
||||
func prepareExecutionsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(rows *sql.Rows) (*Executions, error)) {
|
||||
return sq.Select(
|
||||
ExecutionColumnInstanceID.identifier(),
|
||||
ExecutionColumnID.identifier(),
|
||||
ExecutionColumnChangeDate.identifier(),
|
||||
ExecutionColumnSequence.identifier(),
|
||||
executionTargetsListCol.identifier(),
|
||||
countColumn.identifier(),
|
||||
).From(executionTable.identifier()).
|
||||
Join("(" + executionTargetsQuery + ") AS " + executionTargetsTableAlias.alias + " ON " +
|
||||
ExecutionTargetsColumnInstanceID.identifier() + " = " + ExecutionColumnInstanceID.identifier() + " AND " +
|
||||
ExecutionTargetsColumnExecutionID.identifier() + " = " + ExecutionColumnID.identifier(),
|
||||
).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
scanExecutions
|
||||
}
|
||||
|
||||
type executionTarget struct {
|
||||
Position int `json:"position,omitempty"`
|
||||
Include string `json:"include,omitempty"`
|
||||
Target string `json:"target,omitempty"`
|
||||
}
|
||||
|
||||
func scanExecution(row *sql.Row) (*Execution, error) {
|
||||
execution := new(Execution)
|
||||
targets := make([]byte, 0)
|
||||
|
||||
err := row.Scan(
|
||||
&execution.ResourceOwner,
|
||||
&execution.ID,
|
||||
&execution.EventDate,
|
||||
&execution.Sequence,
|
||||
&targets,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, zerrors.ThrowNotFound(err, "QUERY-qzn1xycesh", "Errors.Execution.NotFound")
|
||||
}
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-f8sjvm4tb8", "Errors.Internal")
|
||||
}
|
||||
|
||||
executionTargets := make([]*executionTarget, 0)
|
||||
if err := json.Unmarshal(targets, &executionTargets); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
execution.Targets = make([]*exec.Target, len(executionTargets))
|
||||
for i := range executionTargets {
|
||||
if executionTargets[i].Target != "" {
|
||||
execution.Targets[i] = &exec.Target{Type: domain.ExecutionTargetTypeTarget, Target: executionTargets[i].Target}
|
||||
}
|
||||
if executionTargets[i].Include != "" {
|
||||
execution.Targets[i] = &exec.Target{Type: domain.ExecutionTargetTypeInclude, Target: executionTargets[i].Include}
|
||||
}
|
||||
}
|
||||
|
||||
return execution, nil
|
||||
}
|
||||
|
||||
func executionTargetsUnmarshal(data []byte) ([]*exec.Target, error) {
|
||||
executionTargets := make([]*executionTarget, 0)
|
||||
if err := json.Unmarshal(data, &executionTargets); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
targets := make([]*exec.Target, len(executionTargets))
|
||||
// position starts with 1
|
||||
for _, item := range executionTargets {
|
||||
if item.Target != "" {
|
||||
targets[item.Position-1] = &exec.Target{Type: domain.ExecutionTargetTypeTarget, Target: item.Target}
|
||||
}
|
||||
if item.Include != "" {
|
||||
targets[item.Position-1] = &exec.Target{Type: domain.ExecutionTargetTypeInclude, Target: item.Include}
|
||||
}
|
||||
}
|
||||
return targets, nil
|
||||
}
|
||||
|
||||
func scanExecutions(rows *sql.Rows) (*Executions, error) {
|
||||
executions := make([]*Execution, 0)
|
||||
var count uint64
|
||||
|
||||
for rows.Next() {
|
||||
execution := new(Execution)
|
||||
targets := make([]byte, 0)
|
||||
|
||||
err := rows.Scan(
|
||||
&execution.ResourceOwner,
|
||||
&execution.ID,
|
||||
&execution.EventDate,
|
||||
&execution.Sequence,
|
||||
&targets,
|
||||
&count,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, zerrors.ThrowNotFound(err, "QUERY-tbrmno85vp", "Errors.Execution.NotFound")
|
||||
}
|
||||
return execution, nil
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-tyw2ydsj84", "Errors.Internal")
|
||||
}
|
||||
|
||||
execution.Targets, err = executionTargetsUnmarshal(targets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
executions = append(executions, execution)
|
||||
}
|
||||
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-yhka3fs3mw", "Errors.Query.CloseRows")
|
||||
}
|
||||
|
||||
return &Executions{
|
||||
Executions: executions,
|
||||
SearchResponse: SearchResponse{
|
||||
Count: count,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
type ExecutionTarget struct {
|
||||
InstanceID string
|
||||
ExecutionID string
|
||||
TargetID string
|
||||
TargetType domain.TargetType
|
||||
Endpoint string
|
||||
Timeout time.Duration
|
||||
InterruptOnError bool
|
||||
}
|
||||
|
||||
func (e *ExecutionTarget) GetExecutionID() string {
|
||||
return e.ExecutionID
|
||||
}
|
||||
func (e *ExecutionTarget) GetTargetID() string {
|
||||
return e.TargetID
|
||||
}
|
||||
func (e *ExecutionTarget) IsInterruptOnError() bool {
|
||||
return e.InterruptOnError
|
||||
}
|
||||
func (e *ExecutionTarget) GetEndpoint() string {
|
||||
return e.Endpoint
|
||||
}
|
||||
func (e *ExecutionTarget) GetTargetType() domain.TargetType {
|
||||
return e.TargetType
|
||||
}
|
||||
func (e *ExecutionTarget) GetTimeout() time.Duration {
|
||||
return e.Timeout
|
||||
}
|
||||
|
||||
func scanExecutionTargets(rows *sql.Rows) ([]*ExecutionTarget, error) {
|
||||
targets := make([]*ExecutionTarget, 0)
|
||||
for rows.Next() {
|
||||
target := new(ExecutionTarget)
|
||||
|
||||
var (
|
||||
instanceID = &sql.NullString{}
|
||||
executionID = &sql.NullString{}
|
||||
targetID = &sql.NullString{}
|
||||
targetType = &sql.NullInt32{}
|
||||
endpoint = &sql.NullString{}
|
||||
timeout = &sql.NullInt64{}
|
||||
interruptOnError = &sql.NullBool{}
|
||||
)
|
||||
|
||||
err := rows.Scan(
|
||||
executionID,
|
||||
instanceID,
|
||||
targetID,
|
||||
targetType,
|
||||
endpoint,
|
||||
timeout,
|
||||
interruptOnError,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
target.InstanceID = instanceID.String
|
||||
target.ExecutionID = executionID.String
|
||||
target.TargetID = targetID.String
|
||||
target.TargetType = domain.TargetType(targetType.Int32)
|
||||
target.Endpoint = endpoint.String
|
||||
target.Timeout = time.Duration(timeout.Int64)
|
||||
target.InterruptOnError = interruptOnError.Bool
|
||||
|
||||
targets = append(targets, target)
|
||||
}
|
||||
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-37ardr0pki", "Errors.Query.CloseRows")
|
||||
}
|
||||
|
||||
return targets, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
SELECT instance_id,
|
||||
execution_id,
|
||||
JSONB_AGG(
|
||||
JSON_OBJECT(
|
||||
'position' : position,
|
||||
'include' : include,
|
||||
'target' : target_id
|
||||
)
|
||||
) as targets
|
||||
FROM projections.executions1_targets
|
||||
GROUP BY instance_id, execution_id
|
||||
@@ -8,44 +8,56 @@ import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
exec "github.com/zitadel/zitadel/internal/repository/execution"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
var (
|
||||
prepareExecutionsStmt = `SELECT projections.executions.id,` +
|
||||
` projections.executions.change_date,` +
|
||||
` projections.executions.resource_owner,` +
|
||||
` projections.executions.sequence,` +
|
||||
` projections.executions.targets,` +
|
||||
` projections.executions.includes,` +
|
||||
prepareExecutionsStmt = `SELECT projections.executions1.instance_id,` +
|
||||
` projections.executions1.id,` +
|
||||
` projections.executions1.change_date,` +
|
||||
` projections.executions1.sequence,` +
|
||||
` execution_targets.targets,` +
|
||||
` COUNT(*) OVER ()` +
|
||||
` FROM projections.executions`
|
||||
` FROM projections.executions1` +
|
||||
` JOIN (` +
|
||||
`SELECT instance_id, execution_id, JSONB_AGG( JSON_OBJECT( 'position' : position, 'include' : include, 'target' : target_id ) ) as targets` +
|
||||
` FROM projections.executions1_targets` +
|
||||
` GROUP BY instance_id, execution_id` +
|
||||
`)` +
|
||||
` AS execution_targets` +
|
||||
` ON execution_targets.instance_id = projections.executions1.instance_id` +
|
||||
` AND execution_targets.execution_id = projections.executions1.id`
|
||||
prepareExecutionsCols = []string{
|
||||
"instance_id",
|
||||
"id",
|
||||
"change_date",
|
||||
"resource_owner",
|
||||
"sequence",
|
||||
"targets",
|
||||
"includes",
|
||||
"count",
|
||||
}
|
||||
|
||||
prepareExecutionStmt = `SELECT projections.executions.id,` +
|
||||
` projections.executions.change_date,` +
|
||||
` projections.executions.resource_owner,` +
|
||||
` projections.executions.sequence,` +
|
||||
` projections.executions.targets,` +
|
||||
` projections.executions.includes` +
|
||||
` FROM projections.executions`
|
||||
prepareExecutionStmt = `SELECT projections.executions1.instance_id,` +
|
||||
` projections.executions1.id,` +
|
||||
` projections.executions1.change_date,` +
|
||||
` projections.executions1.sequence,` +
|
||||
` execution_targets.targets` +
|
||||
` FROM projections.executions1` +
|
||||
` JOIN (` +
|
||||
`SELECT instance_id, execution_id, JSONB_AGG( JSON_OBJECT( 'position' : position, 'include' : include, 'target' : target_id ) ) as targets` +
|
||||
` FROM projections.executions1_targets` +
|
||||
` GROUP BY instance_id, execution_id` +
|
||||
`)` +
|
||||
` AS execution_targets` +
|
||||
` ON execution_targets.instance_id = projections.executions1.instance_id` +
|
||||
` AND execution_targets.execution_id = projections.executions1.id`
|
||||
prepareExecutionCols = []string{
|
||||
"instance_id",
|
||||
"id",
|
||||
"change_date",
|
||||
"resource_owner",
|
||||
"sequence",
|
||||
"targets",
|
||||
"includes",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -81,12 +93,11 @@ func Test_ExecutionPrepares(t *testing.T) {
|
||||
prepareExecutionsCols,
|
||||
[][]driver.Value{
|
||||
{
|
||||
"ro",
|
||||
"id",
|
||||
testNow,
|
||||
"ro",
|
||||
uint64(20211109),
|
||||
database.TextArray[string]{"target"},
|
||||
database.TextArray[string]{"include"},
|
||||
[]byte(`[{"position" : 1, "target" : "target"}, {"position" : 2, "include" : "include"}]`),
|
||||
},
|
||||
},
|
||||
),
|
||||
@@ -103,8 +114,10 @@ func Test_ExecutionPrepares(t *testing.T) {
|
||||
ResourceOwner: "ro",
|
||||
Sequence: 20211109,
|
||||
},
|
||||
Targets: database.TextArray[string]{"target"},
|
||||
Includes: database.TextArray[string]{"include"},
|
||||
Targets: []*exec.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -118,20 +131,18 @@ func Test_ExecutionPrepares(t *testing.T) {
|
||||
prepareExecutionsCols,
|
||||
[][]driver.Value{
|
||||
{
|
||||
"ro",
|
||||
"id-1",
|
||||
testNow,
|
||||
"ro",
|
||||
uint64(20211109),
|
||||
database.TextArray[string]{"target1"},
|
||||
database.TextArray[string]{"include1"},
|
||||
[]byte(`[{"position" : 1, "target" : "target"}, {"position" : 2, "include" : "include"}]`),
|
||||
},
|
||||
{
|
||||
"ro",
|
||||
"id-2",
|
||||
testNow,
|
||||
"ro",
|
||||
uint64(20211110),
|
||||
database.TextArray[string]{"target2"},
|
||||
database.TextArray[string]{"include2"},
|
||||
[]byte(`[{"position" : 2, "target" : "target"}, {"position" : 1, "include" : "include"}]`),
|
||||
},
|
||||
},
|
||||
),
|
||||
@@ -148,8 +159,10 @@ func Test_ExecutionPrepares(t *testing.T) {
|
||||
ResourceOwner: "ro",
|
||||
Sequence: 20211109,
|
||||
},
|
||||
Targets: database.TextArray[string]{"target1"},
|
||||
Includes: database.TextArray[string]{"include1"},
|
||||
Targets: []*exec.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "id-2",
|
||||
@@ -158,8 +171,10 @@ func Test_ExecutionPrepares(t *testing.T) {
|
||||
ResourceOwner: "ro",
|
||||
Sequence: 20211110,
|
||||
},
|
||||
Targets: database.TextArray[string]{"target2"},
|
||||
Includes: database.TextArray[string]{"include2"},
|
||||
Targets: []*exec.Target{
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -207,12 +222,11 @@ func Test_ExecutionPrepares(t *testing.T) {
|
||||
regexp.QuoteMeta(prepareExecutionStmt),
|
||||
prepareExecutionCols,
|
||||
[]driver.Value{
|
||||
"ro",
|
||||
"id",
|
||||
testNow,
|
||||
"ro",
|
||||
uint64(20211109),
|
||||
database.TextArray[string]{"target"},
|
||||
database.TextArray[string]{"include"},
|
||||
[]byte(`[{"position" : 1, "target" : "target"}, {"position" : 2, "include" : "include"}]`),
|
||||
},
|
||||
),
|
||||
},
|
||||
@@ -223,8 +237,10 @@ func Test_ExecutionPrepares(t *testing.T) {
|
||||
ResourceOwner: "ro",
|
||||
Sequence: 20211109,
|
||||
},
|
||||
Targets: database.TextArray[string]{"target"},
|
||||
Includes: database.TextArray[string]{"include"},
|
||||
Targets: []*exec.Target{
|
||||
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ package projection
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
old_handler "github.com/zitadel/zitadel/internal/eventstore/handler"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
||||
@@ -11,15 +12,19 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ExecutionTable = "projections.executions"
|
||||
ExecutionIDCol = "id"
|
||||
ExecutionCreationDateCol = "creation_date"
|
||||
ExecutionChangeDateCol = "change_date"
|
||||
ExecutionResourceOwnerCol = "resource_owner"
|
||||
ExecutionInstanceIDCol = "instance_id"
|
||||
ExecutionSequenceCol = "sequence"
|
||||
ExecutionTargetsCol = "targets"
|
||||
ExecutionIncludesCol = "includes"
|
||||
ExecutionTable = "projections.executions1"
|
||||
ExecutionIDCol = "id"
|
||||
ExecutionCreationDateCol = "creation_date"
|
||||
ExecutionChangeDateCol = "change_date"
|
||||
ExecutionInstanceIDCol = "instance_id"
|
||||
ExecutionSequenceCol = "sequence"
|
||||
|
||||
ExecutionTargetSuffix = "targets"
|
||||
ExecutionTargetExecutionIDCol = "execution_id"
|
||||
ExecutionTargetInstanceIDCol = "instance_id"
|
||||
ExecutionTargetPositionCol = "position"
|
||||
ExecutionTargetTargetIDCol = "target_id"
|
||||
ExecutionTargetIncludeCol = "include"
|
||||
)
|
||||
|
||||
type executionProjection struct{}
|
||||
@@ -33,19 +38,28 @@ func (*executionProjection) Name() string {
|
||||
}
|
||||
|
||||
func (*executionProjection) Init() *old_handler.Check {
|
||||
return handler.NewTableCheck(
|
||||
return handler.NewMultiTableCheck(
|
||||
handler.NewTable([]*handler.InitColumn{
|
||||
handler.NewColumn(ExecutionIDCol, handler.ColumnTypeText),
|
||||
handler.NewColumn(ExecutionCreationDateCol, handler.ColumnTypeTimestamp),
|
||||
handler.NewColumn(ExecutionChangeDateCol, handler.ColumnTypeTimestamp),
|
||||
handler.NewColumn(ExecutionResourceOwnerCol, handler.ColumnTypeText),
|
||||
handler.NewColumn(ExecutionInstanceIDCol, handler.ColumnTypeText),
|
||||
handler.NewColumn(ExecutionSequenceCol, handler.ColumnTypeInt64),
|
||||
handler.NewColumn(ExecutionTargetsCol, handler.ColumnTypeTextArray, handler.Nullable()),
|
||||
handler.NewColumn(ExecutionIncludesCol, handler.ColumnTypeTextArray, handler.Nullable()),
|
||||
handler.NewColumn(ExecutionInstanceIDCol, handler.ColumnTypeText),
|
||||
},
|
||||
handler.NewPrimaryKey(ExecutionInstanceIDCol, ExecutionIDCol),
|
||||
),
|
||||
handler.NewSuffixedTable([]*handler.InitColumn{
|
||||
handler.NewColumn(ExecutionTargetInstanceIDCol, handler.ColumnTypeText),
|
||||
handler.NewColumn(ExecutionTargetExecutionIDCol, handler.ColumnTypeText),
|
||||
handler.NewColumn(ExecutionTargetPositionCol, handler.ColumnTypeInt64),
|
||||
handler.NewColumn(ExecutionTargetIncludeCol, handler.ColumnTypeText, handler.Nullable()),
|
||||
handler.NewColumn(ExecutionTargetTargetIDCol, handler.ColumnTypeText, handler.Nullable()),
|
||||
},
|
||||
handler.NewPrimaryKey(ExecutionTargetInstanceIDCol, ExecutionTargetExecutionIDCol, ExecutionTargetPositionCol),
|
||||
ExecutionTargetSuffix,
|
||||
handler.WithForeignKey(handler.NewForeignKey("execution", []string{ExecutionTargetInstanceIDCol, ExecutionTargetExecutionIDCol}, []string{ExecutionInstanceIDCol, ExecutionIDCol})),
|
||||
handler.WithIndex(handler.NewIndex("execution", []string{ExecutionTargetInstanceIDCol, ExecutionTargetExecutionIDCol})),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -55,7 +69,7 @@ func (p *executionProjection) Reducers() []handler.AggregateReducer {
|
||||
Aggregate: exec.AggregateType,
|
||||
EventReducers: []handler.EventReducer{
|
||||
{
|
||||
Event: exec.SetEventType,
|
||||
Event: exec.SetEventV2Type,
|
||||
Reduce: p.reduceExecutionSet,
|
||||
},
|
||||
{
|
||||
@@ -77,21 +91,65 @@ func (p *executionProjection) Reducers() []handler.AggregateReducer {
|
||||
}
|
||||
|
||||
func (p *executionProjection) reduceExecutionSet(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, err := assertEvent[*exec.SetEvent](event)
|
||||
e, err := assertEvent[*exec.SetEventV2](event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
columns := []handler.Column{
|
||||
handler.NewCol(ExecutionInstanceIDCol, e.Aggregate().InstanceID),
|
||||
handler.NewCol(ExecutionIDCol, e.Aggregate().ID),
|
||||
handler.NewCol(ExecutionResourceOwnerCol, e.Aggregate().ResourceOwner),
|
||||
handler.NewCol(ExecutionCreationDateCol, handler.OnlySetValueOnInsert(ExecutionTable, e.CreationDate())),
|
||||
handler.NewCol(ExecutionChangeDateCol, e.CreationDate()),
|
||||
handler.NewCol(ExecutionSequenceCol, e.Sequence()),
|
||||
handler.NewCol(ExecutionTargetsCol, e.Targets),
|
||||
handler.NewCol(ExecutionIncludesCol, e.Includes),
|
||||
|
||||
stmts := []func(eventstore.Event) handler.Exec{
|
||||
handler.AddUpsertStatement(
|
||||
[]handler.Column{
|
||||
handler.NewCol(ExecutionInstanceIDCol, e.Aggregate().InstanceID),
|
||||
handler.NewCol(ExecutionIDCol, e.Aggregate().ID),
|
||||
},
|
||||
[]handler.Column{
|
||||
handler.NewCol(ExecutionInstanceIDCol, e.Aggregate().InstanceID),
|
||||
handler.NewCol(ExecutionIDCol, e.Aggregate().ID),
|
||||
handler.NewCol(ExecutionCreationDateCol, handler.OnlySetValueOnInsert(ExecutionTable, e.CreationDate())),
|
||||
handler.NewCol(ExecutionChangeDateCol, e.CreationDate()),
|
||||
handler.NewCol(ExecutionSequenceCol, e.Sequence()),
|
||||
},
|
||||
),
|
||||
// cleanup execution targets to re-insert them
|
||||
handler.AddDeleteStatement(
|
||||
[]handler.Condition{
|
||||
handler.NewCond(ExecutionTargetInstanceIDCol, e.Aggregate().InstanceID),
|
||||
handler.NewCond(ExecutionTargetExecutionIDCol, e.Aggregate().ID),
|
||||
},
|
||||
handler.WithTableSuffix(ExecutionTargetSuffix),
|
||||
),
|
||||
}
|
||||
return handler.NewUpsertStatement(e, columns[0:2], columns), nil
|
||||
|
||||
if len(e.Targets) > 0 {
|
||||
for i, target := range e.Targets {
|
||||
var targetStr, includeStr string
|
||||
switch target.Type {
|
||||
case domain.ExecutionTargetTypeTarget:
|
||||
targetStr = target.Target
|
||||
case domain.ExecutionTargetTypeInclude:
|
||||
includeStr = target.Target
|
||||
case domain.ExecutionTargetTypeUnspecified:
|
||||
continue
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
stmts = append(stmts,
|
||||
handler.AddCreateStatement(
|
||||
[]handler.Column{
|
||||
handler.NewCol(ExecutionTargetInstanceIDCol, e.Aggregate().InstanceID),
|
||||
handler.NewCol(ExecutionTargetExecutionIDCol, e.Aggregate().ID),
|
||||
handler.NewCol(ExecutionTargetPositionCol, i+1),
|
||||
handler.NewCol(ExecutionTargetIncludeCol, includeStr),
|
||||
handler.NewCol(ExecutionTargetTargetIDCol, targetStr),
|
||||
},
|
||||
handler.WithTableSuffix(ExecutionTargetSuffix),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return handler.NewMultiStatement(e, stmts...), nil
|
||||
}
|
||||
|
||||
func (p *executionProjection) reduceExecutionRemoved(event eventstore.Event) (*handler.Statement, error) {
|
||||
@@ -99,8 +157,8 @@ func (p *executionProjection) reduceExecutionRemoved(event eventstore.Event) (*h
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return handler.NewDeleteStatement(
|
||||
e,
|
||||
|
||||
return handler.NewDeleteStatement(e,
|
||||
[]handler.Condition{
|
||||
handler.NewCond(ExecutionInstanceIDCol, e.Aggregate().InstanceID),
|
||||
handler.NewCond(ExecutionIDCol, e.Aggregate().ID),
|
||||
|
||||
@@ -25,11 +25,11 @@ func TestExecutionProjection_reduces(t *testing.T) {
|
||||
args: args{
|
||||
event: getEvent(
|
||||
testEvent(
|
||||
exec.SetEventType,
|
||||
exec.SetEventV2Type,
|
||||
exec.AggregateType,
|
||||
[]byte(`{"targets": ["target"], "includes": ["include"]}`),
|
||||
[]byte(`{"targets": [{"type":2,"target":"target"},{"type":1,"target":"include"}]}`),
|
||||
),
|
||||
eventstore.GenericEventMapper[exec.SetEvent],
|
||||
eventstore.GenericEventMapper[exec.SetEventV2],
|
||||
),
|
||||
},
|
||||
reduce: (&executionProjection{}).reduceExecutionSet,
|
||||
@@ -39,16 +39,40 @@ func TestExecutionProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.executions (instance_id, id, resource_owner, creation_date, change_date, sequence, targets, includes) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (instance_id, id) DO UPDATE SET (resource_owner, creation_date, change_date, sequence, targets, includes) = (EXCLUDED.resource_owner, projections.executions.creation_date, EXCLUDED.change_date, EXCLUDED.sequence, EXCLUDED.targets, EXCLUDED.includes)",
|
||||
expectedStmt: "INSERT INTO projections.executions1 (instance_id, id, creation_date, change_date, sequence) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (instance_id, id) DO UPDATE SET (creation_date, change_date, sequence) = (projections.executions1.creation_date, EXCLUDED.change_date, EXCLUDED.sequence)",
|
||||
expectedArgs: []interface{}{
|
||||
"instance-id",
|
||||
"agg-id",
|
||||
"ro-id",
|
||||
anyArg{},
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
[]string{"target"},
|
||||
[]string{"include"},
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.executions1_targets WHERE (instance_id = $1) AND (execution_id = $2)",
|
||||
expectedArgs: []interface{}{
|
||||
"instance-id",
|
||||
"agg-id",
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.executions1_targets (instance_id, execution_id, position, include, target_id) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedArgs: []interface{}{
|
||||
"instance-id",
|
||||
"agg-id",
|
||||
1,
|
||||
"",
|
||||
"target",
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.executions1_targets (instance_id, execution_id, position, include, target_id) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedArgs: []interface{}{
|
||||
"instance-id",
|
||||
"agg-id",
|
||||
2,
|
||||
"include",
|
||||
"",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -74,7 +98,7 @@ func TestExecutionProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.executions WHERE (instance_id = $1) AND (id = $2)",
|
||||
expectedStmt: "DELETE FROM projections.executions1 WHERE (instance_id = $1) AND (id = $2)",
|
||||
expectedArgs: []interface{}{
|
||||
"instance-id",
|
||||
"agg-id",
|
||||
@@ -103,7 +127,7 @@ func TestExecutionProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.executions WHERE (instance_id = $1)",
|
||||
expectedStmt: "DELETE FROM projections.executions1 WHERE (instance_id = $1)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
},
|
||||
|
||||
@@ -269,8 +269,8 @@ func newProjectionsList() {
|
||||
RestrictionsProjection,
|
||||
SystemFeatureProjection,
|
||||
InstanceFeatureProjection,
|
||||
ExecutionProjection,
|
||||
TargetProjection,
|
||||
ExecutionProjection,
|
||||
UserSchemaProjection,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
TargetTable = "projections.targets"
|
||||
TargetTable = "projections.targets1"
|
||||
TargetIDCol = "id"
|
||||
TargetCreationDateCol = "creation_date"
|
||||
TargetChangeDateCol = "change_date"
|
||||
@@ -20,9 +20,8 @@ const (
|
||||
TargetSequenceCol = "sequence"
|
||||
TargetNameCol = "name"
|
||||
TargetTargetType = "target_type"
|
||||
TargetURLCol = "url"
|
||||
TargetEndpointCol = "endpoint"
|
||||
TargetTimeoutCol = "timeout"
|
||||
TargetAsyncCol = "async"
|
||||
TargetInterruptOnErrorCol = "interrupt_on_error"
|
||||
)
|
||||
|
||||
@@ -47,10 +46,9 @@ func (*targetProjection) Init() *old_handler.Check {
|
||||
handler.NewColumn(TargetTargetType, handler.ColumnTypeEnum),
|
||||
handler.NewColumn(TargetSequenceCol, handler.ColumnTypeInt64),
|
||||
handler.NewColumn(TargetNameCol, handler.ColumnTypeText),
|
||||
handler.NewColumn(TargetURLCol, handler.ColumnTypeText, handler.Default("")),
|
||||
handler.NewColumn(TargetTimeoutCol, handler.ColumnTypeInt64, handler.Default(0)),
|
||||
handler.NewColumn(TargetAsyncCol, handler.ColumnTypeBool, handler.Default(false)),
|
||||
handler.NewColumn(TargetInterruptOnErrorCol, handler.ColumnTypeBool, handler.Default(false)),
|
||||
handler.NewColumn(TargetEndpointCol, handler.ColumnTypeText),
|
||||
handler.NewColumn(TargetTimeoutCol, handler.ColumnTypeInt64),
|
||||
handler.NewColumn(TargetInterruptOnErrorCol, handler.ColumnTypeBool),
|
||||
},
|
||||
handler.NewPrimaryKey(TargetInstanceIDCol, TargetIDCol),
|
||||
),
|
||||
@@ -103,10 +101,9 @@ func (p *targetProjection) reduceTargetAdded(event eventstore.Event) (*handler.S
|
||||
handler.NewCol(TargetChangeDateCol, e.CreationDate()),
|
||||
handler.NewCol(TargetSequenceCol, e.Sequence()),
|
||||
handler.NewCol(TargetNameCol, e.Name),
|
||||
handler.NewCol(TargetURLCol, e.URL),
|
||||
handler.NewCol(TargetEndpointCol, e.Endpoint),
|
||||
handler.NewCol(TargetTargetType, e.TargetType),
|
||||
handler.NewCol(TargetTimeoutCol, e.Timeout),
|
||||
handler.NewCol(TargetAsyncCol, e.Async),
|
||||
handler.NewCol(TargetInterruptOnErrorCol, e.InterruptOnError),
|
||||
},
|
||||
), nil
|
||||
@@ -128,15 +125,12 @@ func (p *targetProjection) reduceTargetChanged(event eventstore.Event) (*handler
|
||||
if e.TargetType != nil {
|
||||
values = append(values, handler.NewCol(TargetTargetType, *e.TargetType))
|
||||
}
|
||||
if e.URL != nil {
|
||||
values = append(values, handler.NewCol(TargetURLCol, *e.URL))
|
||||
if e.Endpoint != nil {
|
||||
values = append(values, handler.NewCol(TargetEndpointCol, *e.Endpoint))
|
||||
}
|
||||
if e.Timeout != nil {
|
||||
values = append(values, handler.NewCol(TargetTimeoutCol, *e.Timeout))
|
||||
}
|
||||
if e.Async != nil {
|
||||
values = append(values, handler.NewCol(TargetAsyncCol, *e.Async))
|
||||
}
|
||||
if e.InterruptOnError != nil {
|
||||
values = append(values, handler.NewCol(TargetInterruptOnErrorCol, *e.InterruptOnError))
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestTargetProjection_reduces(t *testing.T) {
|
||||
testEvent(
|
||||
target.AddedEventType,
|
||||
target.AggregateType,
|
||||
[]byte(`{"name": "name", "targetType":0, "url":"https://example.com", "timeout": 3000000000, "async": true, "interruptOnError": true}`),
|
||||
[]byte(`{"name": "name", "targetType":0, "endpoint":"https://example.com", "timeout": 3000000000, "async": true, "interruptOnError": true}`),
|
||||
),
|
||||
eventstore.GenericEventMapper[target.AddedEvent],
|
||||
),
|
||||
@@ -41,7 +41,7 @@ func TestTargetProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.targets (instance_id, resource_owner, id, creation_date, change_date, sequence, name, url, target_type, timeout, async, interrupt_on_error) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)",
|
||||
expectedStmt: "INSERT INTO projections.targets1 (instance_id, resource_owner, id, creation_date, change_date, sequence, name, endpoint, target_type, timeout, interrupt_on_error) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)",
|
||||
expectedArgs: []interface{}{
|
||||
"instance-id",
|
||||
"ro-id",
|
||||
@@ -54,7 +54,6 @@ func TestTargetProjection_reduces(t *testing.T) {
|
||||
domain.TargetTypeWebhook,
|
||||
3 * time.Second,
|
||||
true,
|
||||
true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -68,7 +67,7 @@ func TestTargetProjection_reduces(t *testing.T) {
|
||||
testEvent(
|
||||
target.ChangedEventType,
|
||||
target.AggregateType,
|
||||
[]byte(`{"name": "name2", "targetType":0, "url":"https://example.com", "timeout": 3000000000, "async": true, "interruptOnError": true}`),
|
||||
[]byte(`{"name": "name2", "targetType":0, "endpoint":"https://example.com", "timeout": 3000000000, "async": true, "interruptOnError": true}`),
|
||||
),
|
||||
eventstore.GenericEventMapper[target.ChangedEvent],
|
||||
),
|
||||
@@ -80,7 +79,7 @@ func TestTargetProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.targets SET (change_date, sequence, resource_owner, name, target_type, url, timeout, async, interrupt_on_error) = ($1, $2, $3, $4, $5, $6, $7, $8, $9) WHERE (instance_id = $10) AND (id = $11)",
|
||||
expectedStmt: "UPDATE projections.targets1 SET (change_date, sequence, resource_owner, name, target_type, endpoint, timeout, interrupt_on_error) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (instance_id = $9) AND (id = $10)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@@ -90,7 +89,6 @@ func TestTargetProjection_reduces(t *testing.T) {
|
||||
"https://example.com",
|
||||
3 * time.Second,
|
||||
true,
|
||||
true,
|
||||
"instance-id",
|
||||
"agg-id",
|
||||
},
|
||||
@@ -118,7 +116,7 @@ func TestTargetProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.targets WHERE (instance_id = $1) AND (id = $2)",
|
||||
expectedStmt: "DELETE FROM projections.targets1 WHERE (instance_id = $1) AND (id = $2)",
|
||||
expectedArgs: []interface{}{
|
||||
"instance-id",
|
||||
"agg-id",
|
||||
@@ -147,7 +145,7 @@ func TestTargetProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.targets WHERE (instance_id = $1)",
|
||||
expectedStmt: "DELETE FROM projections.targets1 WHERE (instance_id = $1)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
},
|
||||
|
||||
@@ -478,31 +478,31 @@ func (q *SubSelect) comp() sq.Sqlizer {
|
||||
return selectQuery
|
||||
}
|
||||
|
||||
type ListQuery struct {
|
||||
type listQuery struct {
|
||||
Column Column
|
||||
Data interface{}
|
||||
Compare ListComparison
|
||||
}
|
||||
|
||||
func NewListQuery(column Column, value interface{}, compare ListComparison) (*ListQuery, error) {
|
||||
func NewListQuery(column Column, value interface{}, compare ListComparison) (*listQuery, error) {
|
||||
if compare < 0 || compare >= listCompareMax {
|
||||
return nil, ErrInvalidCompare
|
||||
}
|
||||
if column.isZero() {
|
||||
return nil, ErrMissingColumn
|
||||
}
|
||||
return &ListQuery{
|
||||
return &listQuery{
|
||||
Column: column,
|
||||
Data: value,
|
||||
Compare: compare,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (q *ListQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
||||
func (q *listQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
||||
return query.Where(q.comp())
|
||||
}
|
||||
|
||||
func (q *ListQuery) comp() sq.Sqlizer {
|
||||
func (q *listQuery) comp() sq.Sqlizer {
|
||||
if q.Compare != ListIn {
|
||||
return nil
|
||||
}
|
||||
@@ -517,7 +517,7 @@ func (q *ListQuery) comp() sq.Sqlizer {
|
||||
return sq.Eq{q.Column.identifier(): q.Data}
|
||||
}
|
||||
|
||||
func (q *ListQuery) Col() Column {
|
||||
func (q *listQuery) Col() Column {
|
||||
return q.Column
|
||||
}
|
||||
|
||||
@@ -720,6 +720,25 @@ type listContains struct {
|
||||
args interface{}
|
||||
}
|
||||
|
||||
func NewListContains(c Column, value interface{}) (*listContains, error) {
|
||||
return &listContains{
|
||||
col: c,
|
||||
args: value,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (q *listContains) Col() Column {
|
||||
return q.col
|
||||
}
|
||||
|
||||
func (q *listContains) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
||||
return query.Where(q.comp())
|
||||
}
|
||||
|
||||
func (q *listContains) ToSql() (string, []interface{}, error) {
|
||||
return q.col.identifier() + " @> ? ", []interface{}{q.args}, nil
|
||||
}
|
||||
|
||||
func (q *listContains) comp() sq.Sqlizer {
|
||||
return q
|
||||
}
|
||||
|
||||
@@ -521,7 +521,7 @@ func TestNewListQuery(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *ListQuery
|
||||
want *listQuery
|
||||
wantErr func(error) bool
|
||||
}{
|
||||
{
|
||||
@@ -575,7 +575,7 @@ func TestNewListQuery(t *testing.T) {
|
||||
data: []interface{}{"hurst"},
|
||||
compare: ListIn,
|
||||
},
|
||||
want: &ListQuery{
|
||||
want: &listQuery{
|
||||
Column: testCol,
|
||||
Data: []interface{}{"hurst"},
|
||||
Compare: ListIn,
|
||||
@@ -588,7 +588,7 @@ func TestNewListQuery(t *testing.T) {
|
||||
data: &SubSelect{Column: testCol, Queries: []SearchQuery{&textQuery{testCol, "horst1", TextEquals}}},
|
||||
compare: ListIn,
|
||||
},
|
||||
want: &ListQuery{
|
||||
want: &listQuery{
|
||||
Column: testCol,
|
||||
Data: &SubSelect{Column: testCol, Queries: []SearchQuery{&textQuery{testCol, "horst1", TextEquals}}},
|
||||
Compare: ListIn,
|
||||
@@ -751,7 +751,7 @@ func TestListQuery_comp(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &ListQuery{
|
||||
s := &listQuery{
|
||||
Column: tt.fields.Column,
|
||||
Data: tt.fields.Data,
|
||||
Compare: tt.fields.Compare,
|
||||
|
||||
@@ -52,17 +52,13 @@ var (
|
||||
table: targetTable,
|
||||
}
|
||||
TargetColumnURL = Column{
|
||||
name: projection.TargetURLCol,
|
||||
name: projection.TargetEndpointCol,
|
||||
table: targetTable,
|
||||
}
|
||||
TargetColumnTimeout = Column{
|
||||
name: projection.TargetTimeoutCol,
|
||||
table: targetTable,
|
||||
}
|
||||
TargetColumnAsync = Column{
|
||||
name: projection.TargetAsyncCol,
|
||||
table: targetTable,
|
||||
}
|
||||
TargetColumnInterruptOnError = Column{
|
||||
name: projection.TargetInterruptOnErrorCol,
|
||||
table: targetTable,
|
||||
@@ -84,9 +80,8 @@ type Target struct {
|
||||
|
||||
Name string
|
||||
TargetType domain.TargetType
|
||||
URL string
|
||||
Endpoint string
|
||||
Timeout time.Duration
|
||||
Async bool
|
||||
InterruptOnError bool
|
||||
}
|
||||
|
||||
@@ -138,7 +133,6 @@ func prepareTargetsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuil
|
||||
TargetColumnTargetType.identifier(),
|
||||
TargetColumnTimeout.identifier(),
|
||||
TargetColumnURL.identifier(),
|
||||
TargetColumnAsync.identifier(),
|
||||
TargetColumnInterruptOnError.identifier(),
|
||||
countColumn.identifier(),
|
||||
).From(targetTable.identifier()).
|
||||
@@ -156,8 +150,7 @@ func prepareTargetsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuil
|
||||
&target.Name,
|
||||
&target.TargetType,
|
||||
&target.Timeout,
|
||||
&target.URL,
|
||||
&target.Async,
|
||||
&target.Endpoint,
|
||||
&target.InterruptOnError,
|
||||
&count,
|
||||
)
|
||||
@@ -190,7 +183,6 @@ func prepareTargetQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuild
|
||||
TargetColumnTargetType.identifier(),
|
||||
TargetColumnTimeout.identifier(),
|
||||
TargetColumnURL.identifier(),
|
||||
TargetColumnAsync.identifier(),
|
||||
TargetColumnInterruptOnError.identifier(),
|
||||
).From(targetTable.identifier()).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
@@ -204,8 +196,7 @@ func prepareTargetQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuild
|
||||
&target.Name,
|
||||
&target.TargetType,
|
||||
&target.Timeout,
|
||||
&target.URL,
|
||||
&target.Async,
|
||||
&target.Endpoint,
|
||||
&target.InterruptOnError,
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@@ -14,18 +14,17 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
prepareTargetsStmt = `SELECT projections.targets.id,` +
|
||||
` projections.targets.change_date,` +
|
||||
` projections.targets.resource_owner,` +
|
||||
` projections.targets.sequence,` +
|
||||
` projections.targets.name,` +
|
||||
` projections.targets.target_type,` +
|
||||
` projections.targets.timeout,` +
|
||||
` projections.targets.url,` +
|
||||
` projections.targets.async,` +
|
||||
` projections.targets.interrupt_on_error,` +
|
||||
prepareTargetsStmt = `SELECT projections.targets1.id,` +
|
||||
` projections.targets1.change_date,` +
|
||||
` projections.targets1.resource_owner,` +
|
||||
` projections.targets1.sequence,` +
|
||||
` projections.targets1.name,` +
|
||||
` projections.targets1.target_type,` +
|
||||
` projections.targets1.timeout,` +
|
||||
` projections.targets1.endpoint,` +
|
||||
` projections.targets1.interrupt_on_error,` +
|
||||
` COUNT(*) OVER ()` +
|
||||
` FROM projections.targets`
|
||||
` FROM projections.targets1`
|
||||
prepareTargetsCols = []string{
|
||||
"id",
|
||||
"change_date",
|
||||
@@ -34,23 +33,21 @@ var (
|
||||
"name",
|
||||
"target_type",
|
||||
"timeout",
|
||||
"url",
|
||||
"async",
|
||||
"endpoint",
|
||||
"interrupt_on_error",
|
||||
"count",
|
||||
}
|
||||
|
||||
prepareTargetStmt = `SELECT projections.targets.id,` +
|
||||
` projections.targets.change_date,` +
|
||||
` projections.targets.resource_owner,` +
|
||||
` projections.targets.sequence,` +
|
||||
` projections.targets.name,` +
|
||||
` projections.targets.target_type,` +
|
||||
` projections.targets.timeout,` +
|
||||
` projections.targets.url,` +
|
||||
` projections.targets.async,` +
|
||||
` projections.targets.interrupt_on_error` +
|
||||
` FROM projections.targets`
|
||||
prepareTargetStmt = `SELECT projections.targets1.id,` +
|
||||
` projections.targets1.change_date,` +
|
||||
` projections.targets1.resource_owner,` +
|
||||
` projections.targets1.sequence,` +
|
||||
` projections.targets1.name,` +
|
||||
` projections.targets1.target_type,` +
|
||||
` projections.targets1.timeout,` +
|
||||
` projections.targets1.endpoint,` +
|
||||
` projections.targets1.interrupt_on_error` +
|
||||
` FROM projections.targets1`
|
||||
prepareTargetCols = []string{
|
||||
"id",
|
||||
"change_date",
|
||||
@@ -59,8 +56,7 @@ var (
|
||||
"name",
|
||||
"target_type",
|
||||
"timeout",
|
||||
"url",
|
||||
"async",
|
||||
"endpoint",
|
||||
"interrupt_on_error",
|
||||
}
|
||||
)
|
||||
@@ -106,7 +102,6 @@ func Test_TargetPrepares(t *testing.T) {
|
||||
1 * time.Second,
|
||||
"https://example.com",
|
||||
true,
|
||||
true,
|
||||
},
|
||||
},
|
||||
),
|
||||
@@ -126,8 +121,7 @@ func Test_TargetPrepares(t *testing.T) {
|
||||
Name: "target-name",
|
||||
TargetType: domain.TargetTypeWebhook,
|
||||
Timeout: 1 * time.Second,
|
||||
URL: "https://example.com",
|
||||
Async: true,
|
||||
Endpoint: "https://example.com",
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
@@ -151,7 +145,6 @@ func Test_TargetPrepares(t *testing.T) {
|
||||
1 * time.Second,
|
||||
"https://example.com",
|
||||
true,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"id-2",
|
||||
@@ -163,14 +156,24 @@ func Test_TargetPrepares(t *testing.T) {
|
||||
1 * time.Second,
|
||||
"https://example.com",
|
||||
false,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"id-3",
|
||||
testNow,
|
||||
"ro",
|
||||
uint64(20211110),
|
||||
"target-name3",
|
||||
domain.TargetTypeAsync,
|
||||
1 * time.Second,
|
||||
"https://example.com",
|
||||
false,
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &Targets{
|
||||
SearchResponse: SearchResponse{
|
||||
Count: 2,
|
||||
Count: 3,
|
||||
},
|
||||
Targets: []*Target{
|
||||
{
|
||||
@@ -183,9 +186,8 @@ func Test_TargetPrepares(t *testing.T) {
|
||||
Name: "target-name1",
|
||||
TargetType: domain.TargetTypeWebhook,
|
||||
Timeout: 1 * time.Second,
|
||||
URL: "https://example.com",
|
||||
Async: true,
|
||||
InterruptOnError: false,
|
||||
Endpoint: "https://example.com",
|
||||
InterruptOnError: true,
|
||||
},
|
||||
{
|
||||
ID: "id-2",
|
||||
@@ -197,9 +199,21 @@ func Test_TargetPrepares(t *testing.T) {
|
||||
Name: "target-name2",
|
||||
TargetType: domain.TargetTypeWebhook,
|
||||
Timeout: 1 * time.Second,
|
||||
URL: "https://example.com",
|
||||
Async: false,
|
||||
InterruptOnError: true,
|
||||
Endpoint: "https://example.com",
|
||||
InterruptOnError: false,
|
||||
},
|
||||
{
|
||||
ID: "id-3",
|
||||
ObjectDetails: domain.ObjectDetails{
|
||||
EventDate: testNow,
|
||||
ResourceOwner: "ro",
|
||||
Sequence: 20211110,
|
||||
},
|
||||
Name: "target-name3",
|
||||
TargetType: domain.TargetTypeAsync,
|
||||
Timeout: 1 * time.Second,
|
||||
Endpoint: "https://example.com",
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -256,7 +270,6 @@ func Test_TargetPrepares(t *testing.T) {
|
||||
1 * time.Second,
|
||||
"https://example.com",
|
||||
true,
|
||||
false,
|
||||
},
|
||||
),
|
||||
},
|
||||
@@ -270,9 +283,8 @@ func Test_TargetPrepares(t *testing.T) {
|
||||
Name: "target-name",
|
||||
TargetType: domain.TargetTypeWebhook,
|
||||
Timeout: 1 * time.Second,
|
||||
URL: "https://example.com",
|
||||
Async: true,
|
||||
InterruptOnError: false,
|
||||
Endpoint: "https://example.com",
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
WITH RECURSIVE
|
||||
dissolved_execution_targets(execution_id, instance_id, position, "include", "target_id")
|
||||
AS (SELECT execution_id
|
||||
, instance_id
|
||||
, ARRAY [position]
|
||||
, "include"
|
||||
, "target_id"
|
||||
FROM matched_targets_and_includes
|
||||
UNION ALL
|
||||
SELECT e.execution_id
|
||||
, p.instance_id
|
||||
, e.position || p.position
|
||||
, p."include"
|
||||
, p."target_id"
|
||||
FROM dissolved_execution_targets e
|
||||
JOIN projections.executions1_targets p
|
||||
ON e.instance_id = p.instance_id
|
||||
AND e.include IS NOT NULL
|
||||
AND e.include = p.execution_id),
|
||||
matched AS (SELECT *
|
||||
FROM projections.executions1
|
||||
WHERE instance_id = $1
|
||||
AND id = ANY($2)
|
||||
ORDER BY id DESC
|
||||
LIMIT 1),
|
||||
matched_targets_and_includes AS (SELECT pos.*
|
||||
FROM matched m
|
||||
JOIN
|
||||
projections.executions1_targets pos
|
||||
ON m.id = pos.execution_id
|
||||
AND m.instance_id = pos.instance_id
|
||||
ORDER BY execution_id,
|
||||
position)
|
||||
select e.execution_id, e.instance_id, e.target_id, t.target_type, t.endpoint, t.timeout, t.interrupt_on_error
|
||||
FROM dissolved_execution_targets e
|
||||
JOIN projections.targets1 t
|
||||
ON e.instance_id = t.instance_id
|
||||
AND e.target_id = t.id
|
||||
WHERE "include" = ''
|
||||
ORDER BY position DESC;
|
||||
@@ -0,0 +1,47 @@
|
||||
WITH RECURSIVE
|
||||
dissolved_execution_targets(execution_id, instance_id, position, "include", "target_id")
|
||||
AS (SELECT execution_id
|
||||
, instance_id
|
||||
, ARRAY [position]
|
||||
, "include"
|
||||
, "target_id"
|
||||
FROM matched_targets_and_includes
|
||||
UNION ALL
|
||||
SELECT e.execution_id
|
||||
, p.instance_id
|
||||
, e.position || p.position
|
||||
, p."include"
|
||||
, p."target_id"
|
||||
FROM dissolved_execution_targets e
|
||||
JOIN projections.executions1_targets p
|
||||
ON e.instance_id = p.instance_id
|
||||
AND e.include IS NOT NULL
|
||||
AND e.include = p.execution_id),
|
||||
matched AS ((SELECT *
|
||||
FROM projections.executions1
|
||||
WHERE instance_id = $1
|
||||
AND id = ANY($2)
|
||||
ORDER BY id DESC
|
||||
LIMIT 1)
|
||||
UNION ALL
|
||||
(SELECT *
|
||||
FROM projections.executions1
|
||||
WHERE instance_id = $1
|
||||
AND id = ANY($3)
|
||||
ORDER BY id DESC
|
||||
LIMIT 1)),
|
||||
matched_targets_and_includes AS (SELECT pos.*
|
||||
FROM matched m
|
||||
JOIN
|
||||
projections.executions1_targets pos
|
||||
ON m.id = pos.execution_id
|
||||
AND m.instance_id = pos.instance_id
|
||||
ORDER BY execution_id,
|
||||
position)
|
||||
select e.execution_id, e.instance_id, e.target_id, t.target_type, t.endpoint, t.timeout, t.interrupt_on_error
|
||||
FROM dissolved_execution_targets e
|
||||
JOIN projections.targets1 t
|
||||
ON e.instance_id = t.instance_id
|
||||
AND e.target_id = t.id
|
||||
WHERE "include" = ''
|
||||
ORDER BY position DESC;
|
||||
@@ -23,7 +23,10 @@ func NewAggregate(aggrID, instanceID string) *eventstore.Aggregate {
|
||||
}
|
||||
|
||||
func ID(executionType domain.ExecutionType, value string) string {
|
||||
return strings.Join([]string{executionType.String(), value}, ".")
|
||||
if strings.HasPrefix(value, "/") {
|
||||
return strings.Join([]string{executionType.String(), value}, "")
|
||||
}
|
||||
return strings.Join([]string{executionType.String(), value}, "/")
|
||||
}
|
||||
|
||||
func IDAll(executionType domain.ExecutionType) string {
|
||||
|
||||
@@ -4,5 +4,6 @@ import "github.com/zitadel/zitadel/internal/eventstore"
|
||||
|
||||
func init() {
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, SetEventType, eventstore.GenericEventMapper[SetEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, SetEventV2Type, eventstore.GenericEventMapper[SetEventV2])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, RemovedEventType, eventstore.GenericEventMapper[RemovedEvent])
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@ package execution
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
const (
|
||||
eventTypePrefix eventstore.EventType = "execution."
|
||||
SetEventType = eventTypePrefix + "set"
|
||||
SetEventV2Type = eventTypePrefix + "v2.set"
|
||||
RemovedEventType = eventTypePrefix + "removed"
|
||||
)
|
||||
|
||||
@@ -31,18 +33,39 @@ func (e *SetEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewSetEvent(
|
||||
type SetEventV2 struct {
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
|
||||
Targets []*Target `json:"targets"`
|
||||
}
|
||||
|
||||
func (e *SetEventV2) SetBaseEvent(b *eventstore.BaseEvent) {
|
||||
e.BaseEvent = b
|
||||
}
|
||||
|
||||
func (e *SetEventV2) Payload() any {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *SetEventV2) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
type Target struct {
|
||||
Type domain.ExecutionTargetType `json:"type"`
|
||||
Target string `json:"target"`
|
||||
}
|
||||
|
||||
func NewSetEventV2(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
targets []string,
|
||||
includes []string,
|
||||
) *SetEvent {
|
||||
return &SetEvent{
|
||||
targets []*Target,
|
||||
) *SetEventV2 {
|
||||
return &SetEventV2{
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx, aggregate, SetEventType,
|
||||
ctx, aggregate, SetEventV2Type,
|
||||
),
|
||||
Targets: targets,
|
||||
Includes: includes,
|
||||
Targets: targets,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,9 +20,8 @@ type AddedEvent struct {
|
||||
|
||||
Name string `json:"name"`
|
||||
TargetType domain.TargetType `json:"targetType"`
|
||||
URL string `json:"url"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Timeout time.Duration `json:"timeout"`
|
||||
Async bool `json:"async"`
|
||||
InterruptOnError bool `json:"interruptOnError"`
|
||||
}
|
||||
|
||||
@@ -43,16 +42,15 @@ func NewAddedEvent(
|
||||
aggregate *eventstore.Aggregate,
|
||||
name string,
|
||||
targetType domain.TargetType,
|
||||
url string,
|
||||
endpoint string,
|
||||
timeout time.Duration,
|
||||
async bool,
|
||||
interruptOnError bool,
|
||||
) *AddedEvent {
|
||||
return &AddedEvent{
|
||||
*eventstore.NewBaseEventForPush(
|
||||
ctx, aggregate, AddedEventType,
|
||||
),
|
||||
name, targetType, url, timeout, async, interruptOnError}
|
||||
name, targetType, endpoint, timeout, interruptOnError}
|
||||
}
|
||||
|
||||
type ChangedEvent struct {
|
||||
@@ -60,9 +58,8 @@ type ChangedEvent struct {
|
||||
|
||||
Name *string `json:"name,omitempty"`
|
||||
TargetType *domain.TargetType `json:"targetType,omitempty"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
Endpoint *string `json:"endpoint,omitempty"`
|
||||
Timeout *time.Duration `json:"timeout,omitempty"`
|
||||
Async *bool `json:"async,omitempty"`
|
||||
InterruptOnError *bool `json:"interruptOnError,omitempty"`
|
||||
|
||||
oldName string
|
||||
@@ -119,9 +116,9 @@ func ChangeTargetType(targetType domain.TargetType) func(event *ChangedEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeURL(url string) func(event *ChangedEvent) {
|
||||
func ChangeEndpoint(endpoint string) func(event *ChangedEvent) {
|
||||
return func(e *ChangedEvent) {
|
||||
e.URL = &url
|
||||
e.Endpoint = &endpoint
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,12 +128,6 @@ func ChangeTimeout(timeout time.Duration) func(event *ChangedEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeAsync(async bool) func(event *ChangedEvent) {
|
||||
return func(e *ChangedEvent) {
|
||||
e.Async = &async
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeInterruptOnError(interruptOnError bool) func(event *ChangedEvent) {
|
||||
return func(e *ChangedEvent) {
|
||||
e.InterruptOnError = &interruptOnError
|
||||
|
||||
@@ -484,6 +484,7 @@ Errors:
|
||||
NotActive: Действието не е активно
|
||||
NotInactive: Действието не е неактивно
|
||||
MaxAllowed: Не са разрешени допълнителни активни действия
|
||||
NotEnabled: Функцията „Действие“ не е активирана
|
||||
Flow:
|
||||
FlowTypeMissing: Липсва FlowType
|
||||
Empty: Потокът вече е празен
|
||||
|
||||
@@ -470,6 +470,7 @@ Errors:
|
||||
NotActive: Akce není aktivní
|
||||
NotInactive: Akce není neaktivní
|
||||
MaxAllowed: Není dovoleno více aktivních akcí
|
||||
NotEnabled: Funkce "Akce" není povolena
|
||||
Flow:
|
||||
FlowTypeMissing: Chybí typ toku
|
||||
Empty: Tok je již prázdný
|
||||
|
||||
@@ -470,6 +470,7 @@ Errors:
|
||||
NotActive: Action ist nicht aktiv
|
||||
NotInactive: Action ist nicht inaktiv
|
||||
MaxAllowed: Keine weitere aktiven Actions mehr erlaubt
|
||||
NotEnabled: Function "Action" ist nicht aktiviert
|
||||
Flow:
|
||||
FlowTypeMissing: FlowType fehlt
|
||||
Empty: Flow ist bereits leer
|
||||
|
||||
@@ -470,6 +470,7 @@ Errors:
|
||||
NotActive: Action is not active
|
||||
NotInactive: Action is not inactive
|
||||
MaxAllowed: No additional active Actions allowed
|
||||
NotEnabled: Feature "Action" is not enabled
|
||||
Flow:
|
||||
FlowTypeMissing: FlowType missing
|
||||
Empty: Flow is already empty
|
||||
|
||||
@@ -470,6 +470,7 @@ Errors:
|
||||
NotActive: La acción no está activa
|
||||
NotInactive: La acción no está inactiva
|
||||
MaxAllowed: No hay acciones adicionales activas permitidas
|
||||
NotEnabled: La función "Acción" no está habilitada
|
||||
Flow:
|
||||
FlowTypeMissing: Falta el tipo de flujo
|
||||
Empty: El flujo ya está vacío
|
||||
|
||||
@@ -470,6 +470,7 @@ Errors:
|
||||
NotActive: L'action n'est pas active
|
||||
NotInactive: L'action n'est pas inactive
|
||||
MaxAllowed: Aucune action active supplémentaire n'est autorisée
|
||||
NotEnabled: La fonctionnalité "Action" n'est pas activée
|
||||
Flow:
|
||||
FlowTypeMissing: FlowType missing
|
||||
Empty: Le flux est déjà vide
|
||||
|
||||
@@ -470,6 +470,7 @@ Errors:
|
||||
NotActive: L'azione non è attiva
|
||||
NotInactive: L'azione non è inattiva
|
||||
MaxAllowed: Non sono permesse altre azioni attive
|
||||
NotEnabled: La funzione "Azione" non è abilitata
|
||||
Flow:
|
||||
FlowTypeMissing: FlowType mancante
|
||||
Empty: Flow è già vuoto
|
||||
|
||||
@@ -459,6 +459,7 @@ Errors:
|
||||
NotActive: アクションはアクティブではありません
|
||||
NotInactive: アクションは非アクティブではありません
|
||||
MaxAllowed: 追加のアクティブアクションは許可されていません
|
||||
NotEnabled: 機能「アクション」が有効になっていません
|
||||
Flow:
|
||||
FlowTypeMissing: フロータイプがありません
|
||||
Empty: フローはすでに空です
|
||||
|
||||
@@ -469,6 +469,7 @@ Errors:
|
||||
NotActive: Акцијата не е активна
|
||||
NotInactive: Акцијата не е неактивна
|
||||
MaxAllowed: Не се дозволени дополнителни активни акции
|
||||
NotEnabled: Функцијата „Акција“ не е овозможена
|
||||
Flow:
|
||||
FlowTypeMissing: FlowType не е наведен
|
||||
Empty: Flow е веќе празен
|
||||
|
||||
@@ -469,6 +469,7 @@ Errors:
|
||||
NotActive: Actie is niet actief
|
||||
NotInactive: Actie is niet inactief
|
||||
MaxAllowed: Geen extra actieve acties toegestaan
|
||||
NotEnabled: Functie "Actie" is niet ingeschakeld
|
||||
Flow:
|
||||
FlowTypeMissing: FlowType ontbreekt
|
||||
Empty: Flow is al leeg
|
||||
|
||||
@@ -470,6 +470,7 @@ Errors:
|
||||
NotActive: Działanie nie jest aktywne
|
||||
NotInactive: Działanie nie jest dezaktywowane
|
||||
MaxAllowed: Nie dopuszcza się dodatkowych aktywnych działań.
|
||||
NotEnabled: Funkcja „Akcja” nie jest włączona
|
||||
Flow:
|
||||
FlowTypeMissing: Typ przepływu brakuje
|
||||
Empty: Przepływ jest już pusty
|
||||
|
||||
@@ -469,6 +469,7 @@ Errors:
|
||||
NotActive: A ação não está ativa
|
||||
NotInactive: A ação não está inativa
|
||||
MaxAllowed: Não são permitidas ações adicionais ativas
|
||||
NotEnabled: O recurso "Ação" não está ativado
|
||||
Flow:
|
||||
FlowTypeMissing: O tipo de fluxo está faltando
|
||||
Empty: O fluxo já está vazio
|
||||
|
||||
@@ -463,6 +463,7 @@ Errors:
|
||||
NotActive: Действие не активно
|
||||
NotInactive: Действие не является неактивным
|
||||
MaxAllowed: Дополнительные активные действия запрещены
|
||||
NotEnabled: Функция «Действие» не включена
|
||||
Flow:
|
||||
FlowTypeMissing: Тип процесса отсутствует
|
||||
Empty: Процесс уже пуст
|
||||
|
||||
@@ -470,6 +470,7 @@ Errors:
|
||||
NotActive: 动作不是启用状态
|
||||
NotInactive: 动作不是停用状态
|
||||
MaxAllowed: 不允许额外的动作
|
||||
NotEnabled: 未启用“操作”功能
|
||||
Flow:
|
||||
FlowTypeMissing: 缺少身份认证流程类型
|
||||
Empty: 身份认证流程为空
|
||||
|
||||
Reference in New Issue
Block a user