mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 11:27:33 +00:00
feat(v3alpha): read actions (#8357)
# Which Problems Are Solved The current v3alpha actions APIs don't exactly adhere to the [new resources API design](https://zitadel.com/docs/apis/v3#standard-resources). # How the Problems Are Solved - **Improved ID access**: The aggregate ID is added to the resource details object, so accessing resource IDs and constructing proto messages for resources is easier - **Explicit Instances**: Optionally, the instance can be explicitly given in each request - **Pagination**: A default search limit and a max search limit are added to the defaults.yaml. They apply to the new v3 APIs (currently only actions). The search query defaults are changed to ascending by creation date, because this makes the pagination results the most deterministic. The creation date is also added to the object details. The bug with updated creation dates is fixed for executions and targets. - **Removed Sequences**: Removed Sequence from object details and ProcessedSequence from search details # Additional Changes Object details IDs are checked in unit test only if an empty ID is expected. Centralizing the details check also makes this internal object more flexible for future evolutions. # Additional Context - Closes #8169 - Depends on https://github.com/zitadel/zitadel/pull/8225 --------- Co-authored-by: Silvan <silvan.reusser@gmail.com> Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com>
This commit is contained in:
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
settings_object "github.com/zitadel/zitadel/internal/api/grpc/settings/object/v3alpha"
|
||||
resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/repository/execution"
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
func (s *Server) SetExecution(ctx context.Context, req *action.SetExecutionRequest) (*action.SetExecutionResponse, error) {
|
||||
if err := checkExecutionEnabled(ctx); err != nil {
|
||||
if err := checkActionsEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqTargets := req.GetExecution().GetTargets()
|
||||
@@ -34,24 +34,21 @@ func (s *Server) SetExecution(ctx context.Context, req *action.SetExecutionReque
|
||||
set := &command.SetExecution{
|
||||
Targets: targets,
|
||||
}
|
||||
owner := &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: authz.GetInstance(ctx).InstanceID(),
|
||||
}
|
||||
var err error
|
||||
var details *domain.ObjectDetails
|
||||
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||
switch t := req.GetCondition().GetConditionType().(type) {
|
||||
case *action.Condition_Request:
|
||||
cond := executionConditionFromRequest(t.Request)
|
||||
details, err = s.command.SetExecutionRequest(ctx, cond, set, owner.Id)
|
||||
details, err = s.command.SetExecutionRequest(ctx, cond, set, instanceID)
|
||||
case *action.Condition_Response:
|
||||
cond := executionConditionFromResponse(t.Response)
|
||||
details, err = s.command.SetExecutionResponse(ctx, cond, set, owner.Id)
|
||||
details, err = s.command.SetExecutionResponse(ctx, cond, set, instanceID)
|
||||
case *action.Condition_Event:
|
||||
cond := executionConditionFromEvent(t.Event)
|
||||
details, err = s.command.SetExecutionEvent(ctx, cond, set, owner.Id)
|
||||
details, err = s.command.SetExecutionEvent(ctx, cond, set, instanceID)
|
||||
case *action.Condition_Function:
|
||||
details, err = s.command.SetExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function.GetName()), set, owner.Id)
|
||||
details, err = s.command.SetExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function.GetName()), set, instanceID)
|
||||
default:
|
||||
err = zerrors.ThrowInvalidArgument(nil, "ACTION-5r5Ju", "Errors.Execution.ConditionInvalid")
|
||||
}
|
||||
@@ -59,7 +56,7 @@ func (s *Server) SetExecution(ctx context.Context, req *action.SetExecutionReque
|
||||
return nil, err
|
||||
}
|
||||
return &action.SetExecutionResponse{
|
||||
Details: settings_object.DomainToDetailsPb(details, owner),
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||
settings_object "github.com/zitadel/zitadel/pkg/grpc/settings/object/v3alpha"
|
||||
resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
|
||||
)
|
||||
|
||||
func executionTargetsSingleTarget(id string) []*action.ExecutionTargetType {
|
||||
@@ -25,8 +25,9 @@ func executionTargetsSingleInclude(include *action.Condition) []*action.Executio
|
||||
}
|
||||
|
||||
func TestServer_SetExecution_Request(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
_, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX)
|
||||
ensureFeatureEnabled(t, isolatedIAMOwnerCTX)
|
||||
targetResp := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -51,7 +52,7 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "no condition, error",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
@@ -66,7 +67,7 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "method, not existing",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
@@ -85,7 +86,7 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "method, ok",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
@@ -101,18 +102,18 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &settings_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "service, not existing",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
@@ -131,7 +132,7 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "service, ok",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
@@ -147,18 +148,18 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &settings_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all, ok",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
@@ -174,11 +175,11 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &settings_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -187,8 +188,8 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// We want to have the same response no matter how often we call the function
|
||||
Client.SetExecution(tt.ctx, tt.req)
|
||||
got, err := Client.SetExecution(tt.ctx, tt.req)
|
||||
Tester.Client.ActionV3.SetExecution(tt.ctx, tt.req)
|
||||
got, err := Tester.Client.ActionV3.SetExecution(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
@@ -196,7 +197,7 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertSettingsDetails(t, tt.want.Details, got.Details)
|
||||
integration.AssertResourceDetails(t, tt.want.Details, got.Details)
|
||||
|
||||
// cleanup to not impact other requests
|
||||
Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||
@@ -205,8 +206,9 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
_, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX)
|
||||
ensureFeatureEnabled(t, isolatedIAMOwnerCTX)
|
||||
targetResp := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
executionCond := &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
@@ -216,7 +218,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
Tester.SetExecution(CTX, t,
|
||||
Tester.SetExecution(isolatedIAMOwnerCTX, t,
|
||||
executionCond,
|
||||
executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
)
|
||||
@@ -230,7 +232,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
Tester.SetExecution(CTX, t,
|
||||
Tester.SetExecution(isolatedIAMOwnerCTX, t,
|
||||
circularExecutionService,
|
||||
executionTargetsSingleInclude(executionCond),
|
||||
)
|
||||
@@ -243,7 +245,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
Tester.SetExecution(CTX, t,
|
||||
Tester.SetExecution(isolatedIAMOwnerCTX, t,
|
||||
circularExecutionMethod,
|
||||
executionTargetsSingleInclude(circularExecutionService),
|
||||
)
|
||||
@@ -257,7 +259,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "method, circular error",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: circularExecutionService,
|
||||
Execution: &action.Execution{
|
||||
@@ -268,7 +270,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "method, ok",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
@@ -280,23 +282,22 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
|
||||
Targets: executionTargetsSingleInclude(executionCond),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &settings_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "service, ok",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
@@ -308,16 +309,15 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
|
||||
Targets: executionTargetsSingleInclude(executionCond),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &settings_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -326,15 +326,15 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// We want to have the same response no matter how often we call the function
|
||||
Client.SetExecution(tt.ctx, tt.req)
|
||||
got, err := Client.SetExecution(tt.ctx, tt.req)
|
||||
Tester.Client.ActionV3.SetExecution(tt.ctx, tt.req)
|
||||
got, err := Tester.Client.ActionV3.SetExecution(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertSettingsDetails(t, tt.want.Details, got.Details)
|
||||
integration.AssertResourceDetails(t, tt.want.Details, got.Details)
|
||||
|
||||
// cleanup to not impact other requests
|
||||
Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||
@@ -343,8 +343,9 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_SetExecution_Response(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
_, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX)
|
||||
ensureFeatureEnabled(t, isolatedIAMOwnerCTX)
|
||||
targetResp := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -369,7 +370,7 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "no condition, error",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Response{
|
||||
@@ -384,7 +385,7 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "method, not existing",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Response{
|
||||
@@ -403,7 +404,7 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "method, ok",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Response{
|
||||
@@ -419,18 +420,18 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &settings_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "service, not existing",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Response{
|
||||
@@ -449,7 +450,7 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "service, ok",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Response{
|
||||
@@ -465,18 +466,18 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &settings_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all, ok",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Response{
|
||||
@@ -492,11 +493,11 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &settings_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -505,15 +506,15 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// We want to have the same response no matter how often we call the function
|
||||
Client.SetExecution(tt.ctx, tt.req)
|
||||
got, err := Client.SetExecution(tt.ctx, tt.req)
|
||||
Tester.Client.ActionV3.SetExecution(tt.ctx, tt.req)
|
||||
got, err := Tester.Client.ActionV3.SetExecution(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertSettingsDetails(t, tt.want.Details, got.Details)
|
||||
integration.AssertResourceDetails(t, tt.want.Details, got.Details)
|
||||
|
||||
// cleanup to not impact other requests
|
||||
Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||
@@ -522,8 +523,9 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_SetExecution_Event(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
_, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX)
|
||||
ensureFeatureEnabled(t, isolatedIAMOwnerCTX)
|
||||
targetResp := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -550,7 +552,7 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "no condition, error",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Event{
|
||||
@@ -568,7 +570,7 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
||||
|
||||
{
|
||||
name: "event, not existing",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Event{
|
||||
@@ -586,7 +588,7 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
||||
*/
|
||||
{
|
||||
name: "event, ok",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Event{
|
||||
@@ -602,11 +604,11 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &settings_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -616,7 +618,7 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
||||
|
||||
{
|
||||
name: "group, not existing",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Event{
|
||||
@@ -634,7 +636,7 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
||||
*/
|
||||
{
|
||||
name: "group, ok",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Event{
|
||||
@@ -650,18 +652,18 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &settings_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all, ok",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Event{
|
||||
@@ -677,11 +679,11 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &settings_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -690,15 +692,15 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// We want to have the same response no matter how often we call the function
|
||||
Client.SetExecution(tt.ctx, tt.req)
|
||||
got, err := Client.SetExecution(tt.ctx, tt.req)
|
||||
Tester.Client.ActionV3.SetExecution(tt.ctx, tt.req)
|
||||
got, err := Tester.Client.ActionV3.SetExecution(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertSettingsDetails(t, tt.want.Details, got.Details)
|
||||
integration.AssertResourceDetails(t, tt.want.Details, got.Details)
|
||||
|
||||
// cleanup to not impact other requests
|
||||
Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||
@@ -707,8 +709,9 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_SetExecution_Function(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
_, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX)
|
||||
ensureFeatureEnabled(t, isolatedIAMOwnerCTX)
|
||||
targetResp := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -733,7 +736,7 @@ func TestServer_SetExecution_Function(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "no condition, error",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Response{
|
||||
@@ -748,7 +751,7 @@ func TestServer_SetExecution_Function(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "function, not existing",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Function{
|
||||
@@ -763,7 +766,7 @@ func TestServer_SetExecution_Function(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "function, ok",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Function{
|
||||
@@ -775,11 +778,11 @@ func TestServer_SetExecution_Function(t *testing.T) {
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &settings_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -788,15 +791,15 @@ func TestServer_SetExecution_Function(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// We want to have the same response no matter how often we call the function
|
||||
Client.SetExecution(tt.ctx, tt.req)
|
||||
got, err := Client.SetExecution(tt.ctx, tt.req)
|
||||
Tester.Client.ActionV3.SetExecution(tt.ctx, tt.req)
|
||||
got, err := Tester.Client.ActionV3.SetExecution(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertSettingsDetails(t, tt.want.Details, got.Details)
|
||||
integration.AssertResourceDetails(t, tt.want.Details, got.Details)
|
||||
|
||||
// cleanup to not impact other requests
|
||||
Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||
|
@@ -0,0 +1,335 @@
|
||||
//go:build integration
|
||||
|
||||
package action_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||
resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
|
||||
)
|
||||
|
||||
func TestServer_ExecutionTarget(t *testing.T) {
|
||||
_, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX)
|
||||
ensureFeatureEnabled(t, isolatedIAMOwnerCTX)
|
||||
|
||||
fullMethod := "/zitadel.resources.action.v3alpha.ZITADELActions/GetTarget"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
dep func(context.Context, *action.GetTargetRequest, *action.GetTargetResponse) (func(), error)
|
||||
clean func(context.Context)
|
||||
req *action.GetTargetRequest
|
||||
want *action.GetTargetResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "GetTarget, request and response, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) (func(), error) {
|
||||
|
||||
orgID := Tester.Organisation.ID
|
||||
projectID := ""
|
||||
userID := Tester.Users.Get(instanceID, integration.IAMOwner).ID
|
||||
|
||||
// create target for target changes
|
||||
targetCreatedName := fmt.Sprint("GetTarget", 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.GetTargetRequest{Id: targetCreated.GetDetails().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.GetDetails().GetId()))
|
||||
|
||||
// expected response from the GetTarget
|
||||
expectedResponse := &action.GetTargetResponse{
|
||||
Target: &action.GetTarget{
|
||||
Config: &action.Target{
|
||||
Name: targetCreatedName,
|
||||
Endpoint: targetCreatedURL,
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
Details: targetCreated.GetDetails(),
|
||||
},
|
||||
}
|
||||
// has to be set separately because of the pointers
|
||||
response.Target = &action.GetTarget{
|
||||
Details: targetCreated.GetDetails(),
|
||||
Config: &action.Target{
|
||||
Name: targetCreatedName,
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
Endpoint: targetCreatedURL,
|
||||
},
|
||||
}
|
||||
|
||||
// content for partial update
|
||||
changedResponse := &action.GetTargetResponse{
|
||||
Target: &action.GetTarget{
|
||||
Details: &resource_object.Details{
|
||||
Id: targetCreated.GetDetails().GetId(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 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.GetDetails().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.GetTargetRequest{
|
||||
Id: "something",
|
||||
},
|
||||
want: &action.GetTargetResponse{
|
||||
Target: &action.GetTarget{
|
||||
Details: &resource_object.Details{
|
||||
Id: "changed",
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GetTarget, request, interrupt",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) (func(), error) {
|
||||
|
||||
fullMethod := "/zitadel.resources.action.v3alpha.ZITADELActions/GetTarget"
|
||||
orgID := Tester.Organisation.ID
|
||||
projectID := ""
|
||||
userID := Tester.Users.Get(instanceID, 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.GetTargetRequest{Id: "notchanged"})
|
||||
|
||||
targetRequest := Tester.CreateTarget(ctx, t, "", urlRequest, domain.TargetTypeCall, true)
|
||||
Tester.SetExecution(ctx, t, conditionRequestFullMethod(fullMethod), executionTargetsSingleTarget(targetRequest.GetDetails().GetId()))
|
||||
// GetTarget with used target
|
||||
request.Id = targetRequest.GetDetails().GetId()
|
||||
|
||||
return func() {
|
||||
closeRequest()
|
||||
}, nil
|
||||
},
|
||||
clean: func(ctx context.Context) {
|
||||
Tester.DeleteExecution(ctx, t, conditionRequestFullMethod(fullMethod))
|
||||
},
|
||||
req: &action.GetTargetRequest{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "GetTarget, response, interrupt",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) (func(), error) {
|
||||
|
||||
fullMethod := "/zitadel.resources.action.v3alpha.ZITADELActions/GetTarget"
|
||||
orgID := Tester.Organisation.ID
|
||||
projectID := ""
|
||||
userID := Tester.Users.Get(instanceID, integration.IAMOwner).ID
|
||||
|
||||
// create target for target changes
|
||||
targetCreatedName := fmt.Sprint("GetTarget", time.Now().UnixNano()+1)
|
||||
targetCreatedURL := "https://nonexistent"
|
||||
|
||||
targetCreated := Tester.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, domain.TargetTypeCall, false)
|
||||
|
||||
// GetTarget with used target
|
||||
request.Id = targetCreated.GetDetails().GetId()
|
||||
|
||||
// expected response from the GetTarget
|
||||
expectedResponse := &action.GetTargetResponse{
|
||||
Target: &action.GetTarget{
|
||||
Details: targetCreated.GetDetails(),
|
||||
Config: &action.Target{
|
||||
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.GetTargetResponse{
|
||||
Target: &action.GetTarget{
|
||||
Details: &resource_object.Details{
|
||||
Id: "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.GetDetails().GetId()))
|
||||
|
||||
return func() {
|
||||
closeResponse()
|
||||
}, nil
|
||||
},
|
||||
clean: func(ctx context.Context) {
|
||||
Tester.DeleteExecution(ctx, t, conditionResponseFullMethod(fullMethod))
|
||||
},
|
||||
req: &action.GetTargetRequest{},
|
||||
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 := Tester.Client.ActionV3.GetTarget(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertResourceDetails(t, tt.want.GetTarget().GetDetails(), got.GetTarget().GetDetails())
|
||||
require.Equal(t, tt.want.GetTarget().GetConfig(), got.GetTarget().GetConfig())
|
||||
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
|
||||
}
|
410
internal/api/grpc/resources/action/v3alpha/query.go
Normal file
410
internal/api/grpc/resources/action/v3alpha/query.go
Normal file
@@ -0,0 +1,410 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||
)
|
||||
|
||||
const (
|
||||
conditionIDAllSegmentCount = 0
|
||||
conditionIDRequestResponseServiceSegmentCount = 1
|
||||
conditionIDRequestResponseMethodSegmentCount = 2
|
||||
conditionIDEventGroupSegmentCount = 1
|
||||
)
|
||||
|
||||
func (s *Server) GetTarget(ctx context.Context, req *action.GetTargetRequest) (*action.GetTargetResponse, error) {
|
||||
if err := checkActionsEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.query.GetTargetByID(ctx, req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &action.GetTargetResponse{
|
||||
Target: targetToPb(resp),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type InstanceContext interface {
|
||||
GetInstanceId() string
|
||||
GetInstanceDomain() string
|
||||
}
|
||||
|
||||
type Context interface {
|
||||
GetOwner() InstanceContext
|
||||
}
|
||||
|
||||
func (s *Server) SearchTargets(ctx context.Context, req *action.SearchTargetsRequest) (*action.SearchTargetsResponse, error) {
|
||||
if err := checkActionsEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries, err := s.searchTargetsRequestToModel(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := s.query.SearchTargets(ctx, queries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &action.SearchTargetsResponse{
|
||||
Result: targetsToPb(resp.Targets),
|
||||
Details: resource_object.ToSearchDetailsPb(queries.SearchRequest, resp.SearchResponse),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) SearchExecutions(ctx context.Context, req *action.SearchExecutionsRequest) (*action.SearchExecutionsResponse, error) {
|
||||
if err := checkActionsEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries, err := s.searchExecutionsRequestToModel(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := s.query.SearchExecutions(ctx, queries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &action.SearchExecutionsResponse{
|
||||
Result: executionsToPb(resp.Executions),
|
||||
Details: resource_object.ToSearchDetailsPb(queries.SearchRequest, resp.SearchResponse),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func targetsToPb(targets []*query.Target) []*action.GetTarget {
|
||||
t := make([]*action.GetTarget, len(targets))
|
||||
for i, target := range targets {
|
||||
t[i] = targetToPb(target)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func targetToPb(t *query.Target) *action.GetTarget {
|
||||
target := &action.GetTarget{
|
||||
Details: resource_object.DomainToDetailsPb(&t.ObjectDetails, object.OwnerType_OWNER_TYPE_INSTANCE, t.ResourceOwner),
|
||||
Config: &action.Target{
|
||||
Name: t.Name,
|
||||
Timeout: durationpb.New(t.Timeout),
|
||||
Endpoint: t.Endpoint,
|
||||
},
|
||||
}
|
||||
switch t.TargetType {
|
||||
case domain.TargetTypeWebhook:
|
||||
target.Config.TargetType = &action.Target_RestWebhook{RestWebhook: &action.SetRESTWebhook{InterruptOnError: t.InterruptOnError}}
|
||||
case domain.TargetTypeCall:
|
||||
target.Config.TargetType = &action.Target_RestCall{RestCall: &action.SetRESTCall{InterruptOnError: t.InterruptOnError}}
|
||||
case domain.TargetTypeAsync:
|
||||
target.Config.TargetType = &action.Target_RestAsync{RestAsync: &action.SetRESTAsync{}}
|
||||
default:
|
||||
target.Config.TargetType = nil
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
func (s *Server) searchTargetsRequestToModel(req *action.SearchTargetsRequest) (*query.TargetSearchQueries, error) {
|
||||
offset, limit, asc, err := resource_object.SearchQueryPbToQuery(s.systemDefaults, req.Query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries, err := targetQueriesToQuery(req.Filters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &query.TargetSearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
SortingColumn: targetFieldNameToSortingColumn(req.SortingColumn),
|
||||
},
|
||||
Queries: queries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func targetQueriesToQuery(queries []*action.TargetSearchFilter) (_ []query.SearchQuery, err error) {
|
||||
q := make([]query.SearchQuery, len(queries))
|
||||
for i, qry := range queries {
|
||||
q[i], err = targetQueryToQuery(qry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func targetQueryToQuery(filter *action.TargetSearchFilter) (query.SearchQuery, error) {
|
||||
switch q := filter.Filter.(type) {
|
||||
case *action.TargetSearchFilter_TargetNameFilter:
|
||||
return targetNameQueryToQuery(q.TargetNameFilter)
|
||||
case *action.TargetSearchFilter_InTargetIdsFilter:
|
||||
return targetInTargetIdsQueryToQuery(q.InTargetIdsFilter)
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func targetNameQueryToQuery(q *action.TargetNameFilter) (query.SearchQuery, error) {
|
||||
return query.NewTargetNameSearchQuery(resource_object.TextMethodPbToQuery(q.Method), q.GetTargetName())
|
||||
}
|
||||
|
||||
func targetInTargetIdsQueryToQuery(q *action.InTargetIDsFilter) (query.SearchQuery, error) {
|
||||
return query.NewTargetInIDsSearchQuery(q.GetTargetIds())
|
||||
}
|
||||
|
||||
// targetFieldNameToSortingColumn defaults to the creation date because this ensures deterministic pagination
|
||||
func targetFieldNameToSortingColumn(field *action.TargetFieldName) query.Column {
|
||||
if field == nil {
|
||||
return query.TargetColumnCreationDate
|
||||
}
|
||||
switch *field {
|
||||
case action.TargetFieldName_TARGET_FIELD_NAME_UNSPECIFIED:
|
||||
return query.TargetColumnID
|
||||
case action.TargetFieldName_TARGET_FIELD_NAME_ID:
|
||||
return query.TargetColumnID
|
||||
case action.TargetFieldName_TARGET_FIELD_NAME_CREATED_DATE:
|
||||
return query.TargetColumnCreationDate
|
||||
case action.TargetFieldName_TARGET_FIELD_NAME_CHANGED_DATE:
|
||||
return query.TargetColumnChangeDate
|
||||
case action.TargetFieldName_TARGET_FIELD_NAME_NAME:
|
||||
return query.TargetColumnName
|
||||
case action.TargetFieldName_TARGET_FIELD_NAME_TARGET_TYPE:
|
||||
return query.TargetColumnTargetType
|
||||
case action.TargetFieldName_TARGET_FIELD_NAME_URL:
|
||||
return query.TargetColumnURL
|
||||
case action.TargetFieldName_TARGET_FIELD_NAME_TIMEOUT:
|
||||
return query.TargetColumnTimeout
|
||||
case action.TargetFieldName_TARGET_FIELD_NAME_INTERRUPT_ON_ERROR:
|
||||
return query.TargetColumnInterruptOnError
|
||||
default:
|
||||
return query.TargetColumnCreationDate
|
||||
}
|
||||
}
|
||||
|
||||
// executionFieldNameToSortingColumn defaults to the creation date because this ensures deterministic pagination
|
||||
func executionFieldNameToSortingColumn(field *action.ExecutionFieldName) query.Column {
|
||||
if field == nil {
|
||||
return query.ExecutionColumnCreationDate
|
||||
}
|
||||
switch *field {
|
||||
case action.ExecutionFieldName_EXECUTION_FIELD_NAME_UNSPECIFIED:
|
||||
return query.ExecutionColumnID
|
||||
case action.ExecutionFieldName_EXECUTION_FIELD_NAME_ID:
|
||||
return query.ExecutionColumnID
|
||||
case action.ExecutionFieldName_EXECUTION_FIELD_NAME_CREATED_DATE:
|
||||
return query.ExecutionColumnCreationDate
|
||||
case action.ExecutionFieldName_EXECUTION_FIELD_NAME_CHANGED_DATE:
|
||||
return query.ExecutionColumnChangeDate
|
||||
default:
|
||||
return query.ExecutionColumnCreationDate
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) searchExecutionsRequestToModel(req *action.SearchExecutionsRequest) (*query.ExecutionSearchQueries, error) {
|
||||
offset, limit, asc, err := resource_object.SearchQueryPbToQuery(s.systemDefaults, req.Query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries, err := executionQueriesToQuery(req.Filters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &query.ExecutionSearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
SortingColumn: executionFieldNameToSortingColumn(req.SortingColumn),
|
||||
},
|
||||
Queries: queries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func executionQueriesToQuery(queries []*action.ExecutionSearchFilter) (_ []query.SearchQuery, err error) {
|
||||
q := make([]query.SearchQuery, len(queries))
|
||||
for i, query := range queries {
|
||||
q[i], err = executionQueryToQuery(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func executionQueryToQuery(searchQuery *action.ExecutionSearchFilter) (query.SearchQuery, error) {
|
||||
switch q := searchQuery.Filter.(type) {
|
||||
case *action.ExecutionSearchFilter_InConditionsFilter:
|
||||
return inConditionsQueryToQuery(q.InConditionsFilter)
|
||||
case *action.ExecutionSearchFilter_ExecutionTypeFilter:
|
||||
return executionTypeToQuery(q.ExecutionTypeFilter)
|
||||
case *action.ExecutionSearchFilter_IncludeFilter:
|
||||
include, err := conditionToInclude(q.IncludeFilter.GetInclude())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return query.NewIncludeSearchQuery(include)
|
||||
case *action.ExecutionSearchFilter_TargetFilter:
|
||||
return query.NewTargetSearchQuery(q.TargetFilter.GetTargetId())
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func executionTypeToQuery(q *action.ExecutionTypeFilter) (query.SearchQuery, error) {
|
||||
switch q.ExecutionType {
|
||||
case action.ExecutionType_EXECUTION_TYPE_UNSPECIFIED:
|
||||
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeUnspecified)
|
||||
case action.ExecutionType_EXECUTION_TYPE_REQUEST:
|
||||
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeRequest)
|
||||
case action.ExecutionType_EXECUTION_TYPE_RESPONSE:
|
||||
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeResponse)
|
||||
case action.ExecutionType_EXECUTION_TYPE_EVENT:
|
||||
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeEvent)
|
||||
case action.ExecutionType_EXECUTION_TYPE_FUNCTION:
|
||||
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeFunction)
|
||||
default:
|
||||
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeUnspecified)
|
||||
}
|
||||
}
|
||||
|
||||
func inConditionsQueryToQuery(q *action.InConditionsFilter) (query.SearchQuery, error) {
|
||||
values := make([]string, len(q.GetConditions()))
|
||||
for i, condition := range q.GetConditions() {
|
||||
id, err := conditionToID(condition)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
values[i] = id
|
||||
}
|
||||
return query.NewExecutionInIDsSearchQuery(values)
|
||||
}
|
||||
|
||||
func conditionToID(q *action.Condition) (string, error) {
|
||||
switch t := q.GetConditionType().(type) {
|
||||
case *action.Condition_Request:
|
||||
cond := &command.ExecutionAPICondition{
|
||||
Method: t.Request.GetMethod(),
|
||||
Service: t.Request.GetService(),
|
||||
All: t.Request.GetAll(),
|
||||
}
|
||||
return cond.ID(domain.ExecutionTypeRequest), nil
|
||||
case *action.Condition_Response:
|
||||
cond := &command.ExecutionAPICondition{
|
||||
Method: t.Response.GetMethod(),
|
||||
Service: t.Response.GetService(),
|
||||
All: t.Response.GetAll(),
|
||||
}
|
||||
return cond.ID(domain.ExecutionTypeResponse), nil
|
||||
case *action.Condition_Event:
|
||||
cond := &command.ExecutionEventCondition{
|
||||
Event: t.Event.GetEvent(),
|
||||
Group: t.Event.GetGroup(),
|
||||
All: t.Event.GetAll(),
|
||||
}
|
||||
return cond.ID(), nil
|
||||
case *action.Condition_Function:
|
||||
return command.ExecutionFunctionCondition(t.Function.GetName()).ID(), nil
|
||||
default:
|
||||
return "", zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func executionsToPb(executions []*query.Execution) []*action.GetExecution {
|
||||
e := make([]*action.GetExecution, len(executions))
|
||||
for i, execution := range executions {
|
||||
e[i] = executionToPb(execution)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func executionToPb(e *query.Execution) *action.GetExecution {
|
||||
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.GetExecution{
|
||||
Details: resource_object.DomainToDetailsPb(&e.ObjectDetails, object.OwnerType_OWNER_TYPE_INSTANCE, e.ResourceOwner),
|
||||
Execution: &action.Execution{
|
||||
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 conditionIDRequestResponseMethodSegmentCount:
|
||||
return &action.Condition{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Method{Method: id}}}}
|
||||
case conditionIDRequestResponseServiceSegmentCount:
|
||||
return &action.Condition{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Service{Service: strings.TrimPrefix(id, "/")}}}}
|
||||
case conditionIDAllSegmentCount:
|
||||
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 conditionIDRequestResponseMethodSegmentCount:
|
||||
return &action.Condition{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Method{Method: id}}}}
|
||||
case conditionIDRequestResponseServiceSegmentCount:
|
||||
return &action.Condition{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Service{Service: strings.TrimPrefix(id, "/")}}}}
|
||||
case conditionIDAllSegmentCount:
|
||||
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 conditionIDEventGroupSegmentCount:
|
||||
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 conditionIDAllSegmentCount:
|
||||
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, "/")}}}
|
||||
}
|
@@ -0,0 +1,898 @@
|
||||
//go:build integration
|
||||
|
||||
package action_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"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"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||
resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
|
||||
)
|
||||
|
||||
func TestServer_GetTarget(t *testing.T) {
|
||||
_, _, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX)
|
||||
ensureFeatureEnabled(t, isolatedIAMOwnerCTX)
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
dep func(context.Context, *action.GetTargetRequest, *action.GetTargetResponse) error
|
||||
req *action.GetTargetRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *action.GetTargetResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
args: args{
|
||||
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
||||
req: &action.GetTargetRequest{},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.GetTargetRequest{Id: "notexisting"},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "get, ok",
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error {
|
||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false)
|
||||
request.Id = resp.GetDetails().GetId()
|
||||
response.Target.Config.Name = name
|
||||
response.Target.Details = resp.GetDetails()
|
||||
return nil
|
||||
},
|
||||
req: &action.GetTargetRequest{},
|
||||
},
|
||||
want: &action.GetTargetResponse{
|
||||
Target: &action.GetTarget{
|
||||
Details: &resource_object.Details{
|
||||
Created: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
},
|
||||
Config: &action.Target{
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get, async, ok",
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error {
|
||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeAsync, false)
|
||||
request.Id = resp.GetDetails().GetId()
|
||||
response.Target.Config.Name = name
|
||||
response.Target.Details = resp.GetDetails()
|
||||
return nil
|
||||
},
|
||||
req: &action.GetTargetRequest{},
|
||||
},
|
||||
want: &action.GetTargetResponse{
|
||||
Target: &action.GetTarget{
|
||||
Details: &resource_object.Details{
|
||||
Created: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
},
|
||||
Config: &action.Target{
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestAsync{
|
||||
RestAsync: &action.SetRESTAsync{},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get, webhook interruptOnError, ok",
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error {
|
||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, true)
|
||||
request.Id = resp.GetDetails().GetId()
|
||||
response.Target.Config.Name = name
|
||||
response.Target.Details = resp.GetDetails()
|
||||
return nil
|
||||
},
|
||||
req: &action.GetTargetRequest{},
|
||||
},
|
||||
want: &action.GetTargetResponse{
|
||||
Target: &action.GetTarget{
|
||||
Details: &resource_object.Details{
|
||||
Created: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
},
|
||||
Config: &action.Target{
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get, call, ok",
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error {
|
||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeCall, false)
|
||||
request.Id = resp.GetDetails().GetId()
|
||||
response.Target.Config.Name = name
|
||||
response.Target.Details = resp.GetDetails()
|
||||
return nil
|
||||
},
|
||||
req: &action.GetTargetRequest{},
|
||||
},
|
||||
want: &action.GetTargetResponse{
|
||||
Target: &action.GetTarget{
|
||||
Details: &resource_object.Details{
|
||||
Created: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
},
|
||||
Config: &action.Target{
|
||||
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: isolatedIAMOwnerCTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error {
|
||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeCall, true)
|
||||
request.Id = resp.GetDetails().GetId()
|
||||
response.Target.Config.Name = name
|
||||
response.Target.Details = resp.GetDetails()
|
||||
return nil
|
||||
},
|
||||
req: &action.GetTargetRequest{},
|
||||
},
|
||||
want: &action.GetTargetResponse{
|
||||
Target: &action.GetTarget{
|
||||
Details: &resource_object.Details{
|
||||
Created: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
},
|
||||
Config: &action.Target{
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.args.dep != nil {
|
||||
err := tt.args.dep(tt.args.ctx, tt.args.req, tt.want)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, getErr := Tester.Client.ActionV3.GetTarget(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, getErr, "Error: "+getErr.Error())
|
||||
} else {
|
||||
assert.NoError(t, getErr)
|
||||
wantTarget := tt.want.GetTarget()
|
||||
gotTarget := got.GetTarget()
|
||||
integration.AssertResourceDetails(t, wantTarget.GetDetails(), gotTarget.GetDetails())
|
||||
assert.Equal(t, wantTarget.GetConfig(), gotTarget.GetConfig())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ListTargets(t *testing.T) {
|
||||
_, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX)
|
||||
ensureFeatureEnabled(t, isolatedIAMOwnerCTX)
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
dep func(context.Context, *action.SearchTargetsRequest, *action.SearchTargetsResponse) error
|
||||
req *action.SearchTargetsRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *action.SearchTargetsResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
args: args{
|
||||
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
||||
req: &action.SearchTargetsRequest{},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "list, not found",
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SearchTargetsRequest{
|
||||
Filters: []*action.TargetSearchFilter{
|
||||
{Filter: &action.TargetSearchFilter_InTargetIdsFilter{
|
||||
InTargetIdsFilter: &action.InTargetIDsFilter{
|
||||
TargetIds: []string{"notfound"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &action.SearchTargetsResponse{
|
||||
Details: &resource_object.ListDetails{
|
||||
TotalResult: 0,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Result: []*action.GetTarget{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list single id",
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(ctx context.Context, request *action.SearchTargetsRequest, response *action.SearchTargetsResponse) error {
|
||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false)
|
||||
request.Filters[0].Filter = &action.TargetSearchFilter_InTargetIdsFilter{
|
||||
InTargetIdsFilter: &action.InTargetIDsFilter{
|
||||
TargetIds: []string{resp.GetDetails().GetId()},
|
||||
},
|
||||
}
|
||||
response.Details.Timestamp = resp.GetDetails().GetChanged()
|
||||
|
||||
response.Result[0].Details = resp.GetDetails()
|
||||
response.Result[0].Config.Name = name
|
||||
return nil
|
||||
},
|
||||
req: &action.SearchTargetsRequest{
|
||||
Filters: []*action.TargetSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &action.SearchTargetsResponse{
|
||||
Details: &resource_object.ListDetails{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Result: []*action.GetTarget{
|
||||
{
|
||||
Details: &resource_object.Details{
|
||||
Created: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
},
|
||||
Config: &action.Target{
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "list single name",
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(ctx context.Context, request *action.SearchTargetsRequest, response *action.SearchTargetsResponse) error {
|
||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false)
|
||||
request.Filters[0].Filter = &action.TargetSearchFilter_TargetNameFilter{
|
||||
TargetNameFilter: &action.TargetNameFilter{
|
||||
TargetName: name,
|
||||
},
|
||||
}
|
||||
response.Details.Timestamp = resp.GetDetails().GetChanged()
|
||||
|
||||
response.Result[0].Details = resp.GetDetails()
|
||||
response.Result[0].Config.Name = name
|
||||
return nil
|
||||
},
|
||||
req: &action.SearchTargetsRequest{
|
||||
Filters: []*action.TargetSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &action.SearchTargetsResponse{
|
||||
Details: &resource_object.ListDetails{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Result: []*action.GetTarget{
|
||||
{
|
||||
Details: &resource_object.Details{
|
||||
Created: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
Config: &action.Target{
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list multiple id",
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(ctx context.Context, request *action.SearchTargetsRequest, response *action.SearchTargetsResponse) error {
|
||||
name1 := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||
name2 := fmt.Sprint(time.Now().UnixNano() + 3)
|
||||
name3 := fmt.Sprint(time.Now().UnixNano() + 5)
|
||||
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.Filters[0].Filter = &action.TargetSearchFilter_InTargetIdsFilter{
|
||||
InTargetIdsFilter: &action.InTargetIDsFilter{
|
||||
TargetIds: []string{resp1.GetDetails().GetId(), resp2.GetDetails().GetId(), resp3.GetDetails().GetId()},
|
||||
},
|
||||
}
|
||||
response.Details.Timestamp = resp3.GetDetails().GetChanged()
|
||||
|
||||
response.Result[0].Details = resp1.GetDetails()
|
||||
response.Result[0].Config.Name = name1
|
||||
response.Result[1].Details = resp2.GetDetails()
|
||||
response.Result[1].Config.Name = name2
|
||||
response.Result[2].Details = resp3.GetDetails()
|
||||
response.Result[2].Config.Name = name3
|
||||
return nil
|
||||
},
|
||||
req: &action.SearchTargetsRequest{
|
||||
Filters: []*action.TargetSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &action.SearchTargetsResponse{
|
||||
Details: &resource_object.ListDetails{
|
||||
TotalResult: 3,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Result: []*action.GetTarget{
|
||||
{
|
||||
Details: &resource_object.Details{
|
||||
Created: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
Config: &action.Target{
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
},
|
||||
{
|
||||
Details: &resource_object.Details{
|
||||
Created: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
Config: &action.Target{
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
},
|
||||
{
|
||||
Details: &resource_object.Details{
|
||||
Created: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
Config: &action.Target{
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestAsync{
|
||||
RestAsync: &action.SetRESTAsync{},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.args.dep != nil {
|
||||
err := tt.args.dep(tt.args.ctx, tt.args.req, tt.want)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
retryDuration := 5 * time.Second
|
||||
if ctxDeadline, ok := isolatedIAMOwnerCTX.Deadline(); ok {
|
||||
retryDuration = time.Until(ctxDeadline)
|
||||
}
|
||||
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
got, listErr := Tester.Client.ActionV3.SearchTargets(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(ttt, listErr, "Error: "+listErr.Error())
|
||||
} else {
|
||||
assert.NoError(ttt, listErr)
|
||||
}
|
||||
if listErr != nil {
|
||||
return
|
||||
}
|
||||
// always first check length, otherwise its failed anyway
|
||||
assert.Len(ttt, got.Result, len(tt.want.Result))
|
||||
for i := range tt.want.Result {
|
||||
integration.AssertResourceDetails(t, tt.want.Result[i].GetDetails(), got.Result[i].GetDetails())
|
||||
assert.Equal(ttt, tt.want.Result[i].GetConfig(), got.Result[i].GetConfig())
|
||||
}
|
||||
integration.AssertResourceListDetails(t, tt.want, got)
|
||||
}, retryDuration, time.Millisecond*100, "timeout waiting for expected execution result")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_SearchExecutions(t *testing.T) {
|
||||
_, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX)
|
||||
ensureFeatureEnabled(t, isolatedIAMOwnerCTX)
|
||||
targetResp := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
dep func(context.Context, *action.SearchExecutionsRequest, *action.SearchExecutionsResponse) error
|
||||
req *action.SearchExecutionsRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *action.SearchExecutionsResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
args: args{
|
||||
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
||||
req: &action.SearchExecutionsRequest{},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "list request single condition",
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(ctx context.Context, request *action.SearchExecutionsRequest, response *action.SearchExecutionsResponse) error {
|
||||
cond := request.Filters[0].GetInConditionsFilter().GetConditions()[0]
|
||||
resp := Tester.SetExecution(ctx, t, cond, executionTargetsSingleTarget(targetResp.GetDetails().GetId()))
|
||||
|
||||
response.Details.Timestamp = resp.GetDetails().GetChanged()
|
||||
// Set expected response with used values for SetExecution
|
||||
response.Result[0].Details = resp.GetDetails()
|
||||
response.Result[0].Condition = cond
|
||||
return nil
|
||||
},
|
||||
req: &action.SearchExecutionsRequest{
|
||||
Filters: []*action.ExecutionSearchFilter{{
|
||||
Filter: &action.ExecutionSearchFilter_InConditionsFilter{
|
||||
InConditionsFilter: &action.InConditionsFilter{
|
||||
Conditions: []*action.Condition{{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Method{
|
||||
Method: "/zitadel.session.v2.SessionService/GetSession",
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
want: &action.SearchExecutionsResponse{
|
||||
Details: &resource_object.ListDetails{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Result: []*action.GetExecution{
|
||||
{
|
||||
Details: &resource_object.Details{
|
||||
Created: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
},
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Method{
|
||||
Method: "/zitadel.session.v2.SessionService/GetSession",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list request single target",
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(ctx context.Context, request *action.SearchExecutionsRequest, response *action.SearchExecutionsResponse) error {
|
||||
target := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false)
|
||||
// add target as Filter to the request
|
||||
request.Filters[0] = &action.ExecutionSearchFilter{
|
||||
Filter: &action.ExecutionSearchFilter_TargetFilter{
|
||||
TargetFilter: &action.TargetFilter{
|
||||
TargetId: target.GetDetails().GetId(),
|
||||
},
|
||||
},
|
||||
}
|
||||
cond := &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Method{
|
||||
Method: "/zitadel.management.v1.ManagementService/UpdateAction",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
targets := executionTargetsSingleTarget(target.GetDetails().GetId())
|
||||
resp := Tester.SetExecution(ctx, t, cond, targets)
|
||||
|
||||
response.Details.Timestamp = resp.GetDetails().GetChanged()
|
||||
|
||||
response.Result[0].Details = resp.GetDetails()
|
||||
response.Result[0].Condition = cond
|
||||
response.Result[0].Execution.Targets = targets
|
||||
return nil
|
||||
},
|
||||
req: &action.SearchExecutionsRequest{
|
||||
Filters: []*action.ExecutionSearchFilter{{}},
|
||||
},
|
||||
},
|
||||
want: &action.SearchExecutionsResponse{
|
||||
Details: &resource_object.ListDetails{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Result: []*action.GetExecution{
|
||||
{
|
||||
Details: &resource_object.Details{
|
||||
Created: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
},
|
||||
Condition: &action.Condition{},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(""),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "list request single include",
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(ctx context.Context, request *action.SearchExecutionsRequest, response *action.SearchExecutionsResponse) error {
|
||||
cond := &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Method{
|
||||
Method: "/zitadel.management.v1.ManagementService/GetAction",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
Tester.SetExecution(ctx, t, cond, executionTargetsSingleTarget(targetResp.GetDetails().GetId()))
|
||||
request.Filters[0].GetIncludeFilter().Include = cond
|
||||
|
||||
includeCond := &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Method{
|
||||
Method: "/zitadel.management.v1.ManagementService/ListActions",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
includeTargets := executionTargetsSingleInclude(cond)
|
||||
resp2 := Tester.SetExecution(ctx, t, includeCond, includeTargets)
|
||||
|
||||
response.Details.Timestamp = resp2.GetDetails().GetChanged()
|
||||
|
||||
response.Result[0].Details = resp2.GetDetails()
|
||||
response.Result[0].Condition = includeCond
|
||||
response.Result[0].Execution = &action.Execution{
|
||||
Targets: includeTargets,
|
||||
}
|
||||
return nil
|
||||
},
|
||||
req: &action.SearchExecutionsRequest{
|
||||
Filters: []*action.ExecutionSearchFilter{{
|
||||
Filter: &action.ExecutionSearchFilter_IncludeFilter{
|
||||
IncludeFilter: &action.IncludeFilter{},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
want: &action.SearchExecutionsResponse{
|
||||
Details: &resource_object.ListDetails{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Result: []*action.GetExecution{
|
||||
{
|
||||
Details: &resource_object.Details{
|
||||
Created: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list multiple conditions",
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(ctx context.Context, request *action.SearchExecutionsRequest, response *action.SearchExecutionsResponse) error {
|
||||
|
||||
cond1 := request.Filters[0].GetInConditionsFilter().GetConditions()[0]
|
||||
targets1 := executionTargetsSingleTarget(targetResp.GetDetails().GetId())
|
||||
resp1 := Tester.SetExecution(ctx, t, cond1, targets1)
|
||||
response.Result[0].Details = resp1.GetDetails()
|
||||
response.Result[0].Condition = cond1
|
||||
response.Result[0].Execution = &action.Execution{
|
||||
Targets: targets1,
|
||||
}
|
||||
|
||||
cond2 := request.Filters[0].GetInConditionsFilter().GetConditions()[1]
|
||||
targets2 := executionTargetsSingleTarget(targetResp.GetDetails().GetId())
|
||||
resp2 := Tester.SetExecution(ctx, t, cond2, targets2)
|
||||
response.Result[1].Details = resp2.GetDetails()
|
||||
response.Result[1].Condition = cond2
|
||||
response.Result[1].Execution = &action.Execution{
|
||||
Targets: targets2,
|
||||
}
|
||||
|
||||
cond3 := request.Filters[0].GetInConditionsFilter().GetConditions()[2]
|
||||
targets3 := executionTargetsSingleTarget(targetResp.GetDetails().GetId())
|
||||
resp3 := Tester.SetExecution(ctx, t, cond3, targets3)
|
||||
response.Result[2].Details = resp3.GetDetails()
|
||||
response.Result[2].Condition = cond3
|
||||
response.Result[2].Execution = &action.Execution{
|
||||
Targets: targets3,
|
||||
}
|
||||
response.Details.Timestamp = resp3.GetDetails().GetChanged()
|
||||
return nil
|
||||
},
|
||||
req: &action.SearchExecutionsRequest{
|
||||
Filters: []*action.ExecutionSearchFilter{{
|
||||
Filter: &action.ExecutionSearchFilter_InConditionsFilter{
|
||||
InConditionsFilter: &action.InConditionsFilter{
|
||||
Conditions: []*action.Condition{
|
||||
{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Method{
|
||||
Method: "/zitadel.session.v2.SessionService/GetSession",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Method{
|
||||
Method: "/zitadel.session.v2.SessionService/CreateSession",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Method{
|
||||
Method: "/zitadel.session.v2.SessionService/SetSession",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
want: &action.SearchExecutionsResponse{
|
||||
Details: &resource_object.ListDetails{
|
||||
TotalResult: 3,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Result: []*action.GetExecution{
|
||||
{
|
||||
Details: &resource_object.Details{
|
||||
Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID},
|
||||
},
|
||||
}, {
|
||||
Details: &resource_object.Details{
|
||||
Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID},
|
||||
},
|
||||
}, {
|
||||
Details: &resource_object.Details{
|
||||
Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list multiple conditions all types",
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(ctx context.Context, request *action.SearchExecutionsRequest, response *action.SearchExecutionsResponse) error {
|
||||
targets := executionTargetsSingleTarget(targetResp.GetDetails().GetId())
|
||||
for i, cond := range request.Filters[0].GetInConditionsFilter().GetConditions() {
|
||||
resp := Tester.SetExecution(ctx, t, cond, targets)
|
||||
response.Result[i].Details = resp.GetDetails()
|
||||
response.Result[i].Condition = cond
|
||||
response.Result[i].Execution = &action.Execution{
|
||||
Targets: targets,
|
||||
}
|
||||
// filled with info of last sequence
|
||||
response.Details.Timestamp = resp.GetDetails().GetChanged()
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
req: &action.SearchExecutionsRequest{
|
||||
Filters: []*action.ExecutionSearchFilter{{
|
||||
Filter: &action.ExecutionSearchFilter_InConditionsFilter{
|
||||
InConditionsFilter: &action.InConditionsFilter{
|
||||
Conditions: []*action.Condition{
|
||||
{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Method{Method: "/zitadel.session.v2.SessionService/GetSession"}}}},
|
||||
{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Service{Service: "zitadel.session.v2.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.v2.SessionService/GetSession"}}}},
|
||||
{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Service{Service: "zitadel.session.v2.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.SearchExecutionsResponse{
|
||||
Details: &resource_object.ListDetails{
|
||||
TotalResult: 10,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Result: []*action.GetExecution{
|
||||
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}},
|
||||
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}},
|
||||
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}},
|
||||
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}},
|
||||
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}},
|
||||
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}},
|
||||
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}},
|
||||
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}},
|
||||
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}},
|
||||
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.args.dep != nil {
|
||||
err := tt.args.dep(tt.args.ctx, tt.args.req, tt.want)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
retryDuration := 5 * time.Second
|
||||
if ctxDeadline, ok := isolatedIAMOwnerCTX.Deadline(); ok {
|
||||
retryDuration = time.Until(ctxDeadline)
|
||||
}
|
||||
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
got, listErr := Tester.Client.ActionV3.SearchExecutions(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, listErr, "Error: "+listErr.Error())
|
||||
} else {
|
||||
assert.NoError(t, listErr)
|
||||
}
|
||||
if listErr != nil {
|
||||
return
|
||||
}
|
||||
// always first check length, otherwise its failed anyway
|
||||
assert.Len(t, got.Result, len(tt.want.Result))
|
||||
for i := range tt.want.Result {
|
||||
// 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.AssertResourceListDetails(t, tt.want, got)
|
||||
}, retryDuration, time.Millisecond*100, "timeout waiting for expected execution result")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func containExecution(executionList []*action.GetExecution, execution *action.GetExecution) (int, bool) {
|
||||
for i, exec := range executionList {
|
||||
if reflect.DeepEqual(exec.Details, execution.Details) {
|
||||
return i, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||
@@ -17,6 +18,7 @@ var _ action.ZITADELActionsServer = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
action.UnimplementedZITADELActionsServer
|
||||
systemDefaults systemdefaults.SystemDefaults
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
ListActionFunctions func() []string
|
||||
@@ -27,6 +29,7 @@ type Server struct {
|
||||
type Config struct{}
|
||||
|
||||
func CreateServer(
|
||||
systemDefaults systemdefaults.SystemDefaults,
|
||||
command *command.Commands,
|
||||
query *query.Queries,
|
||||
listActionFunctions func() []string,
|
||||
@@ -34,6 +37,7 @@ func CreateServer(
|
||||
listGRPCServices func() []string,
|
||||
) *Server {
|
||||
return &Server{
|
||||
systemDefaults: systemDefaults,
|
||||
command: command,
|
||||
query: query,
|
||||
ListActionFunctions: listActionFunctions,
|
||||
@@ -62,7 +66,7 @@ func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
|
||||
return action.RegisterZITADELActionsHandler
|
||||
}
|
||||
|
||||
func checkExecutionEnabled(ctx context.Context) error {
|
||||
func checkActionsEnabled(ctx context.Context) error {
|
||||
if authz.GetInstance(ctx).Features().Actions {
|
||||
return nil
|
||||
}
|
||||
|
@@ -13,49 +13,48 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
feature "github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
CTX context.Context
|
||||
Tester *integration.Tester
|
||||
Client action.ZITADELActionsClient
|
||||
IAMOwnerCTX, SystemCTX context.Context
|
||||
Tester *integration.Tester
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(func() int {
|
||||
ctx, errCtx, cancel := integration.Contexts(5 * time.Minute)
|
||||
ctx, _, cancel := integration.Contexts(5 * time.Minute)
|
||||
defer cancel()
|
||||
|
||||
Tester = integration.NewTester(ctx)
|
||||
defer Tester.Done()
|
||||
Client = Tester.Client.ActionV3
|
||||
|
||||
CTX, _ = Tester.WithAuthorization(ctx, integration.IAMOwner), errCtx
|
||||
IAMOwnerCTX = Tester.WithAuthorization(ctx, integration.IAMOwner)
|
||||
SystemCTX = Tester.WithAuthorization(ctx, integration.SystemUser)
|
||||
|
||||
return m.Run()
|
||||
}())
|
||||
}
|
||||
|
||||
func ensureFeatureEnabled(t *testing.T) {
|
||||
f, err := Tester.Client.FeatureV2.GetInstanceFeatures(CTX, &feature.GetInstanceFeaturesRequest{
|
||||
func ensureFeatureEnabled(t *testing.T, iamOwnerCTX context.Context) {
|
||||
f, err := Tester.Client.FeatureV2.GetInstanceFeatures(iamOwnerCTX, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
if f.Actions.GetEnabled() {
|
||||
return
|
||||
}
|
||||
_, err = Tester.Client.FeatureV2.SetInstanceFeatures(CTX, &feature.SetInstanceFeaturesRequest{
|
||||
_, err = Tester.Client.FeatureV2.SetInstanceFeatures(iamOwnerCTX, &feature.SetInstanceFeaturesRequest{
|
||||
Actions: gu.Ptr(true),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
retryDuration := time.Minute
|
||||
if ctxDeadline, ok := CTX.Deadline(); ok {
|
||||
if ctxDeadline, ok := iamOwnerCTX.Deadline(); ok {
|
||||
retryDuration = time.Until(ctxDeadline)
|
||||
}
|
||||
require.EventuallyWithT(t,
|
||||
func(ttt *assert.CollectT) {
|
||||
f, err := Tester.Client.FeatureV2.GetInstanceFeatures(CTX, &feature.GetInstanceFeaturesRequest{
|
||||
f, err := Tester.Client.FeatureV2.GetInstanceFeatures(iamOwnerCTX, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
require.NoError(ttt, err)
|
||||
|
@@ -15,45 +15,45 @@ import (
|
||||
)
|
||||
|
||||
func (s *Server) CreateTarget(ctx context.Context, req *action.CreateTargetRequest) (*action.CreateTargetResponse, error) {
|
||||
if err := checkExecutionEnabled(ctx); err != nil {
|
||||
if err := checkActionsEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
add := createTargetToCommand(req)
|
||||
instance := targetOwnerInstance(ctx)
|
||||
details, err := s.command.AddTarget(ctx, add, instance.Id)
|
||||
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||
details, err := s.command.AddTarget(ctx, add, instanceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &action.CreateTargetResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, instance, add.AggregateID),
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) PatchTarget(ctx context.Context, req *action.PatchTargetRequest) (*action.PatchTargetResponse, error) {
|
||||
if err := checkExecutionEnabled(ctx); err != nil {
|
||||
if err := checkActionsEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instance := targetOwnerInstance(ctx)
|
||||
details, err := s.command.ChangeTarget(ctx, patchTargetToCommand(req), instance.Id)
|
||||
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||
details, err := s.command.ChangeTarget(ctx, patchTargetToCommand(req), instanceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &action.PatchTargetResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, instance, req.GetId()),
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteTarget(ctx context.Context, req *action.DeleteTargetRequest) (*action.DeleteTargetResponse, error) {
|
||||
if err := checkExecutionEnabled(ctx); err != nil {
|
||||
if err := checkActionsEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instance := targetOwnerInstance(ctx)
|
||||
details, err := s.command.DeleteTarget(ctx, req.GetId(), instance.Id)
|
||||
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||
details, err := s.command.DeleteTarget(ctx, req.GetId(), instanceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &action.DeleteTargetResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, instance, req.GetId()),
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -112,10 +112,3 @@ func patchTargetToCommand(req *action.PatchTargetRequest) *command.ChangeTarget
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
func targetOwnerInstance(ctx context.Context) *object.Owner {
|
||||
return &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: authz.GetInstance(ctx).InstanceID(),
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ package action_test
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -15,13 +16,13 @@ import (
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||
resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
|
||||
)
|
||||
|
||||
func TestServer_CreateTarget(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
_, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX)
|
||||
ensureFeatureEnabled(t, isolatedIAMOwnerCTX)
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
@@ -39,7 +40,7 @@ func TestServer_CreateTarget(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty name",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: "",
|
||||
},
|
||||
@@ -47,7 +48,7 @@ func TestServer_CreateTarget(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty type",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
TargetType: nil,
|
||||
@@ -56,7 +57,7 @@ func TestServer_CreateTarget(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty webhook url",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
@@ -67,7 +68,7 @@ func TestServer_CreateTarget(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty request response url",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
TargetType: &action.Target_RestCall{
|
||||
@@ -78,7 +79,7 @@ func TestServer_CreateTarget(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty timeout",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
Endpoint: "https://example.com",
|
||||
@@ -91,7 +92,7 @@ func TestServer_CreateTarget(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "async, ok",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
Endpoint: "https://example.com",
|
||||
@@ -101,16 +102,16 @@ func TestServer_CreateTarget(t *testing.T) {
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "webhook, ok",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
Endpoint: "https://example.com",
|
||||
@@ -122,16 +123,16 @@ func TestServer_CreateTarget(t *testing.T) {
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "webhook, interrupt on error, ok",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
Endpoint: "https://example.com",
|
||||
@@ -143,16 +144,16 @@ func TestServer_CreateTarget(t *testing.T) {
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "call, ok",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
Endpoint: "https://example.com",
|
||||
@@ -164,17 +165,17 @@ func TestServer_CreateTarget(t *testing.T) {
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "call, interruptOnError, ok",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
Endpoint: "https://example.com",
|
||||
@@ -186,17 +187,17 @@ func TestServer_CreateTarget(t *testing.T) {
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Client.CreateTarget(tt.ctx, &action.CreateTargetRequest{Target: tt.req})
|
||||
got, err := Tester.Client.ActionV3.CreateTarget(tt.ctx, &action.CreateTargetRequest{Target: tt.req})
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
@@ -208,7 +209,8 @@ func TestServer_CreateTarget(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_PatchTarget(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
_, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX)
|
||||
ensureFeatureEnabled(t, isolatedIAMOwnerCTX)
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *action.PatchTargetRequest
|
||||
@@ -223,7 +225,7 @@ func TestServer_PatchTarget(t *testing.T) {
|
||||
{
|
||||
name: "missing permission",
|
||||
prepare: func(request *action.PatchTargetRequest) error {
|
||||
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||
targetID := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||
request.Id = targetID
|
||||
return nil
|
||||
},
|
||||
@@ -244,7 +246,7 @@ func TestServer_PatchTarget(t *testing.T) {
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.PatchTargetRequest{
|
||||
Target: &action.PatchTarget{
|
||||
Name: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
|
||||
@@ -256,12 +258,12 @@ func TestServer_PatchTarget(t *testing.T) {
|
||||
{
|
||||
name: "change name, ok",
|
||||
prepare: func(request *action.PatchTargetRequest) error {
|
||||
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||
targetID := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||
request.Id = targetID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.PatchTargetRequest{
|
||||
Target: &action.PatchTarget{
|
||||
Name: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
|
||||
@@ -269,22 +271,22 @@ func TestServer_PatchTarget(t *testing.T) {
|
||||
},
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change type, ok",
|
||||
prepare: func(request *action.PatchTargetRequest) error {
|
||||
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||
targetID := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||
request.Id = targetID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.PatchTargetRequest{
|
||||
Target: &action.PatchTarget{
|
||||
TargetType: &action.PatchTarget_RestCall{
|
||||
@@ -296,22 +298,22 @@ func TestServer_PatchTarget(t *testing.T) {
|
||||
},
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change url, ok",
|
||||
prepare: func(request *action.PatchTargetRequest) error {
|
||||
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||
targetID := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||
request.Id = targetID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.PatchTargetRequest{
|
||||
Target: &action.PatchTarget{
|
||||
Endpoint: gu.Ptr("https://example.com/hooks/new"),
|
||||
@@ -319,22 +321,22 @@ func TestServer_PatchTarget(t *testing.T) {
|
||||
},
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change timeout, ok",
|
||||
prepare: func(request *action.PatchTargetRequest) error {
|
||||
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||
targetID := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||
request.Id = targetID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.PatchTargetRequest{
|
||||
Target: &action.PatchTarget{
|
||||
Timeout: durationpb.New(20 * time.Second),
|
||||
@@ -342,22 +344,22 @@ func TestServer_PatchTarget(t *testing.T) {
|
||||
},
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change type async, ok",
|
||||
prepare: func(request *action.PatchTargetRequest) error {
|
||||
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeAsync, false).GetDetails().GetId()
|
||||
targetID := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeAsync, false).GetDetails().GetId()
|
||||
request.Id = targetID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.PatchTargetRequest{
|
||||
Target: &action.PatchTarget{
|
||||
TargetType: &action.PatchTarget_RestAsync{
|
||||
@@ -367,10 +369,10 @@ func TestServer_PatchTarget(t *testing.T) {
|
||||
},
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -380,8 +382,8 @@ func TestServer_PatchTarget(t *testing.T) {
|
||||
err := tt.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
// We want to have the same response no matter how often we call the function
|
||||
Client.PatchTarget(tt.args.ctx, tt.args.req)
|
||||
got, err := Client.PatchTarget(tt.args.ctx, tt.args.req)
|
||||
Tester.Client.ActionV3.PatchTarget(tt.args.ctx, tt.args.req)
|
||||
got, err := Tester.Client.ActionV3.PatchTarget(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
@@ -393,8 +395,9 @@ func TestServer_PatchTarget(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_DeleteTarget(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
target := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false)
|
||||
_, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX)
|
||||
ensureFeatureEnabled(t, isolatedIAMOwnerCTX)
|
||||
target := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false)
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
@@ -412,7 +415,7 @@ func TestServer_DeleteTarget(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty id",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.DeleteTargetRequest{
|
||||
Id: "",
|
||||
},
|
||||
@@ -420,22 +423,22 @@ func TestServer_DeleteTarget(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "delete target",
|
||||
ctx: CTX,
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.DeleteTargetRequest{
|
||||
Id: target.GetDetails().GetId(),
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
Id: instanceID,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Client.DeleteTarget(tt.ctx, tt.req)
|
||||
got, err := Tester.Client.ActionV3.DeleteTarget(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
|
Reference in New Issue
Block a user