mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-13 17:35:12 +00:00
feat: add action v2 execution on requests and responses (#7637)
* feat: add execution of targets to grpc calls * feat: add execution of targets to grpc calls * feat: add execution of targets to grpc calls * feat: add execution of targets to grpc calls * feat: add execution of targets to grpc calls * feat: add execution of targets to grpc calls * feat: add execution of targets to grpc calls * feat: split request and response logic to handle the different context information * feat: split request and response logic to handle the different context information * fix: integration test * fix: import alias * fix: refactor execution package * fix: refactor execution interceptor integration and unit tests * fix: refactor execution interceptor integration and unit tests * fix: refactor execution interceptor integration and unit tests * fix: refactor execution interceptor integration and unit tests * fix: refactor execution interceptor integration and unit tests * docs: basic documentation for executions and targets * fix: change order for interceptors * fix: merge back origin/main * fix: change target definition command and query side (#7735) * fix: change target definition command and query side * fix: correct refactoring name changes * fix: correct refactoring name changes * fix: changing execution defintion with target list and type * fix: changing execution definition with target list and type * fix: add back search queries for target and include * fix: projections change for execution with targets suffix table * fix: projections change for execution with targets suffix table * fix: projections change for execution with targets suffix table * fix: projections change for execution with targets suffix table * fix: projections change for execution with targets suffix table * fix: projections change for execution with targets suffix table * fix: projections change for execution with targets suffix table * docs: add example to actions v2 * docs: add example to actions v2 * fix: correct integration tests on query for executions * fix: add separate event for execution v2 as content changed * fix: add separate event for execution v2 as content changed * fix: added review comment changes * fix: added review comment changes * fix: added review comment changes --------- Co-authored-by: adlerhurst <[email protected]> * fix: added review comment changes * fix: added review comment changes * Update internal/api/grpc/server/middleware/execution_interceptor.go Co-authored-by: Silvan <[email protected]> * fix: added review comment changes * fix: added review comment changes * fix: added review comment changes * fix: added review comment changes * fix: added review comment changes * fix: added review comment changes --------- Co-authored-by: adlerhurst <[email protected]> Co-authored-by: Elio Bischof <[email protected]>
This commit is contained in:
co-authored by
adlerhurst
Elio Bischof
parent
7e345444bf
commit
1c5ecba42a
@@ -0,0 +1,323 @@
|
||||
//go:build integration
|
||||
|
||||
package action_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server/middleware"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
||||
)
|
||||
|
||||
func TestServer_ExecutionTarget(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
|
||||
fullMethod := "/zitadel.action.v3alpha.ActionService/GetTargetByID"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
dep func(context.Context, *action.GetTargetByIDRequest, *action.GetTargetByIDResponse) (func(), error)
|
||||
clean func(context.Context)
|
||||
req *action.GetTargetByIDRequest
|
||||
want *action.GetTargetByIDResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "GetTargetByID, request and response, ok",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) (func(), error) {
|
||||
|
||||
instanceID := Tester.Instance.InstanceID()
|
||||
orgID := Tester.Organisation.ID
|
||||
projectID := ""
|
||||
userID := Tester.Users.Get(integration.FirstInstanceUsersKey, integration.IAMOwner).ID
|
||||
|
||||
// create target for target changes
|
||||
targetCreatedName := fmt.Sprint("GetTargetByID", time.Now().UnixNano()+1)
|
||||
targetCreatedURL := "https://nonexistent"
|
||||
|
||||
targetCreated := Tester.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, domain.TargetTypeCall, false)
|
||||
|
||||
// request received by target
|
||||
wantRequest := &middleware.ContextInfoRequest{FullMethod: fullMethod, InstanceID: instanceID, OrgID: orgID, ProjectID: projectID, UserID: userID, Request: request}
|
||||
changedRequest := &action.GetTargetByIDRequest{TargetId: targetCreated.GetId()}
|
||||
// replace original request with different targetID
|
||||
urlRequest, closeRequest := testServerCall(wantRequest, 0, http.StatusOK, changedRequest)
|
||||
targetRequest := Tester.CreateTarget(ctx, t, "", urlRequest, domain.TargetTypeCall, false)
|
||||
Tester.SetExecution(ctx, t, conditionRequestFullMethod(fullMethod), executionTargetsSingleTarget(targetRequest.GetId()))
|
||||
// GetTargetByID with used target
|
||||
request.TargetId = targetRequest.GetId()
|
||||
|
||||
// expected response from the GetTargetByID
|
||||
expectedResponse := &action.GetTargetByIDResponse{
|
||||
Target: &action.Target{
|
||||
TargetId: targetCreated.GetId(),
|
||||
Details: targetCreated.GetDetails(),
|
||||
Name: targetCreatedName,
|
||||
Endpoint: targetCreatedURL,
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
}
|
||||
// has to be set separately because of the pointers
|
||||
response.Target = &action.Target{
|
||||
TargetId: targetCreated.GetId(),
|
||||
Details: targetCreated.GetDetails(),
|
||||
Name: targetCreatedName,
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
}
|
||||
|
||||
// content for partial update
|
||||
changedResponse := &action.GetTargetByIDResponse{
|
||||
Target: &action.Target{
|
||||
TargetId: "changed",
|
||||
},
|
||||
}
|
||||
// change partial updated content on returned response
|
||||
response.Target.TargetId = changedResponse.Target.TargetId
|
||||
|
||||
// response received by target
|
||||
wantResponse := &middleware.ContextInfoResponse{
|
||||
FullMethod: fullMethod,
|
||||
InstanceID: instanceID,
|
||||
OrgID: orgID,
|
||||
ProjectID: projectID,
|
||||
UserID: userID,
|
||||
Request: changedRequest,
|
||||
Response: expectedResponse,
|
||||
}
|
||||
// after request with different targetID, return changed response
|
||||
targetResponseURL, closeResponse := testServerCall(wantResponse, 0, http.StatusOK, changedResponse)
|
||||
targetResponse := Tester.CreateTarget(ctx, t, "", targetResponseURL, domain.TargetTypeCall, false)
|
||||
Tester.SetExecution(ctx, t, conditionResponseFullMethod(fullMethod), executionTargetsSingleTarget(targetResponse.GetId()))
|
||||
|
||||
return func() {
|
||||
closeRequest()
|
||||
closeResponse()
|
||||
}, nil
|
||||
},
|
||||
clean: func(ctx context.Context) {
|
||||
Tester.DeleteExecution(ctx, t, conditionRequestFullMethod(fullMethod))
|
||||
Tester.DeleteExecution(ctx, t, conditionResponseFullMethod(fullMethod))
|
||||
},
|
||||
req: &action.GetTargetByIDRequest{},
|
||||
want: &action.GetTargetByIDResponse{},
|
||||
},
|
||||
/*{
|
||||
name: "GetTargetByID, request, interrupt",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) (func(), error) {
|
||||
|
||||
fullMethod := "/zitadel.action.v3alpha.ActionService/GetTargetByID"
|
||||
instanceID := Tester.Instance.InstanceID()
|
||||
orgID := Tester.Organisation.ID
|
||||
projectID := ""
|
||||
userID := Tester.Users.Get(integration.FirstInstanceUsersKey, integration.IAMOwner).ID
|
||||
|
||||
// request received by target
|
||||
wantRequest := &middleware.ContextInfoRequest{FullMethod: fullMethod, InstanceID: instanceID, OrgID: orgID, ProjectID: projectID, UserID: userID, Request: request}
|
||||
urlRequest, closeRequest := testServerCall(wantRequest, 0, http.StatusInternalServerError, &action.GetTargetByIDRequest{TargetId: "notchanged"})
|
||||
|
||||
targetRequest := Tester.CreateTarget(ctx, t, "", urlRequest, domain.TargetTypeCall, true)
|
||||
Tester.SetExecution(ctx, t, conditionRequestFullMethod(fullMethod), executionTargetsSingleTarget(targetRequest.GetId()))
|
||||
// GetTargetByID with used target
|
||||
request.TargetId = targetRequest.GetId()
|
||||
|
||||
return func() {
|
||||
closeRequest()
|
||||
}, nil
|
||||
},
|
||||
clean: func(ctx context.Context) {
|
||||
Tester.DeleteExecution(ctx, t, conditionRequestFullMethod(fullMethod))
|
||||
},
|
||||
req: &action.GetTargetByIDRequest{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "GetTargetByID, response, interrupt",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) (func(), error) {
|
||||
|
||||
fullMethod := "/zitadel.action.v3alpha.ActionService/GetTargetByID"
|
||||
instanceID := Tester.Instance.InstanceID()
|
||||
orgID := Tester.Organisation.ID
|
||||
projectID := ""
|
||||
userID := Tester.Users.Get(integration.FirstInstanceUsersKey, integration.IAMOwner).ID
|
||||
|
||||
// create target for target changes
|
||||
targetCreatedName := fmt.Sprint("GetTargetByID", time.Now().UnixNano()+1)
|
||||
targetCreatedURL := "https://nonexistent"
|
||||
|
||||
targetCreated := Tester.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, domain.TargetTypeCall, false)
|
||||
|
||||
// GetTargetByID with used target
|
||||
request.TargetId = targetCreated.GetId()
|
||||
|
||||
// expected response from the GetTargetByID
|
||||
expectedResponse := &action.GetTargetByIDResponse{
|
||||
Target: &action.Target{
|
||||
TargetId: targetCreated.GetId(),
|
||||
Details: targetCreated.GetDetails(),
|
||||
Name: targetCreatedName,
|
||||
Endpoint: targetCreatedURL,
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
}
|
||||
|
||||
// content for partial update
|
||||
changedResponse := &action.GetTargetByIDResponse{
|
||||
Target: &action.Target{
|
||||
TargetId: "changed",
|
||||
},
|
||||
}
|
||||
|
||||
// response received by target
|
||||
wantResponse := &middleware.ContextInfoResponse{
|
||||
FullMethod: fullMethod,
|
||||
InstanceID: instanceID,
|
||||
OrgID: orgID,
|
||||
ProjectID: projectID,
|
||||
UserID: userID,
|
||||
Request: request,
|
||||
Response: expectedResponse,
|
||||
}
|
||||
// after request with different targetID, return changed response
|
||||
targetResponseURL, closeResponse := testServerCall(wantResponse, 0, http.StatusInternalServerError, changedResponse)
|
||||
targetResponse := Tester.CreateTarget(ctx, t, "", targetResponseURL, domain.TargetTypeCall, true)
|
||||
Tester.SetExecution(ctx, t, conditionResponseFullMethod(fullMethod), executionTargetsSingleTarget(targetResponse.GetId()))
|
||||
|
||||
return func() {
|
||||
closeResponse()
|
||||
}, nil
|
||||
},
|
||||
clean: func(ctx context.Context) {
|
||||
Tester.DeleteExecution(ctx, t, conditionResponseFullMethod(fullMethod))
|
||||
},
|
||||
req: &action.GetTargetByIDRequest{},
|
||||
wantErr: true,
|
||||
},*/
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.dep != nil {
|
||||
close, err := tt.dep(tt.ctx, tt.req, tt.want)
|
||||
require.NoError(t, err)
|
||||
defer close()
|
||||
}
|
||||
|
||||
got, err := Client.GetTargetByID(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertDetails(t, tt.want.GetTarget(), got.GetTarget())
|
||||
|
||||
assert.Equal(t, tt.want.Target.TargetId, got.Target.TargetId)
|
||||
|
||||
if tt.clean != nil {
|
||||
tt.clean(tt.ctx)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func conditionRequestFullMethod(fullMethod string) *action.Condition {
|
||||
return &action.Condition{
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Method{
|
||||
Method: fullMethod,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func conditionResponseFullMethod(fullMethod string) *action.Condition {
|
||||
return &action.Condition{
|
||||
ConditionType: &action.Condition_Response{
|
||||
Response: &action.ResponseExecution{
|
||||
Condition: &action.ResponseExecution_Method{
|
||||
Method: fullMethod,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func testServerCall(
|
||||
reqBody interface{},
|
||||
sleep time.Duration,
|
||||
statusCode int,
|
||||
respBody interface{},
|
||||
) (string, func()) {
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
http.Error(w, "error, marshall: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
sentBody, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "error, read body: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(data, sentBody) {
|
||||
http.Error(w, "error, equal:\n"+string(data)+"\nsent:\n"+string(sentBody), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if statusCode != http.StatusOK {
|
||||
http.Error(w, "error, statusCode", statusCode)
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(sleep)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
resp, err := json.Marshal(respBody)
|
||||
if err != nil {
|
||||
http.Error(w, "error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if _, err := io.WriteString(w, string(resp)); err != nil {
|
||||
http.Error(w, "error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(handler))
|
||||
|
||||
return server.URL, server.Close
|
||||
}
|
||||
Reference in New Issue
Block a user