mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 14:47:33 +00:00
chore(tests): use a coverage server binary (#8407)
# Which Problems Are Solved Use a single server instance for API integration tests. This optimizes the time taken for the integration test pipeline, because it allows running tests on multiple packages in parallel. Also, it saves time by not start and stopping a zitadel server for every package. # How the Problems Are Solved - Build a binary with `go build -race -cover ....` - Integration tests only construct clients. The server remains running in the background. - The integration package and tested packages now fully utilize the API. No more direct database access trough `query` and `command` packages. - Use Makefile recipes to setup, start and stop the server in the background. - The binary has the race detector enabled - Init and setup jobs are configured to halt immediately on race condition - Because the server runs in the background, races are only logged. When the server is stopped and race logs exist, the Makefile recipe will throw an error and print the logs. - Makefile recipes include logic to print logs and convert coverage reports after the server is stopped. - Some tests need a downstream HTTP server to make requests, like quota and milestones. A new `integration/sink` package creates an HTTP server and uses websockets to forward HTTP request back to the test packages. The package API uses Go channels for abstraction and easy usage. # Additional Changes - Integration test files already used the `//go:build integration` directive. In order to properly split integration from unit tests, integration test files need to be in a `integration_test` subdirectory of their package. - `UseIsolatedInstance` used to overwrite the `Tester.Client` for each instance. Now a `Instance` object is returned with a gRPC client that is connected to the isolated instance's hostname. - The `Tester` type is now `Instance`. The object is created for the first instance, used by default in any test. Isolated instances are also `Instance` objects and therefore benefit from the same methods and values. The first instance and any other us capable of creating an isolated instance over the system API. - All test packages run in an Isolated instance by calling `NewInstance()` - Individual tests that use an isolated instance use `t.Parallel()` # Additional Context - Closes #6684 - https://go.dev/doc/articles/race_detector - https://go.dev/doc/build-cover --------- Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,338 @@
|
||||
//go:build integration
|
||||
|
||||
package action_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server/middleware"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
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) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
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 := instance.DefaultOrg.Id
|
||||
projectID := ""
|
||||
userID := instance.Users.Get(integration.UserTypeIAMOwner).ID
|
||||
|
||||
// create target for target changes
|
||||
targetCreatedName := gofakeit.Name()
|
||||
targetCreatedURL := "https://nonexistent"
|
||||
|
||||
targetCreated := instance.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, domain.TargetTypeCall, false)
|
||||
|
||||
// request received by target
|
||||
wantRequest := &middleware.ContextInfoRequest{FullMethod: fullMethod, InstanceID: instance.ID(), 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 := instance.CreateTarget(ctx, t, "", urlRequest, domain.TargetTypeCall, false)
|
||||
instance.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: instance.ID(),
|
||||
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 := instance.CreateTarget(ctx, t, "", targetResponseURL, domain.TargetTypeCall, false)
|
||||
instance.SetExecution(ctx, t, conditionResponseFullMethod(fullMethod), executionTargetsSingleTarget(targetResponse.GetDetails().GetId()))
|
||||
|
||||
return func() {
|
||||
closeRequest()
|
||||
closeResponse()
|
||||
}, nil
|
||||
},
|
||||
clean: func(ctx context.Context) {
|
||||
instance.DeleteExecution(ctx, t, conditionRequestFullMethod(fullMethod))
|
||||
instance.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: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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 := instance.DefaultOrg.Id
|
||||
projectID := ""
|
||||
userID := instance.Users.Get(integration.UserTypeIAMOwner).ID
|
||||
|
||||
// request received by target
|
||||
wantRequest := &middleware.ContextInfoRequest{FullMethod: fullMethod, InstanceID: instance.ID(), OrgID: orgID, ProjectID: projectID, UserID: userID, Request: request}
|
||||
urlRequest, closeRequest := testServerCall(wantRequest, 0, http.StatusInternalServerError, &action.GetTargetRequest{Id: "notchanged"})
|
||||
|
||||
targetRequest := instance.CreateTarget(ctx, t, "", urlRequest, domain.TargetTypeCall, true)
|
||||
instance.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) {
|
||||
instance.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 := instance.DefaultOrg.Id
|
||||
projectID := ""
|
||||
userID := instance.Users.Get(integration.UserTypeIAMOwner).ID
|
||||
|
||||
// create target for target changes
|
||||
targetCreatedName := gofakeit.Name()
|
||||
targetCreatedURL := "https://nonexistent"
|
||||
|
||||
targetCreated := instance.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: instance.ID(),
|
||||
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 := instance.CreateTarget(ctx, t, "", targetResponseURL, domain.TargetTypeCall, true)
|
||||
instance.SetExecution(ctx, t, conditionResponseFullMethod(fullMethod), executionTargetsSingleTarget(targetResponse.GetDetails().GetId()))
|
||||
|
||||
return func() {
|
||||
closeResponse()
|
||||
}, nil
|
||||
},
|
||||
clean: func(ctx context.Context) {
|
||||
instance.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 := instance.Client.ActionV3Alpha.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())
|
||||
assert.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
|
||||
}
|
@@ -0,0 +1,818 @@
|
||||
//go:build integration
|
||||
|
||||
package action_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"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 executionTargetsSingleTarget(id string) []*action.ExecutionTargetType {
|
||||
return []*action.ExecutionTargetType{{Type: &action.ExecutionTargetType_Target{Target: id}}}
|
||||
}
|
||||
|
||||
func executionTargetsSingleInclude(include *action.Condition) []*action.ExecutionTargetType {
|
||||
return []*action.ExecutionTargetType{{Type: &action.ExecutionTargetType_Include{Include: include}}}
|
||||
}
|
||||
|
||||
func TestServer_SetExecution_Request(t *testing.T) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *action.SetExecutionRequest
|
||||
want *action.SetExecutionResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
ctx: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_All{All: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no condition, error",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "method, not existing",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Method{
|
||||
Method: "/zitadel.session.v2beta.NotExistingService/List",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "method, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Method{
|
||||
Method: "/zitadel.session.v2beta.SessionService/ListSessions",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "service, not existing",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Service{
|
||||
Service: "NotExistingService",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "service, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Service{
|
||||
Service: "zitadel.session.v2beta.SessionService",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_All{
|
||||
All: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
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
|
||||
instance.Client.ActionV3Alpha.SetExecution(tt.ctx, tt.req)
|
||||
got, err := instance.Client.ActionV3Alpha.SetExecution(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertResourceDetails(t, tt.want.Details, got.Details)
|
||||
|
||||
// cleanup to not impact other requests
|
||||
instance.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
executionCond := &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_All{
|
||||
All: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
instance.SetExecution(isolatedIAMOwnerCTX, t,
|
||||
executionCond,
|
||||
executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
)
|
||||
|
||||
circularExecutionService := &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Service{
|
||||
Service: "zitadel.session.v2beta.SessionService",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
instance.SetExecution(isolatedIAMOwnerCTX, t,
|
||||
circularExecutionService,
|
||||
executionTargetsSingleInclude(executionCond),
|
||||
)
|
||||
circularExecutionMethod := &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Method{
|
||||
Method: "/zitadel.session.v2beta.SessionService/ListSessions",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
instance.SetExecution(isolatedIAMOwnerCTX, t,
|
||||
circularExecutionMethod,
|
||||
executionTargetsSingleInclude(circularExecutionService),
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *action.SetExecutionRequest
|
||||
want *action.SetExecutionResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "method, circular error",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: circularExecutionService,
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleInclude(circularExecutionMethod),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "method, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Method{
|
||||
Method: "/zitadel.session.v2beta.SessionService/ListSessions",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleInclude(executionCond),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "service, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Service{
|
||||
Service: "zitadel.session.v2beta.SessionService",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleInclude(executionCond),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
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
|
||||
instance.Client.ActionV3Alpha.SetExecution(tt.ctx, tt.req)
|
||||
got, err := instance.Client.ActionV3Alpha.SetExecution(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertResourceDetails(t, tt.want.Details, got.Details)
|
||||
|
||||
// cleanup to not impact other requests
|
||||
instance.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_SetExecution_Response(t *testing.T) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *action.SetExecutionRequest
|
||||
want *action.SetExecutionResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
ctx: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Response{
|
||||
Response: &action.ResponseExecution{
|
||||
Condition: &action.ResponseExecution_All{All: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no condition, error",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Response{
|
||||
Response: &action.ResponseExecution{},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "method, not existing",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Response{
|
||||
Response: &action.ResponseExecution{
|
||||
Condition: &action.ResponseExecution_Method{
|
||||
Method: "/zitadel.session.v2beta.NotExistingService/List",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "method, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Response{
|
||||
Response: &action.ResponseExecution{
|
||||
Condition: &action.ResponseExecution_Method{
|
||||
Method: "/zitadel.session.v2beta.SessionService/ListSessions",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "service, not existing",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Response{
|
||||
Response: &action.ResponseExecution{
|
||||
Condition: &action.ResponseExecution_Service{
|
||||
Service: "NotExistingService",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "service, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Response{
|
||||
Response: &action.ResponseExecution{
|
||||
Condition: &action.ResponseExecution_Service{
|
||||
Service: "zitadel.session.v2beta.SessionService",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Response{
|
||||
Response: &action.ResponseExecution{
|
||||
Condition: &action.ResponseExecution_All{
|
||||
All: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
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
|
||||
instance.Client.ActionV3Alpha.SetExecution(tt.ctx, tt.req)
|
||||
got, err := instance.Client.ActionV3Alpha.SetExecution(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertResourceDetails(t, tt.want.Details, got.Details)
|
||||
|
||||
// cleanup to not impact other requests
|
||||
instance.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_SetExecution_Event(t *testing.T) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *action.SetExecutionRequest
|
||||
want *action.SetExecutionResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
ctx: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Event{
|
||||
Event: &action.EventExecution{
|
||||
Condition: &action.EventExecution_All{
|
||||
All: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no condition, error",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Event{
|
||||
Event: &action.EventExecution{},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
/*
|
||||
//TODO event existing check
|
||||
|
||||
{
|
||||
name: "event, not existing",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Event{
|
||||
Event: &action.EventExecution{
|
||||
Condition: &action.EventExecution_Event{
|
||||
Event: "xxx",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
*/
|
||||
{
|
||||
name: "event, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Event{
|
||||
Event: &action.EventExecution{
|
||||
Condition: &action.EventExecution_Event{
|
||||
Event: "xxx",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
/*
|
||||
// TODO:
|
||||
|
||||
{
|
||||
name: "group, not existing",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Event{
|
||||
Event: &action.EventExecution{
|
||||
Condition: &action.EventExecution_Group{
|
||||
Group: "xxx",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
*/
|
||||
{
|
||||
name: "group, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Event{
|
||||
Event: &action.EventExecution{
|
||||
Condition: &action.EventExecution_Group{
|
||||
Group: "xxx",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Event{
|
||||
Event: &action.EventExecution{
|
||||
Condition: &action.EventExecution_All{
|
||||
All: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
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
|
||||
instance.Client.ActionV3Alpha.SetExecution(tt.ctx, tt.req)
|
||||
got, err := instance.Client.ActionV3Alpha.SetExecution(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertResourceDetails(t, tt.want.Details, got.Details)
|
||||
|
||||
// cleanup to not impact other requests
|
||||
instance.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_SetExecution_Function(t *testing.T) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *action.SetExecutionRequest
|
||||
want *action.SetExecutionResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
ctx: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Response{
|
||||
Response: &action.ResponseExecution{
|
||||
Condition: &action.ResponseExecution_All{All: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no condition, error",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Response{
|
||||
Response: &action.ResponseExecution{},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "function, not existing",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Function{
|
||||
Function: &action.FunctionExecution{Name: "xxx"},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "function, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Function{
|
||||
Function: &action.FunctionExecution{Name: "Action.Flow.Type.ExternalAuthentication.Action.TriggerType.PostAuthentication"},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
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
|
||||
instance.Client.ActionV3Alpha.SetExecution(tt.ctx, tt.req)
|
||||
got, err := instance.Client.ActionV3Alpha.SetExecution(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertResourceDetails(t, tt.want.Details, got.Details)
|
||||
|
||||
// cleanup to not impact other requests
|
||||
instance.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||
})
|
||||
}
|
||||
}
|
@@ -0,0 +1,906 @@
|
||||
//go:build integration
|
||||
|
||||
package action_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"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) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
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: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
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 := gofakeit.Name()
|
||||
resp := instance.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 := gofakeit.Name()
|
||||
resp := instance.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 := gofakeit.Name()
|
||||
resp := instance.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 := gofakeit.Name()
|
||||
resp := instance.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 := gofakeit.Name()
|
||||
resp := instance.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 := instance.Client.ActionV3Alpha.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) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
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: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
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 := gofakeit.Name()
|
||||
resp := instance.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 := gofakeit.Name()
|
||||
resp := instance.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: instance.ID(),
|
||||
},
|
||||
},
|
||||
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 := gofakeit.Name()
|
||||
name2 := gofakeit.Name()
|
||||
name3 := gofakeit.Name()
|
||||
resp1 := instance.CreateTarget(ctx, t, name1, "https://example.com", domain.TargetTypeWebhook, false)
|
||||
resp2 := instance.CreateTarget(ctx, t, name2, "https://example.com", domain.TargetTypeCall, true)
|
||||
resp3 := instance.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: instance.ID(),
|
||||
},
|
||||
},
|
||||
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: instance.ID(),
|
||||
},
|
||||
},
|
||||
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: instance.ID(),
|
||||
},
|
||||
},
|
||||
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 := instance.Client.ActionV3Alpha.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
|
||||
if !assert.Len(ttt, got.Result, len(tt.want.Result)) {
|
||||
return
|
||||
}
|
||||
for i := range tt.want.Result {
|
||||
integration.AssertResourceDetails(ttt, tt.want.Result[i].GetDetails(), got.Result[i].GetDetails())
|
||||
assert.Equal(ttt, tt.want.Result[i].GetConfig(), got.Result[i].GetConfig())
|
||||
}
|
||||
integration.AssertResourceListDetails(ttt, tt.want, got)
|
||||
}, retryDuration, time.Millisecond*100, "timeout waiting for expected execution result")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_SearchExecutions(t *testing.T) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
targetResp := instance.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: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
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 := instance.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 := instance.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 := instance.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",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
instance.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 := instance.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 := instance.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 := instance.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 := instance.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: instance.ID()},
|
||||
},
|
||||
}, {
|
||||
Details: &resource_object.Details{
|
||||
Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instance.ID()},
|
||||
},
|
||||
}, {
|
||||
Details: &resource_object.Details{
|
||||
Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instance.ID()},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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 := instance.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: instance.ID()}}},
|
||||
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instance.ID()}}},
|
||||
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instance.ID()}}},
|
||||
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instance.ID()}}},
|
||||
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instance.ID()}}},
|
||||
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instance.ID()}}},
|
||||
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instance.ID()}}},
|
||||
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instance.ID()}}},
|
||||
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instance.ID()}}},
|
||||
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instance.ID()}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
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 := instance.Client.ActionV3Alpha.SearchExecutions(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 {
|
||||
// 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(ttt, 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
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
//go:build integration
|
||||
|
||||
package action_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||
)
|
||||
|
||||
var (
|
||||
CTX context.Context
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(func() int {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
CTX = ctx
|
||||
return m.Run()
|
||||
}())
|
||||
}
|
||||
|
||||
func ensureFeatureEnabled(t *testing.T, instance *integration.Instance) {
|
||||
ctx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
if f.Actions.GetEnabled() {
|
||||
return
|
||||
}
|
||||
_, err = instance.Client.FeatureV2.SetInstanceFeatures(ctx, &feature.SetInstanceFeaturesRequest{
|
||||
Actions: gu.Ptr(true),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
retryDuration := time.Minute
|
||||
if ctxDeadline, ok := ctx.Deadline(); ok {
|
||||
retryDuration = time.Until(ctxDeadline)
|
||||
}
|
||||
require.EventuallyWithT(t,
|
||||
func(ttt *assert.CollectT) {
|
||||
f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
assert.NoError(ttt, err)
|
||||
assert.True(ttt, f.Actions.GetEnabled())
|
||||
},
|
||||
retryDuration,
|
||||
time.Second,
|
||||
"timed out waiting for ensuring instance feature")
|
||||
|
||||
require.EventuallyWithT(t,
|
||||
func(ttt *assert.CollectT) {
|
||||
_, err := instance.Client.ActionV3Alpha.ListExecutionMethods(ctx, &action.ListExecutionMethodsRequest{})
|
||||
assert.NoError(ttt, err)
|
||||
},
|
||||
retryDuration,
|
||||
time.Second,
|
||||
"timed out waiting for ensuring instance feature call")
|
||||
}
|
@@ -0,0 +1,454 @@
|
||||
//go:build integration
|
||||
|
||||
package action_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/muhlemmer/gu"
|
||||
"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_CreateTarget(t *testing.T) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *action.Target
|
||||
want *resource_object.Details
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
ctx: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
req: &action.Target{
|
||||
Name: gofakeit.Name(),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty name",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: "",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty type",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: gofakeit.Name(),
|
||||
TargetType: nil,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty webhook url",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: gofakeit.Name(),
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty request response url",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: gofakeit.Name(),
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty timeout",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: gofakeit.Name(),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{},
|
||||
},
|
||||
Timeout: nil,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "async, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: gofakeit.Name(),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestAsync{
|
||||
RestAsync: &action.SetRESTAsync{},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "webhook, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: gofakeit.Name(),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "webhook, interrupt on error, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: gofakeit.Name(),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "call, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: gofakeit.Name(),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "call, interruptOnError, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: gofakeit.Name(),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := instance.Client.ActionV3Alpha.CreateTarget(tt.ctx, &action.CreateTargetRequest{Target: tt.req})
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.want, got.Details)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_PatchTarget(t *testing.T) {
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *action.PatchTargetRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
prepare func(request *action.PatchTargetRequest) error
|
||||
args args
|
||||
want *resource_object.Details
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
prepare: func(request *action.PatchTargetRequest) error {
|
||||
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||
request.Id = targetID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
req: &action.PatchTargetRequest{
|
||||
Target: &action.PatchTarget{
|
||||
Name: gu.Ptr(gofakeit.Name()),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "not existing",
|
||||
prepare: func(request *action.PatchTargetRequest) error {
|
||||
request.Id = "notexisting"
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.PatchTargetRequest{
|
||||
Target: &action.PatchTarget{
|
||||
Name: gu.Ptr(gofakeit.Name()),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "change name, ok",
|
||||
prepare: func(request *action.PatchTargetRequest) error {
|
||||
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||
request.Id = targetID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.PatchTargetRequest{
|
||||
Target: &action.PatchTarget{
|
||||
Name: gu.Ptr(gofakeit.Name()),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change type, ok",
|
||||
prepare: func(request *action.PatchTargetRequest) error {
|
||||
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||
request.Id = targetID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.PatchTargetRequest{
|
||||
Target: &action.PatchTarget{
|
||||
TargetType: &action.PatchTarget_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change url, ok",
|
||||
prepare: func(request *action.PatchTargetRequest) error {
|
||||
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||
request.Id = targetID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.PatchTargetRequest{
|
||||
Target: &action.PatchTarget{
|
||||
Endpoint: gu.Ptr("https://example.com/hooks/new"),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change timeout, ok",
|
||||
prepare: func(request *action.PatchTargetRequest) error {
|
||||
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||
request.Id = targetID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.PatchTargetRequest{
|
||||
Target: &action.PatchTarget{
|
||||
Timeout: durationpb.New(20 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change type async, ok",
|
||||
prepare: func(request *action.PatchTargetRequest) error {
|
||||
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeAsync, false).GetDetails().GetId()
|
||||
request.Id = targetID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.PatchTargetRequest{
|
||||
Target: &action.PatchTarget{
|
||||
TargetType: &action.PatchTarget_RestAsync{
|
||||
RestAsync: &action.SetRESTAsync{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(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
|
||||
instance.Client.ActionV3Alpha.PatchTarget(tt.args.ctx, tt.args.req)
|
||||
got, err := instance.Client.ActionV3Alpha.PatchTarget(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.want, got.Details)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_DeleteTarget(t *testing.T) {
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
iamOwnerCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
target := instance.CreateTarget(iamOwnerCtx, t, "", "https://example.com", domain.TargetTypeWebhook, false)
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *action.DeleteTargetRequest
|
||||
want *resource_object.Details
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
ctx: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
req: &action.DeleteTargetRequest{
|
||||
Id: target.GetDetails().GetId(),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty id",
|
||||
ctx: iamOwnerCtx,
|
||||
req: &action.DeleteTargetRequest{
|
||||
Id: "",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "delete target",
|
||||
ctx: iamOwnerCtx,
|
||||
req: &action.DeleteTargetRequest{
|
||||
Id: target.GetDetails().GetId(),
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := instance.Client.ActionV3Alpha.DeleteTarget(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.want, got.Details)
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user