mirror of
https://github.com/zitadel/zitadel.git
synced 2025-05-01 06:30:51 +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 <silvan.reusser@gmail.com> * fix: added review comment changes * fix: added review comment changes * Update internal/api/grpc/server/middleware/execution_interceptor.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * 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 <silvan.reusser@gmail.com> Co-authored-by: Elio Bischof <elio@zitadel.com>
This commit is contained in:
parent
7e345444bf
commit
1c5ecba42a
139
docs/docs/apis/actionsv2/execution-local.md
Normal file
139
docs/docs/apis/actionsv2/execution-local.md
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
---
|
||||||
|
title: Actions v2 example execution locally
|
||||||
|
---
|
||||||
|
|
||||||
|
In this guide, you will create a ZITADEL execution and target. After a user is created through the API, the target is called.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before you start, make sure you have everything set up correctly.
|
||||||
|
|
||||||
|
- You need to be at least a ZITADEL [_IAM_OWNER_](/guides/manage/console/managers)
|
||||||
|
- Your ZITADEL instance needs to have the actions feature enabled.
|
||||||
|
|
||||||
|
## Start example target
|
||||||
|
|
||||||
|
To start a simple HTTP server locally, which receives the webhook call, the following code example can be used:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// webhook HandleFunc to read the request body and then print out the contents
|
||||||
|
func webhook(w http.ResponseWriter, req *http.Request) {
|
||||||
|
// read the body content
|
||||||
|
sentBody, err := io.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
// if there was an error while reading the body return an error
|
||||||
|
http.Error(w, "error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// print out the read content
|
||||||
|
fmt.Println(string(sentBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// handle the HTTP call under "/webhook"
|
||||||
|
http.HandleFunc("/webhook", webhook)
|
||||||
|
|
||||||
|
// start an HTTP server with the before defined function to handle the endpoint under "http://localhost:8090"
|
||||||
|
http.ListenAndServe(":8090", nil)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
What happens here is only a target which prints out the received request, which could also be handled with a different logic.
|
||||||
|
|
||||||
|
## Create target
|
||||||
|
|
||||||
|
As you see in the example above the target is created with HTTP and port '8090' and if we want to use it as webhook, the target can be created as follows:
|
||||||
|
|
||||||
|
[Create a target](/apis/resources/action_service_v3/action-service-create-target)
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -L -X POST 'https://$CUSTOM-DOMAIN/v3alpha/targets' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-H 'Accept: application/json' \
|
||||||
|
-H 'Authorization: Bearer <TOKEN>' \
|
||||||
|
--data-raw '{
|
||||||
|
"name": "local webhook",
|
||||||
|
"restWebhook": {
|
||||||
|
"interruptOnError": true
|
||||||
|
},
|
||||||
|
"endpoint": "http://localhost:8090/webhook",
|
||||||
|
"timeout": "10s"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Save the returned ID to set in the execution.
|
||||||
|
|
||||||
|
## Set execution
|
||||||
|
|
||||||
|
To call the target just created before, with the intention to print the request used for user creation by the user V2 API, we define an execution with a method condition.
|
||||||
|
|
||||||
|
[Set an execution](/apis/resources/action_service_v3/action-service-set-execution)
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -L -X PUT 'https://$CUSTOM-DOMAIN/v3alpha/executions' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-H 'Accept: application/json' \
|
||||||
|
-H 'Authorization: Bearer <TOKEN>' \
|
||||||
|
--data-raw '{
|
||||||
|
"condition": {
|
||||||
|
"request": {
|
||||||
|
"method": "/zitadel.user.v2beta.UserService/AddHumanUser"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"target": "<TargetID returned>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example call
|
||||||
|
|
||||||
|
Now on every call on `/zitadel.user.v2beta.UserService/AddHumanUser` the local server prints out the received body of the request:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -L -X PUT 'https://$CUSTOM-DOMAIN/v2beta/users/human' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-H 'Accept: application/json' \
|
||||||
|
-H 'Authorization: Bearer <TOKEN>' \
|
||||||
|
--data-raw '{
|
||||||
|
"profile": {
|
||||||
|
"givenName": "Example_given",
|
||||||
|
"familyName": "Example_family"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"email": "example@example.com"
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Should print out something like, also described under [Sent information Request](./introduction#sent-information-request):
|
||||||
|
```shell
|
||||||
|
{
|
||||||
|
"fullMethod": "/zitadel.user.v2beta.UserService/AddHumanUser",
|
||||||
|
"instanceID": "262851882718855632",
|
||||||
|
"orgID": "262851882718921168",
|
||||||
|
"projectID": "262851882719052240",
|
||||||
|
"userID": "262851882718986704",
|
||||||
|
"request": {
|
||||||
|
"profile": {
|
||||||
|
"given_name": "Example_given",
|
||||||
|
"family_name": "Example_family"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"email": "example@example.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
167
docs/docs/apis/actionsv2/introduction.md
Normal file
167
docs/docs/apis/actionsv2/introduction.md
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
---
|
||||||
|
title: Actions V2
|
||||||
|
---
|
||||||
|
|
||||||
|
This page describes the options you have when defining ZITADEL Actions V2.
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
ZITADEL sends an HTTP Post request to the endpoint set as Target, the received request than can be edited and send back or custom processes can be handled.
|
||||||
|
|
||||||
|
### Sent information Request
|
||||||
|
|
||||||
|
The information sent to the Endpoint is structured as JSON:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"fullMethod": "full method of the GRPC call",
|
||||||
|
"instanceID": "instanceID of the called instance",
|
||||||
|
"orgID": "ID of the organization related to the calling context",
|
||||||
|
"projectID": "ID of the project related to the used application",
|
||||||
|
"userID": "ID of the calling user",
|
||||||
|
"request": "full request of the call"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sent information Response
|
||||||
|
|
||||||
|
The information sent to the Endpoint is structured as JSON:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"fullMethod": "full method of the GRPC call",
|
||||||
|
"instanceID": "instanceID of the called instance",
|
||||||
|
"orgID": "ID of the organization related to the calling context",
|
||||||
|
"projectID": "ID of the project related to the used application",
|
||||||
|
"userID": "ID of the calling user",
|
||||||
|
"request": "full request of the call",
|
||||||
|
"response": "full response of the call"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Target
|
||||||
|
|
||||||
|
The Target describes how ZITADEL interacts with the Endpoint.
|
||||||
|
|
||||||
|
There are different types of Targets:
|
||||||
|
|
||||||
|
- `Webhook`, the call handles the status code but response is irrelevant, can be InterruptOnError
|
||||||
|
- `Call`, the call handles the status code and response, can be InterruptOnError
|
||||||
|
- `Async`, the call handles neither status code nor response, but can be called in parallel with other Targets
|
||||||
|
|
||||||
|
`InterruptOnError` means that the Execution gets interrupted if any of the calls return with a status code >= 400, and the next Target will not be called anymore.
|
||||||
|
|
||||||
|
The API documentation to create a target can be found [here](/apis/resources/action_service_v3/action-service-create-target)
|
||||||
|
|
||||||
|
## Execution
|
||||||
|
|
||||||
|
ZITADEL decides on specific conditions if one or more Targets have to be called.
|
||||||
|
The Execution resource contains 2 parts, the condition and the called targets.
|
||||||
|
|
||||||
|
The condition can be defined for 4 types of processes:
|
||||||
|
|
||||||
|
- `Requests`, before a request is processed by ZITADEL
|
||||||
|
- `Responses`, before a response is sent back to the application
|
||||||
|
- `Functions`, handling specific functionality in the logic of ZITADEL
|
||||||
|
- `Events`, after a specific event happened and was stored in ZITADEL
|
||||||
|
|
||||||
|
The API documentation to set an Execution can be found [here](/apis/resources/action_service_v3/action-service-set-execution)
|
||||||
|
|
||||||
|
### Condition Best Match
|
||||||
|
|
||||||
|
As the conditions can be defined on different levels, ZITADEL tries to find out which Execution is the best match.
|
||||||
|
This means that for example if you have an Execution defined on `all requests`, on the service `zitadel.user.v2beta.UserService` and on `/zitadel.user.v2beta.UserService/AddHumanUser`,
|
||||||
|
ZITADEL would with a call on the `/zitadel.user.v2beta.UserService/AddHumanUser` use the Executions with the following priority:
|
||||||
|
|
||||||
|
1. `/zitadel.user.v2beta.UserService/AddHumanUser`
|
||||||
|
2. `zitadel.user.v2beta.UserService`
|
||||||
|
3. `all`
|
||||||
|
|
||||||
|
If you then have a call on `/zitadel.user.v2beta.UserService/UpdateHumanUser` the following priority would be found:
|
||||||
|
|
||||||
|
1. `zitadel.user.v2beta.UserService`
|
||||||
|
2. `all`
|
||||||
|
|
||||||
|
And if you use a different service, for example `zitadel.session.v2.SessionService`, then the `all` Execution would still be used.
|
||||||
|
|
||||||
|
### Targets and Includes
|
||||||
|
|
||||||
|
An execution can not only contain a list of Targets, but also Includes.
|
||||||
|
The Includes can be defined in the Execution directly, which means you include all defined Targets by a before set Execution.
|
||||||
|
|
||||||
|
If you define 2 Executions as follows:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"condition": {
|
||||||
|
"request": {
|
||||||
|
"service": "zitadel.user.v2beta.UserService"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"target": "<TargetID1>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"condition": {
|
||||||
|
"request": {
|
||||||
|
"method": "/zitadel.user.v2beta.UserService/AddHumanUser"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"target": "<TargetID2>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": {
|
||||||
|
"request": {
|
||||||
|
"service": "zitadel.user.v2beta.UserService"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The called Targets on "/zitadel.user.v2beta.UserService/AddHumanUser" would be, in order:
|
||||||
|
|
||||||
|
1. `<TargetID2>`
|
||||||
|
2. `<TargetID1>`
|
||||||
|
|
||||||
|
### Condition for Requests and Responses
|
||||||
|
|
||||||
|
For Request and Response there are 3 levels the condition can be defined:
|
||||||
|
|
||||||
|
- `Method`, handling a request or response of a specific GRPC full method, which includes the service name and method of the ZITADEL API
|
||||||
|
- `Service`, handling any request or response under a service of the ZITADEL API
|
||||||
|
- `All`, handling any request or response under the ZITADEL API
|
||||||
|
|
||||||
|
The available conditions can be found under:
|
||||||
|
- [All available Methods](/apis/resources/action_service_v3/action-service-list-execution-methods), for example `/zitadel.user.v2beta.UserService/AddHumanUser`
|
||||||
|
- [All available Services](/apis/resources/action_service_v3/action-service-list-execution-services), for example `zitadel.user.v2beta.UserService`
|
||||||
|
|
||||||
|
### Condition for Functions
|
||||||
|
|
||||||
|
Replace the current Actions with the following flows:
|
||||||
|
|
||||||
|
- [Internal Authentication](../actions/internal-authentication)
|
||||||
|
- [External Authentication](../actions/external-authentication)
|
||||||
|
- [Complement Token](../actions/complement-token)
|
||||||
|
- [Customize SAML Response](../actions/customize-samlresponse)
|
||||||
|
|
||||||
|
The available conditions can be found under [all available Functions](/apis/resources/action_service_v3/action-service-list-execution-functions).
|
||||||
|
|
||||||
|
### Condition for Events
|
||||||
|
|
||||||
|
For event there are 3 levels the condition can be defined:
|
||||||
|
|
||||||
|
- Event, handling a specific event
|
||||||
|
- Group, handling a specific group of events
|
||||||
|
- All, handling any event in ZITADEL
|
||||||
|
|
||||||
|
The concept of events can be found under [Events](/concepts/architecture/software#events)
|
40
docs/docs/concepts/features/actions_v2.md
Normal file
40
docs/docs/concepts/features/actions_v2.md
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
title: ZITADEL Actions v2
|
||||||
|
sidebar_label: Actions v2
|
||||||
|
---
|
||||||
|
|
||||||
|
By using ZITADEL actions V2, you can manipulate ZITADELs behavior on specific API calls, events or functions.
|
||||||
|
This is useful when you have special business requirements that ZITADEL doesn't support out-of-the-box.
|
||||||
|
|
||||||
|
:::info
|
||||||
|
We're working on Actions continuously. In the [roadmap](https://zitadel.com/roadmap), you see how we are planning to expand and improve it. Please tell us about your needs and help us prioritize further fixes and features.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Why actions?
|
||||||
|
ZITADEL can't anticipate and solve every possible business rule and integration requirements from all ZITADEL users. Here are some examples:
|
||||||
|
- A business requires domain specific data validation before a user can be created or authenticated.
|
||||||
|
- A business needs to automate tasks. Roles should be assigned to users based on their ADFS 2016+ groups.
|
||||||
|
- A business needs to store metadata on a user that is used for integrating applications.
|
||||||
|
- A business needs to restrict the users who are allowed to register to a certain organization by their email domains.
|
||||||
|
|
||||||
|
With actions, ZITADEL provides a way to solve such problems.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
There are 3 components necessary:
|
||||||
|
- Endpoint, an external endpoint with the desired logic, can be whatever is necessary as long as it can receive an HTTP Post request.
|
||||||
|
- Target, a resource in ZITADEL with all necessary information how to trigger an endpoint
|
||||||
|
- Execution, a resource in ZITADEL with the information when to trigger which targets
|
||||||
|
|
||||||
|
The process is that ZITADEL decides at certain points that with a defined Execution a call to the defined Target(s) is triggered,
|
||||||
|
so that everybody can implement their custom behaviour for as many processes as possible.
|
||||||
|
|
||||||
|
Possible conditions for the Execution:
|
||||||
|
- Request, to react to or manipulate requests to ZITADEL, for example add information to newly created users
|
||||||
|
- Response, to react to or manipulate responses to ZITADEL, for example to provision newly created users to other systems
|
||||||
|
- Function, to react to different functionality in ZITADEL, replaces [Actions](/concepts/features/actions)
|
||||||
|
- Event, to create to different events which get created in ZITADEL, for example to inform somebody if a user gets locked
|
||||||
|
|
||||||
|
## Further reading
|
||||||
|
|
||||||
|
- [Actions v2 example execution locally](/apis/actionsv2/execution-local)
|
||||||
|
- [Actions v2 reference](/apis/actionsv2/introduction#action)
|
@ -795,6 +795,15 @@ module.exports = {
|
|||||||
"apis/actions/objects",
|
"apis/actions/objects",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
label: "Actions V2",
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
"apis/actionsv2/introduction",
|
||||||
|
"apis/actionsv2/execution-local",
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: "doc",
|
type: "doc",
|
||||||
label: "gRPC Status Codes",
|
label: "gRPC Status Codes",
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
||||||
"github.com/zitadel/zitadel/internal/command"
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/repository/execution"
|
||||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,46 +34,46 @@ func (s *Server) SetExecution(ctx context.Context, req *action.SetExecutionReque
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
targets := make([]*execution.Target, len(req.Targets))
|
||||||
|
for i, target := range req.Targets {
|
||||||
|
switch t := target.GetType().(type) {
|
||||||
|
case *action.ExecutionTargetType_Include:
|
||||||
|
include, err := conditionToInclude(t.Include)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
targets[i] = &execution.Target{Type: domain.ExecutionTargetTypeInclude, Target: include}
|
||||||
|
case *action.ExecutionTargetType_Target:
|
||||||
|
targets[i] = &execution.Target{Type: domain.ExecutionTargetTypeTarget, Target: t.Target}
|
||||||
|
}
|
||||||
|
}
|
||||||
set := &command.SetExecution{
|
set := &command.SetExecution{
|
||||||
Targets: req.GetTargets(),
|
Targets: targets,
|
||||||
Includes: req.GetIncludes(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
var details *domain.ObjectDetails
|
var details *domain.ObjectDetails
|
||||||
switch t := req.GetCondition().GetConditionType().(type) {
|
switch t := req.GetCondition().GetConditionType().(type) {
|
||||||
case *action.Condition_Request:
|
case *action.Condition_Request:
|
||||||
cond := &command.ExecutionAPICondition{
|
cond := executionConditionFromRequest(t.Request)
|
||||||
Method: t.Request.GetMethod(),
|
|
||||||
Service: t.Request.GetService(),
|
|
||||||
All: t.Request.GetAll(),
|
|
||||||
}
|
|
||||||
details, err = s.command.SetExecutionRequest(ctx, cond, set, authz.GetInstance(ctx).InstanceID())
|
details, err = s.command.SetExecutionRequest(ctx, cond, set, authz.GetInstance(ctx).InstanceID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case *action.Condition_Response:
|
case *action.Condition_Response:
|
||||||
cond := &command.ExecutionAPICondition{
|
cond := executionConditionFromResponse(t.Response)
|
||||||
Method: t.Response.GetMethod(),
|
|
||||||
Service: t.Response.GetService(),
|
|
||||||
All: t.Response.GetAll(),
|
|
||||||
}
|
|
||||||
details, err = s.command.SetExecutionResponse(ctx, cond, set, authz.GetInstance(ctx).InstanceID())
|
details, err = s.command.SetExecutionResponse(ctx, cond, set, authz.GetInstance(ctx).InstanceID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case *action.Condition_Event:
|
case *action.Condition_Event:
|
||||||
cond := &command.ExecutionEventCondition{
|
cond := executionConditionFromEvent(t.Event)
|
||||||
Event: t.Event.GetEvent(),
|
|
||||||
Group: t.Event.GetGroup(),
|
|
||||||
All: t.Event.GetAll(),
|
|
||||||
}
|
|
||||||
details, err = s.command.SetExecutionEvent(ctx, cond, set, authz.GetInstance(ctx).InstanceID())
|
details, err = s.command.SetExecutionEvent(ctx, cond, set, authz.GetInstance(ctx).InstanceID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case *action.Condition_Function:
|
case *action.Condition_Function:
|
||||||
details, err = s.command.SetExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function), set, authz.GetInstance(ctx).InstanceID())
|
details, err = s.command.SetExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function.GetName()), set, authz.GetInstance(ctx).InstanceID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -82,6 +83,36 @@ func (s *Server) SetExecution(ctx context.Context, req *action.SetExecutionReque
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func conditionToInclude(cond *action.Condition) (string, error) {
|
||||||
|
switch t := cond.GetConditionType().(type) {
|
||||||
|
case *action.Condition_Request:
|
||||||
|
cond := executionConditionFromRequest(t.Request)
|
||||||
|
if err := cond.IsValid(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cond.ID(domain.ExecutionTypeRequest), nil
|
||||||
|
case *action.Condition_Response:
|
||||||
|
cond := executionConditionFromResponse(t.Response)
|
||||||
|
if err := cond.IsValid(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cond.ID(domain.ExecutionTypeRequest), nil
|
||||||
|
case *action.Condition_Event:
|
||||||
|
cond := executionConditionFromEvent(t.Event)
|
||||||
|
if err := cond.IsValid(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cond.ID(), nil
|
||||||
|
case *action.Condition_Function:
|
||||||
|
cond := command.ExecutionFunctionCondition(t.Function.GetName())
|
||||||
|
if err := cond.IsValid(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cond.ID(), nil
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) DeleteExecution(ctx context.Context, req *action.DeleteExecutionRequest) (*action.DeleteExecutionResponse, error) {
|
func (s *Server) DeleteExecution(ctx context.Context, req *action.DeleteExecutionRequest) (*action.DeleteExecutionResponse, error) {
|
||||||
if err := checkExecutionEnabled(ctx); err != nil {
|
if err := checkExecutionEnabled(ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -91,37 +122,25 @@ func (s *Server) DeleteExecution(ctx context.Context, req *action.DeleteExecutio
|
|||||||
var details *domain.ObjectDetails
|
var details *domain.ObjectDetails
|
||||||
switch t := req.GetCondition().GetConditionType().(type) {
|
switch t := req.GetCondition().GetConditionType().(type) {
|
||||||
case *action.Condition_Request:
|
case *action.Condition_Request:
|
||||||
cond := &command.ExecutionAPICondition{
|
cond := executionConditionFromRequest(t.Request)
|
||||||
Method: t.Request.GetMethod(),
|
|
||||||
Service: t.Request.GetService(),
|
|
||||||
All: t.Request.GetAll(),
|
|
||||||
}
|
|
||||||
details, err = s.command.DeleteExecutionRequest(ctx, cond, authz.GetInstance(ctx).InstanceID())
|
details, err = s.command.DeleteExecutionRequest(ctx, cond, authz.GetInstance(ctx).InstanceID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case *action.Condition_Response:
|
case *action.Condition_Response:
|
||||||
cond := &command.ExecutionAPICondition{
|
cond := executionConditionFromResponse(t.Response)
|
||||||
Method: t.Response.GetMethod(),
|
|
||||||
Service: t.Response.GetService(),
|
|
||||||
All: t.Response.GetAll(),
|
|
||||||
}
|
|
||||||
details, err = s.command.DeleteExecutionResponse(ctx, cond, authz.GetInstance(ctx).InstanceID())
|
details, err = s.command.DeleteExecutionResponse(ctx, cond, authz.GetInstance(ctx).InstanceID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case *action.Condition_Event:
|
case *action.Condition_Event:
|
||||||
cond := &command.ExecutionEventCondition{
|
cond := executionConditionFromEvent(t.Event)
|
||||||
Event: t.Event.GetEvent(),
|
|
||||||
Group: t.Event.GetGroup(),
|
|
||||||
All: t.Event.GetAll(),
|
|
||||||
}
|
|
||||||
details, err = s.command.DeleteExecutionEvent(ctx, cond, authz.GetInstance(ctx).InstanceID())
|
details, err = s.command.DeleteExecutionEvent(ctx, cond, authz.GetInstance(ctx).InstanceID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case *action.Condition_Function:
|
case *action.Condition_Function:
|
||||||
details, err = s.command.DeleteExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function), authz.GetInstance(ctx).InstanceID())
|
details, err = s.command.DeleteExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function.GetName()), authz.GetInstance(ctx).InstanceID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -130,3 +149,27 @@ func (s *Server) DeleteExecution(ctx context.Context, req *action.DeleteExecutio
|
|||||||
Details: object.DomainToDetailsPb(details),
|
Details: object.DomainToDetailsPb(details),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func executionConditionFromRequest(request *action.RequestExecution) *command.ExecutionAPICondition {
|
||||||
|
return &command.ExecutionAPICondition{
|
||||||
|
Method: request.GetMethod(),
|
||||||
|
Service: request.GetService(),
|
||||||
|
All: request.GetAll(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func executionConditionFromResponse(response *action.ResponseExecution) *command.ExecutionAPICondition {
|
||||||
|
return &command.ExecutionAPICondition{
|
||||||
|
Method: response.GetMethod(),
|
||||||
|
Service: response.GetService(),
|
||||||
|
All: response.GetAll(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func executionConditionFromEvent(event *action.EventExecution) *command.ExecutionEventCondition {
|
||||||
|
return &command.ExecutionEventCondition{
|
||||||
|
Event: event.GetEvent(),
|
||||||
|
Group: event.GetGroup(),
|
||||||
|
All: event.GetAll(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -9,14 +9,23 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/integration"
|
"github.com/zitadel/zitadel/internal/integration"
|
||||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
||||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func executionTargetsSingleTarget(id string) []*action.ExecutionTargetType {
|
||||||
|
return []*action.ExecutionTargetType{{Type: &action.ExecutionTargetType_Target{Target: id}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func executionTargetsSingleInclude(include *action.Condition) []*action.ExecutionTargetType {
|
||||||
|
return []*action.ExecutionTargetType{{Type: &action.ExecutionTargetType_Include{Include: include}}}
|
||||||
|
}
|
||||||
|
|
||||||
func TestServer_SetExecution_Request(t *testing.T) {
|
func TestServer_SetExecution_Request(t *testing.T) {
|
||||||
ensureFeatureEnabled(t)
|
ensureFeatureEnabled(t)
|
||||||
targetResp := Tester.CreateTarget(CTX, t)
|
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -48,7 +57,7 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
|||||||
Request: &action.RequestExecution{},
|
Request: &action.RequestExecution{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: []string{targetResp.GetId()},
|
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
@ -65,7 +74,7 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: []string{targetResp.GetId()},
|
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
@ -82,7 +91,7 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: []string{targetResp.GetId()},
|
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||||
},
|
},
|
||||||
want: &action.SetExecutionResponse{
|
want: &action.SetExecutionResponse{
|
||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
@ -104,7 +113,7 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: []string{targetResp.GetId()},
|
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
@ -121,7 +130,7 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: []string{targetResp.GetId()},
|
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||||
},
|
},
|
||||||
want: &action.SetExecutionResponse{
|
want: &action.SetExecutionResponse{
|
||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
@ -143,7 +152,7 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: []string{targetResp.GetId()},
|
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||||
},
|
},
|
||||||
want: &action.SetExecutionResponse{
|
want: &action.SetExecutionResponse{
|
||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
@ -163,17 +172,17 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
integration.AssertDetails(t, tt.want, got)
|
integration.AssertDetails(t, tt.want, got)
|
||||||
|
|
||||||
|
// cleanup to not impact other requests
|
||||||
|
Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_SetExecution_Request_Include(t *testing.T) {
|
func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||||
ensureFeatureEnabled(t)
|
ensureFeatureEnabled(t)
|
||||||
|
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||||
targetResp := Tester.CreateTarget(CTX, t)
|
executionCond := &action.Condition{
|
||||||
executionCond := "request"
|
|
||||||
Tester.SetExecution(CTX, t,
|
|
||||||
&action.Condition{
|
|
||||||
ConditionType: &action.Condition_Request{
|
ConditionType: &action.Condition_Request{
|
||||||
Request: &action.RequestExecution{
|
Request: &action.RequestExecution{
|
||||||
Condition: &action.RequestExecution_All{
|
Condition: &action.RequestExecution_All{
|
||||||
@ -181,9 +190,10 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
[]string{targetResp.GetId()},
|
Tester.SetExecution(CTX, t,
|
||||||
[]string{},
|
executionCond,
|
||||||
|
executionTargetsSingleTarget(targetResp.GetId()),
|
||||||
)
|
)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@ -206,7 +216,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Includes: []string{executionCond},
|
Targets: executionTargetsSingleInclude(executionCond),
|
||||||
},
|
},
|
||||||
want: &action.SetExecutionResponse{
|
want: &action.SetExecutionResponse{
|
||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
@ -228,7 +238,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Includes: []string{executionCond},
|
Targets: executionTargetsSingleInclude(executionCond),
|
||||||
},
|
},
|
||||||
want: &action.SetExecutionResponse{
|
want: &action.SetExecutionResponse{
|
||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
@ -237,6 +247,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
/* circular
|
||||||
{
|
{
|
||||||
name: "all, ok",
|
name: "all, ok",
|
||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
@ -250,7 +261,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Includes: []string{executionCond},
|
Targets: executionTargetsSingleInclude(executionCond),
|
||||||
},
|
},
|
||||||
want: &action.SetExecutionResponse{
|
want: &action.SetExecutionResponse{
|
||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
@ -259,6 +270,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@ -270,13 +282,16 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
integration.AssertDetails(t, tt.want, got)
|
integration.AssertDetails(t, tt.want, got)
|
||||||
|
|
||||||
|
// cleanup to not impact other requests
|
||||||
|
Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_DeleteExecution_Request(t *testing.T) {
|
func TestServer_DeleteExecution_Request(t *testing.T) {
|
||||||
ensureFeatureEnabled(t)
|
ensureFeatureEnabled(t)
|
||||||
targetResp := Tester.CreateTarget(CTX, t)
|
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -332,7 +347,7 @@ func TestServer_DeleteExecution_Request(t *testing.T) {
|
|||||||
name: "method, ok",
|
name: "method, ok",
|
||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
||||||
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
|
Tester.SetExecution(ctx, t, request.GetCondition(), executionTargetsSingleTarget(targetResp.GetId()))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
req: &action.DeleteExecutionRequest{
|
req: &action.DeleteExecutionRequest{
|
||||||
@ -373,7 +388,7 @@ func TestServer_DeleteExecution_Request(t *testing.T) {
|
|||||||
name: "service, ok",
|
name: "service, ok",
|
||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
||||||
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
|
Tester.SetExecution(ctx, t, request.GetCondition(), executionTargetsSingleTarget(targetResp.GetId()))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
req: &action.DeleteExecutionRequest{
|
req: &action.DeleteExecutionRequest{
|
||||||
@ -398,7 +413,7 @@ func TestServer_DeleteExecution_Request(t *testing.T) {
|
|||||||
name: "all, ok",
|
name: "all, ok",
|
||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
||||||
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
|
Tester.SetExecution(ctx, t, request.GetCondition(), executionTargetsSingleTarget(targetResp.GetId()))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
req: &action.DeleteExecutionRequest{
|
req: &action.DeleteExecutionRequest{
|
||||||
@ -441,7 +456,7 @@ func TestServer_DeleteExecution_Request(t *testing.T) {
|
|||||||
|
|
||||||
func TestServer_SetExecution_Response(t *testing.T) {
|
func TestServer_SetExecution_Response(t *testing.T) {
|
||||||
ensureFeatureEnabled(t)
|
ensureFeatureEnabled(t)
|
||||||
targetResp := Tester.CreateTarget(CTX, t)
|
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -473,7 +488,7 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
|||||||
Response: &action.ResponseExecution{},
|
Response: &action.ResponseExecution{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: []string{targetResp.GetId()},
|
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
@ -490,7 +505,7 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: []string{targetResp.GetId()},
|
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
@ -507,7 +522,7 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: []string{targetResp.GetId()},
|
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||||
},
|
},
|
||||||
want: &action.SetExecutionResponse{
|
want: &action.SetExecutionResponse{
|
||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
@ -529,7 +544,7 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: []string{targetResp.GetId()},
|
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
@ -546,7 +561,7 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: []string{targetResp.GetId()},
|
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||||
},
|
},
|
||||||
want: &action.SetExecutionResponse{
|
want: &action.SetExecutionResponse{
|
||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
@ -568,7 +583,7 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: []string{targetResp.GetId()},
|
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||||
},
|
},
|
||||||
want: &action.SetExecutionResponse{
|
want: &action.SetExecutionResponse{
|
||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
@ -588,13 +603,16 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
integration.AssertDetails(t, tt.want, got)
|
integration.AssertDetails(t, tt.want, got)
|
||||||
|
|
||||||
|
// cleanup to not impact other requests
|
||||||
|
Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_DeleteExecution_Response(t *testing.T) {
|
func TestServer_DeleteExecution_Response(t *testing.T) {
|
||||||
ensureFeatureEnabled(t)
|
ensureFeatureEnabled(t)
|
||||||
targetResp := Tester.CreateTarget(CTX, t)
|
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -652,7 +670,7 @@ func TestServer_DeleteExecution_Response(t *testing.T) {
|
|||||||
name: "method, ok",
|
name: "method, ok",
|
||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
||||||
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
|
Tester.SetExecution(ctx, t, request.GetCondition(), executionTargetsSingleTarget(targetResp.GetId()))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
req: &action.DeleteExecutionRequest{
|
req: &action.DeleteExecutionRequest{
|
||||||
@ -693,7 +711,7 @@ func TestServer_DeleteExecution_Response(t *testing.T) {
|
|||||||
name: "service, ok",
|
name: "service, ok",
|
||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
||||||
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
|
Tester.SetExecution(ctx, t, request.GetCondition(), executionTargetsSingleTarget(targetResp.GetId()))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
req: &action.DeleteExecutionRequest{
|
req: &action.DeleteExecutionRequest{
|
||||||
@ -718,7 +736,7 @@ func TestServer_DeleteExecution_Response(t *testing.T) {
|
|||||||
name: "all, ok",
|
name: "all, ok",
|
||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
||||||
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
|
Tester.SetExecution(ctx, t, request.GetCondition(), executionTargetsSingleTarget(targetResp.GetId()))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
req: &action.DeleteExecutionRequest{
|
req: &action.DeleteExecutionRequest{
|
||||||
@ -761,7 +779,7 @@ func TestServer_DeleteExecution_Response(t *testing.T) {
|
|||||||
|
|
||||||
func TestServer_SetExecution_Event(t *testing.T) {
|
func TestServer_SetExecution_Event(t *testing.T) {
|
||||||
ensureFeatureEnabled(t)
|
ensureFeatureEnabled(t)
|
||||||
targetResp := Tester.CreateTarget(CTX, t)
|
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -795,7 +813,7 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
|||||||
Event: &action.EventExecution{},
|
Event: &action.EventExecution{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: []string{targetResp.GetId()},
|
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
@ -833,7 +851,7 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: []string{targetResp.GetId()},
|
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||||
},
|
},
|
||||||
want: &action.SetExecutionResponse{
|
want: &action.SetExecutionResponse{
|
||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
@ -876,7 +894,7 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: []string{targetResp.GetId()},
|
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||||
},
|
},
|
||||||
want: &action.SetExecutionResponse{
|
want: &action.SetExecutionResponse{
|
||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
@ -898,7 +916,7 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: []string{targetResp.GetId()},
|
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||||
},
|
},
|
||||||
want: &action.SetExecutionResponse{
|
want: &action.SetExecutionResponse{
|
||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
@ -918,13 +936,16 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
integration.AssertDetails(t, tt.want, got)
|
integration.AssertDetails(t, tt.want, got)
|
||||||
|
|
||||||
|
// cleanup to not impact other requests
|
||||||
|
Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_DeleteExecution_Event(t *testing.T) {
|
func TestServer_DeleteExecution_Event(t *testing.T) {
|
||||||
ensureFeatureEnabled(t)
|
ensureFeatureEnabled(t)
|
||||||
targetResp := Tester.CreateTarget(CTX, t)
|
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -985,7 +1006,7 @@ func TestServer_DeleteExecution_Event(t *testing.T) {
|
|||||||
name: "event, ok",
|
name: "event, ok",
|
||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
||||||
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
|
Tester.SetExecution(ctx, t, request.GetCondition(), executionTargetsSingleTarget(targetResp.GetId()))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
req: &action.DeleteExecutionRequest{
|
req: &action.DeleteExecutionRequest{
|
||||||
@ -1026,7 +1047,7 @@ func TestServer_DeleteExecution_Event(t *testing.T) {
|
|||||||
name: "group, ok",
|
name: "group, ok",
|
||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
||||||
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
|
Tester.SetExecution(ctx, t, request.GetCondition(), executionTargetsSingleTarget(targetResp.GetId()))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
req: &action.DeleteExecutionRequest{
|
req: &action.DeleteExecutionRequest{
|
||||||
@ -1061,18 +1082,13 @@ func TestServer_DeleteExecution_Event(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &action.DeleteExecutionResponse{
|
wantErr: true,
|
||||||
Details: &object.Details{
|
|
||||||
ChangeDate: timestamppb.Now(),
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all, ok",
|
name: "all, ok",
|
||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
||||||
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
|
Tester.SetExecution(ctx, t, request.GetCondition(), executionTargetsSingleTarget(targetResp.GetId()))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
req: &action.DeleteExecutionRequest{
|
req: &action.DeleteExecutionRequest{
|
||||||
@ -1115,7 +1131,7 @@ func TestServer_DeleteExecution_Event(t *testing.T) {
|
|||||||
|
|
||||||
func TestServer_SetExecution_Function(t *testing.T) {
|
func TestServer_SetExecution_Function(t *testing.T) {
|
||||||
ensureFeatureEnabled(t)
|
ensureFeatureEnabled(t)
|
||||||
targetResp := Tester.CreateTarget(CTX, t)
|
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -1147,7 +1163,7 @@ func TestServer_SetExecution_Function(t *testing.T) {
|
|||||||
Response: &action.ResponseExecution{},
|
Response: &action.ResponseExecution{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: []string{targetResp.GetId()},
|
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
@ -1157,10 +1173,10 @@ func TestServer_SetExecution_Function(t *testing.T) {
|
|||||||
req: &action.SetExecutionRequest{
|
req: &action.SetExecutionRequest{
|
||||||
Condition: &action.Condition{
|
Condition: &action.Condition{
|
||||||
ConditionType: &action.Condition_Function{
|
ConditionType: &action.Condition_Function{
|
||||||
Function: "xxx",
|
Function: &action.FunctionExecution{Name: "xxx"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: []string{targetResp.GetId()},
|
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
@ -1170,10 +1186,10 @@ func TestServer_SetExecution_Function(t *testing.T) {
|
|||||||
req: &action.SetExecutionRequest{
|
req: &action.SetExecutionRequest{
|
||||||
Condition: &action.Condition{
|
Condition: &action.Condition{
|
||||||
ConditionType: &action.Condition_Function{
|
ConditionType: &action.Condition_Function{
|
||||||
Function: "Action.Flow.Type.ExternalAuthentication.Action.TriggerType.PostAuthentication",
|
Function: &action.FunctionExecution{Name: "Action.Flow.Type.ExternalAuthentication.Action.TriggerType.PostAuthentication"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Targets: []string{targetResp.GetId()},
|
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||||
},
|
},
|
||||||
want: &action.SetExecutionResponse{
|
want: &action.SetExecutionResponse{
|
||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
@ -1193,13 +1209,16 @@ func TestServer_SetExecution_Function(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
integration.AssertDetails(t, tt.want, got)
|
integration.AssertDetails(t, tt.want, got)
|
||||||
|
|
||||||
|
// cleanup to not impact other requests
|
||||||
|
Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_DeleteExecution_Function(t *testing.T) {
|
func TestServer_DeleteExecution_Function(t *testing.T) {
|
||||||
ensureFeatureEnabled(t)
|
ensureFeatureEnabled(t)
|
||||||
targetResp := Tester.CreateTarget(CTX, t)
|
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -1243,7 +1262,7 @@ func TestServer_DeleteExecution_Function(t *testing.T) {
|
|||||||
req: &action.DeleteExecutionRequest{
|
req: &action.DeleteExecutionRequest{
|
||||||
Condition: &action.Condition{
|
Condition: &action.Condition{
|
||||||
ConditionType: &action.Condition_Function{
|
ConditionType: &action.Condition_Function{
|
||||||
Function: "xxx",
|
Function: &action.FunctionExecution{Name: "xxx"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1253,13 +1272,13 @@ func TestServer_DeleteExecution_Function(t *testing.T) {
|
|||||||
name: "function, ok",
|
name: "function, ok",
|
||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
dep: func(ctx context.Context, request *action.DeleteExecutionRequest) error {
|
||||||
Tester.SetExecution(ctx, t, request.GetCondition(), []string{targetResp.GetId()}, []string{})
|
Tester.SetExecution(ctx, t, request.GetCondition(), executionTargetsSingleTarget(targetResp.GetId()))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
req: &action.DeleteExecutionRequest{
|
req: &action.DeleteExecutionRequest{
|
||||||
Condition: &action.Condition{
|
Condition: &action.Condition{
|
||||||
ConditionType: &action.Condition_Function{
|
ConditionType: &action.Condition_Function{
|
||||||
Function: "Action.Flow.Type.ExternalAuthentication.Action.TriggerType.PostAuthentication",
|
Function: &action.FunctionExecution{Name: "Action.Flow.Type.ExternalAuthentication.Action.TriggerType.PostAuthentication"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,323 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package action_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/grpc/server/middleware"
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/integration"
|
||||||
|
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServer_ExecutionTarget(t *testing.T) {
|
||||||
|
ensureFeatureEnabled(t)
|
||||||
|
|
||||||
|
fullMethod := "/zitadel.action.v3alpha.ActionService/GetTargetByID"
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ctx context.Context
|
||||||
|
dep func(context.Context, *action.GetTargetByIDRequest, *action.GetTargetByIDResponse) (func(), error)
|
||||||
|
clean func(context.Context)
|
||||||
|
req *action.GetTargetByIDRequest
|
||||||
|
want *action.GetTargetByIDResponse
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "GetTargetByID, request and response, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) (func(), error) {
|
||||||
|
|
||||||
|
instanceID := Tester.Instance.InstanceID()
|
||||||
|
orgID := Tester.Organisation.ID
|
||||||
|
projectID := ""
|
||||||
|
userID := Tester.Users.Get(integration.FirstInstanceUsersKey, integration.IAMOwner).ID
|
||||||
|
|
||||||
|
// create target for target changes
|
||||||
|
targetCreatedName := fmt.Sprint("GetTargetByID", time.Now().UnixNano()+1)
|
||||||
|
targetCreatedURL := "https://nonexistent"
|
||||||
|
|
||||||
|
targetCreated := Tester.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, domain.TargetTypeCall, false)
|
||||||
|
|
||||||
|
// request received by target
|
||||||
|
wantRequest := &middleware.ContextInfoRequest{FullMethod: fullMethod, InstanceID: instanceID, OrgID: orgID, ProjectID: projectID, UserID: userID, Request: request}
|
||||||
|
changedRequest := &action.GetTargetByIDRequest{TargetId: targetCreated.GetId()}
|
||||||
|
// replace original request with different targetID
|
||||||
|
urlRequest, closeRequest := testServerCall(wantRequest, 0, http.StatusOK, changedRequest)
|
||||||
|
targetRequest := Tester.CreateTarget(ctx, t, "", urlRequest, domain.TargetTypeCall, false)
|
||||||
|
Tester.SetExecution(ctx, t, conditionRequestFullMethod(fullMethod), executionTargetsSingleTarget(targetRequest.GetId()))
|
||||||
|
// GetTargetByID with used target
|
||||||
|
request.TargetId = targetRequest.GetId()
|
||||||
|
|
||||||
|
// expected response from the GetTargetByID
|
||||||
|
expectedResponse := &action.GetTargetByIDResponse{
|
||||||
|
Target: &action.Target{
|
||||||
|
TargetId: targetCreated.GetId(),
|
||||||
|
Details: targetCreated.GetDetails(),
|
||||||
|
Name: targetCreatedName,
|
||||||
|
Endpoint: targetCreatedURL,
|
||||||
|
TargetType: &action.Target_RestCall{
|
||||||
|
RestCall: &action.SetRESTCall{
|
||||||
|
InterruptOnError: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// has to be set separately because of the pointers
|
||||||
|
response.Target = &action.Target{
|
||||||
|
TargetId: targetCreated.GetId(),
|
||||||
|
Details: targetCreated.GetDetails(),
|
||||||
|
Name: targetCreatedName,
|
||||||
|
TargetType: &action.Target_RestCall{
|
||||||
|
RestCall: &action.SetRESTCall{
|
||||||
|
InterruptOnError: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
}
|
||||||
|
|
||||||
|
// content for partial update
|
||||||
|
changedResponse := &action.GetTargetByIDResponse{
|
||||||
|
Target: &action.Target{
|
||||||
|
TargetId: "changed",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// change partial updated content on returned response
|
||||||
|
response.Target.TargetId = changedResponse.Target.TargetId
|
||||||
|
|
||||||
|
// response received by target
|
||||||
|
wantResponse := &middleware.ContextInfoResponse{
|
||||||
|
FullMethod: fullMethod,
|
||||||
|
InstanceID: instanceID,
|
||||||
|
OrgID: orgID,
|
||||||
|
ProjectID: projectID,
|
||||||
|
UserID: userID,
|
||||||
|
Request: changedRequest,
|
||||||
|
Response: expectedResponse,
|
||||||
|
}
|
||||||
|
// after request with different targetID, return changed response
|
||||||
|
targetResponseURL, closeResponse := testServerCall(wantResponse, 0, http.StatusOK, changedResponse)
|
||||||
|
targetResponse := Tester.CreateTarget(ctx, t, "", targetResponseURL, domain.TargetTypeCall, false)
|
||||||
|
Tester.SetExecution(ctx, t, conditionResponseFullMethod(fullMethod), executionTargetsSingleTarget(targetResponse.GetId()))
|
||||||
|
|
||||||
|
return func() {
|
||||||
|
closeRequest()
|
||||||
|
closeResponse()
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
clean: func(ctx context.Context) {
|
||||||
|
Tester.DeleteExecution(ctx, t, conditionRequestFullMethod(fullMethod))
|
||||||
|
Tester.DeleteExecution(ctx, t, conditionResponseFullMethod(fullMethod))
|
||||||
|
},
|
||||||
|
req: &action.GetTargetByIDRequest{},
|
||||||
|
want: &action.GetTargetByIDResponse{},
|
||||||
|
},
|
||||||
|
/*{
|
||||||
|
name: "GetTargetByID, request, interrupt",
|
||||||
|
ctx: CTX,
|
||||||
|
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) (func(), error) {
|
||||||
|
|
||||||
|
fullMethod := "/zitadel.action.v3alpha.ActionService/GetTargetByID"
|
||||||
|
instanceID := Tester.Instance.InstanceID()
|
||||||
|
orgID := Tester.Organisation.ID
|
||||||
|
projectID := ""
|
||||||
|
userID := Tester.Users.Get(integration.FirstInstanceUsersKey, integration.IAMOwner).ID
|
||||||
|
|
||||||
|
// request received by target
|
||||||
|
wantRequest := &middleware.ContextInfoRequest{FullMethod: fullMethod, InstanceID: instanceID, OrgID: orgID, ProjectID: projectID, UserID: userID, Request: request}
|
||||||
|
urlRequest, closeRequest := testServerCall(wantRequest, 0, http.StatusInternalServerError, &action.GetTargetByIDRequest{TargetId: "notchanged"})
|
||||||
|
|
||||||
|
targetRequest := Tester.CreateTarget(ctx, t, "", urlRequest, domain.TargetTypeCall, true)
|
||||||
|
Tester.SetExecution(ctx, t, conditionRequestFullMethod(fullMethod), executionTargetsSingleTarget(targetRequest.GetId()))
|
||||||
|
// GetTargetByID with used target
|
||||||
|
request.TargetId = targetRequest.GetId()
|
||||||
|
|
||||||
|
return func() {
|
||||||
|
closeRequest()
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
clean: func(ctx context.Context) {
|
||||||
|
Tester.DeleteExecution(ctx, t, conditionRequestFullMethod(fullMethod))
|
||||||
|
},
|
||||||
|
req: &action.GetTargetByIDRequest{},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GetTargetByID, response, interrupt",
|
||||||
|
ctx: CTX,
|
||||||
|
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) (func(), error) {
|
||||||
|
|
||||||
|
fullMethod := "/zitadel.action.v3alpha.ActionService/GetTargetByID"
|
||||||
|
instanceID := Tester.Instance.InstanceID()
|
||||||
|
orgID := Tester.Organisation.ID
|
||||||
|
projectID := ""
|
||||||
|
userID := Tester.Users.Get(integration.FirstInstanceUsersKey, integration.IAMOwner).ID
|
||||||
|
|
||||||
|
// create target for target changes
|
||||||
|
targetCreatedName := fmt.Sprint("GetTargetByID", time.Now().UnixNano()+1)
|
||||||
|
targetCreatedURL := "https://nonexistent"
|
||||||
|
|
||||||
|
targetCreated := Tester.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, domain.TargetTypeCall, false)
|
||||||
|
|
||||||
|
// GetTargetByID with used target
|
||||||
|
request.TargetId = targetCreated.GetId()
|
||||||
|
|
||||||
|
// expected response from the GetTargetByID
|
||||||
|
expectedResponse := &action.GetTargetByIDResponse{
|
||||||
|
Target: &action.Target{
|
||||||
|
TargetId: targetCreated.GetId(),
|
||||||
|
Details: targetCreated.GetDetails(),
|
||||||
|
Name: targetCreatedName,
|
||||||
|
Endpoint: targetCreatedURL,
|
||||||
|
TargetType: &action.Target_RestCall{
|
||||||
|
RestCall: &action.SetRESTCall{
|
||||||
|
InterruptOnError: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// content for partial update
|
||||||
|
changedResponse := &action.GetTargetByIDResponse{
|
||||||
|
Target: &action.Target{
|
||||||
|
TargetId: "changed",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// response received by target
|
||||||
|
wantResponse := &middleware.ContextInfoResponse{
|
||||||
|
FullMethod: fullMethod,
|
||||||
|
InstanceID: instanceID,
|
||||||
|
OrgID: orgID,
|
||||||
|
ProjectID: projectID,
|
||||||
|
UserID: userID,
|
||||||
|
Request: request,
|
||||||
|
Response: expectedResponse,
|
||||||
|
}
|
||||||
|
// after request with different targetID, return changed response
|
||||||
|
targetResponseURL, closeResponse := testServerCall(wantResponse, 0, http.StatusInternalServerError, changedResponse)
|
||||||
|
targetResponse := Tester.CreateTarget(ctx, t, "", targetResponseURL, domain.TargetTypeCall, true)
|
||||||
|
Tester.SetExecution(ctx, t, conditionResponseFullMethod(fullMethod), executionTargetsSingleTarget(targetResponse.GetId()))
|
||||||
|
|
||||||
|
return func() {
|
||||||
|
closeResponse()
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
clean: func(ctx context.Context) {
|
||||||
|
Tester.DeleteExecution(ctx, t, conditionResponseFullMethod(fullMethod))
|
||||||
|
},
|
||||||
|
req: &action.GetTargetByIDRequest{},
|
||||||
|
wantErr: true,
|
||||||
|
},*/
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.dep != nil {
|
||||||
|
close, err := tt.dep(tt.ctx, tt.req, tt.want)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer close()
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := Client.GetTargetByID(tt.ctx, tt.req)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
integration.AssertDetails(t, tt.want.GetTarget(), got.GetTarget())
|
||||||
|
|
||||||
|
assert.Equal(t, tt.want.Target.TargetId, got.Target.TargetId)
|
||||||
|
|
||||||
|
if tt.clean != nil {
|
||||||
|
tt.clean(tt.ctx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func conditionRequestFullMethod(fullMethod string) *action.Condition {
|
||||||
|
return &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{
|
||||||
|
Condition: &action.RequestExecution_Method{
|
||||||
|
Method: fullMethod,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func conditionResponseFullMethod(fullMethod string) *action.Condition {
|
||||||
|
return &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Response{
|
||||||
|
Response: &action.ResponseExecution{
|
||||||
|
Condition: &action.ResponseExecution_Method{
|
||||||
|
Method: fullMethod,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testServerCall(
|
||||||
|
reqBody interface{},
|
||||||
|
sleep time.Duration,
|
||||||
|
statusCode int,
|
||||||
|
respBody interface{},
|
||||||
|
) (string, func()) {
|
||||||
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data, err := json.Marshal(reqBody)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "error, marshall: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sentBody, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "error, read body: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(data, sentBody) {
|
||||||
|
http.Error(w, "error, equal:\n"+string(data)+"\nsent:\n"+string(sentBody), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if statusCode != http.StatusOK {
|
||||||
|
http.Error(w, "error, statusCode", statusCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(sleep)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
resp, err := json.Marshal(respBody)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := io.WriteString(w, string(resp)); err != nil {
|
||||||
|
http.Error(w, "error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(handler))
|
||||||
|
|
||||||
|
return server.URL, server.Close
|
||||||
|
}
|
@ -2,6 +2,7 @@ package action
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"google.golang.org/protobuf/types/known/durationpb"
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
|
||||||
@ -67,8 +68,6 @@ func targetFieldNameToSortingColumn(field action.TargetFieldName) query.Column {
|
|||||||
return query.TargetColumnURL
|
return query.TargetColumnURL
|
||||||
case action.TargetFieldName_FIELD_NAME_TIMEOUT:
|
case action.TargetFieldName_FIELD_NAME_TIMEOUT:
|
||||||
return query.TargetColumnTimeout
|
return query.TargetColumnTimeout
|
||||||
case action.TargetFieldName_FIELD_NAME_ASYNC:
|
|
||||||
return query.TargetColumnAsync
|
|
||||||
case action.TargetFieldName_FIELD_NAME_INTERRUPT_ON_ERROR:
|
case action.TargetFieldName_FIELD_NAME_INTERRUPT_ON_ERROR:
|
||||||
return query.TargetColumnInterruptOnError
|
return query.TargetColumnInterruptOnError
|
||||||
default:
|
default:
|
||||||
@ -134,19 +133,16 @@ func targetToPb(t *query.Target) *action.Target {
|
|||||||
TargetId: t.ID,
|
TargetId: t.ID,
|
||||||
Name: t.Name,
|
Name: t.Name,
|
||||||
Timeout: durationpb.New(t.Timeout),
|
Timeout: durationpb.New(t.Timeout),
|
||||||
}
|
Endpoint: t.Endpoint,
|
||||||
if t.Async {
|
|
||||||
target.ExecutionType = &action.Target_IsAsync{IsAsync: t.Async}
|
|
||||||
}
|
|
||||||
if t.InterruptOnError {
|
|
||||||
target.ExecutionType = &action.Target_InterruptOnError{InterruptOnError: t.InterruptOnError}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t.TargetType {
|
switch t.TargetType {
|
||||||
case domain.TargetTypeWebhook:
|
case domain.TargetTypeWebhook:
|
||||||
target.TargetType = &action.Target_RestWebhook{RestWebhook: &action.SetRESTWebhook{Url: t.URL}}
|
target.TargetType = &action.Target_RestWebhook{RestWebhook: &action.SetRESTWebhook{InterruptOnError: t.InterruptOnError}}
|
||||||
case domain.TargetTypeRequestResponse:
|
case domain.TargetTypeCall:
|
||||||
target.TargetType = &action.Target_RestRequestResponse{RestRequestResponse: &action.SetRESTRequestResponse{Url: t.URL}}
|
target.TargetType = &action.Target_RestCall{RestCall: &action.SetRESTCall{InterruptOnError: t.InterruptOnError}}
|
||||||
|
case domain.TargetTypeAsync:
|
||||||
|
target.TargetType = &action.Target_RestAsync{RestAsync: &action.SetRESTAsync{}}
|
||||||
default:
|
default:
|
||||||
target.TargetType = nil
|
target.TargetType = nil
|
||||||
}
|
}
|
||||||
@ -205,10 +201,14 @@ func executionQueryToQuery(searchQuery *action.SearchQuery) (query.SearchQuery,
|
|||||||
return inConditionsQueryToQuery(q.InConditionsQuery)
|
return inConditionsQueryToQuery(q.InConditionsQuery)
|
||||||
case *action.SearchQuery_ExecutionTypeQuery:
|
case *action.SearchQuery_ExecutionTypeQuery:
|
||||||
return executionTypeToQuery(q.ExecutionTypeQuery)
|
return executionTypeToQuery(q.ExecutionTypeQuery)
|
||||||
case *action.SearchQuery_TargetQuery:
|
|
||||||
return query.NewExecutionTargetSearchQuery(q.TargetQuery.GetTargetId())
|
|
||||||
case *action.SearchQuery_IncludeQuery:
|
case *action.SearchQuery_IncludeQuery:
|
||||||
return query.NewExecutionIncludeSearchQuery(q.IncludeQuery.GetInclude())
|
include, err := conditionToInclude(q.IncludeQuery.GetInclude())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return query.NewIncludeSearchQuery(include)
|
||||||
|
case *action.SearchQuery_TargetQuery:
|
||||||
|
return query.NewTargetSearchQuery(q.TargetQuery.GetTargetId())
|
||||||
default:
|
default:
|
||||||
return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
|
return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
|
||||||
}
|
}
|
||||||
@ -267,7 +267,7 @@ func conditionToID(q *action.Condition) (string, error) {
|
|||||||
}
|
}
|
||||||
return cond.ID(), nil
|
return cond.ID(), nil
|
||||||
case *action.Condition_Function:
|
case *action.Condition_Function:
|
||||||
return t.Function, nil
|
return command.ExecutionFunctionCondition(t.Function.GetName()).ID(), nil
|
||||||
default:
|
default:
|
||||||
return "", zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
|
return "", zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
|
||||||
}
|
}
|
||||||
@ -282,17 +282,83 @@ func executionsToPb(executions []*query.Execution) []*action.Execution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func executionToPb(e *query.Execution) *action.Execution {
|
func executionToPb(e *query.Execution) *action.Execution {
|
||||||
var targets, includes []string
|
targets := make([]*action.ExecutionTargetType, len(e.Targets))
|
||||||
if len(e.Targets) > 0 {
|
for i := range e.Targets {
|
||||||
targets = e.Targets
|
switch e.Targets[i].Type {
|
||||||
|
case domain.ExecutionTargetTypeInclude:
|
||||||
|
targets[i] = &action.ExecutionTargetType{Type: &action.ExecutionTargetType_Include{Include: executionIDToCondition(e.Targets[i].Target)}}
|
||||||
|
case domain.ExecutionTargetTypeTarget:
|
||||||
|
targets[i] = &action.ExecutionTargetType{Type: &action.ExecutionTargetType_Target{Target: e.Targets[i].Target}}
|
||||||
|
case domain.ExecutionTargetTypeUnspecified:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if len(e.Includes) > 0 {
|
|
||||||
includes = e.Includes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &action.Execution{
|
return &action.Execution{
|
||||||
Details: object.DomainToDetailsPb(&e.ObjectDetails),
|
Details: object.DomainToDetailsPb(&e.ObjectDetails),
|
||||||
ExecutionId: e.ID,
|
Condition: executionIDToCondition(e.ID),
|
||||||
Targets: targets,
|
Targets: targets,
|
||||||
Includes: includes,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func executionIDToCondition(include string) *action.Condition {
|
||||||
|
if strings.HasPrefix(include, domain.ExecutionTypeRequest.String()) {
|
||||||
|
return includeRequestToCondition(strings.TrimPrefix(include, domain.ExecutionTypeRequest.String()))
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(include, domain.ExecutionTypeResponse.String()) {
|
||||||
|
return includeResponseToCondition(strings.TrimPrefix(include, domain.ExecutionTypeResponse.String()))
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(include, domain.ExecutionTypeEvent.String()) {
|
||||||
|
return includeEventToCondition(strings.TrimPrefix(include, domain.ExecutionTypeEvent.String()))
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(include, domain.ExecutionTypeFunction.String()) {
|
||||||
|
return includeFunctionToCondition(strings.TrimPrefix(include, domain.ExecutionTypeFunction.String()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func includeRequestToCondition(id string) *action.Condition {
|
||||||
|
switch strings.Count(id, "/") {
|
||||||
|
case 2:
|
||||||
|
return &action.Condition{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Method{Method: id}}}}
|
||||||
|
case 1:
|
||||||
|
return &action.Condition{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Service{Service: strings.TrimPrefix(id, "/")}}}}
|
||||||
|
case 0:
|
||||||
|
return &action.Condition{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_All{All: true}}}}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func includeResponseToCondition(id string) *action.Condition {
|
||||||
|
switch strings.Count(id, "/") {
|
||||||
|
case 2:
|
||||||
|
return &action.Condition{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Method{Method: id}}}}
|
||||||
|
case 1:
|
||||||
|
return &action.Condition{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Service{Service: strings.TrimPrefix(id, "/")}}}}
|
||||||
|
case 0:
|
||||||
|
return &action.Condition{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_All{All: true}}}}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func includeEventToCondition(id string) *action.Condition {
|
||||||
|
switch strings.Count(id, "/") {
|
||||||
|
case 1:
|
||||||
|
if strings.HasSuffix(id, command.EventGroupSuffix) {
|
||||||
|
return &action.Condition{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_Group{Group: strings.TrimSuffix(strings.TrimPrefix(id, "/"), command.EventGroupSuffix)}}}}
|
||||||
|
} else {
|
||||||
|
return &action.Condition{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_Event{Event: strings.TrimPrefix(id, "/")}}}}
|
||||||
|
}
|
||||||
|
case 0:
|
||||||
|
return &action.Condition{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_All{All: true}}}}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func includeFunctionToCondition(id string) *action.Condition {
|
||||||
|
return &action.Condition{ConditionType: &action.Condition_Function{Function: &action.FunctionExecution{Name: strings.TrimPrefix(id, "/")}}}
|
||||||
|
}
|
||||||
|
@ -5,6 +5,7 @@ package action_test
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -12,6 +13,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"google.golang.org/protobuf/types/known/durationpb"
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/integration"
|
"github.com/zitadel/zitadel/internal/integration"
|
||||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
||||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
||||||
@ -52,7 +54,7 @@ func TestServer_GetTargetByID(t *testing.T) {
|
|||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) error {
|
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) error {
|
||||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||||
resp := Tester.CreateTargetWithNameAndType(ctx, t, name, false, false)
|
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false)
|
||||||
request.TargetId = resp.GetId()
|
request.TargetId = resp.GetId()
|
||||||
|
|
||||||
response.Target.TargetId = resp.GetId()
|
response.Target.TargetId = resp.GetId()
|
||||||
@ -69,10 +71,9 @@ func TestServer_GetTargetByID(t *testing.T) {
|
|||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
},
|
},
|
||||||
|
Endpoint: "https://example.com",
|
||||||
TargetType: &action.Target_RestWebhook{
|
TargetType: &action.Target_RestWebhook{
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
RestWebhook: &action.SetRESTWebhook{},
|
||||||
Url: "https://example.com",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
},
|
},
|
||||||
@ -84,7 +85,7 @@ func TestServer_GetTargetByID(t *testing.T) {
|
|||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) error {
|
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) error {
|
||||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||||
resp := Tester.CreateTargetWithNameAndType(ctx, t, name, true, false)
|
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeAsync, false)
|
||||||
request.TargetId = resp.GetId()
|
request.TargetId = resp.GetId()
|
||||||
|
|
||||||
response.Target.TargetId = resp.GetId()
|
response.Target.TargetId = resp.GetId()
|
||||||
@ -101,23 +102,21 @@ func TestServer_GetTargetByID(t *testing.T) {
|
|||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
},
|
},
|
||||||
TargetType: &action.Target_RestWebhook{
|
Endpoint: "https://example.com",
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
TargetType: &action.Target_RestAsync{
|
||||||
Url: "https://example.com",
|
RestAsync: &action.SetRESTAsync{},
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
ExecutionType: &action.Target_IsAsync{IsAsync: true},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "get, interruptOnError, ok",
|
name: "get, webhook interruptOnError, ok",
|
||||||
args: args{
|
args: args{
|
||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) error {
|
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) error {
|
||||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||||
resp := Tester.CreateTargetWithNameAndType(ctx, t, name, false, true)
|
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, true)
|
||||||
request.TargetId = resp.GetId()
|
request.TargetId = resp.GetId()
|
||||||
|
|
||||||
response.Target.TargetId = resp.GetId()
|
response.Target.TargetId = resp.GetId()
|
||||||
@ -134,13 +133,79 @@ func TestServer_GetTargetByID(t *testing.T) {
|
|||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
},
|
},
|
||||||
|
Endpoint: "https://example.com",
|
||||||
TargetType: &action.Target_RestWebhook{
|
TargetType: &action.Target_RestWebhook{
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
RestWebhook: &action.SetRESTWebhook{
|
||||||
Url: "https://example.com",
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get, call, ok",
|
||||||
|
args: args{
|
||||||
|
ctx: CTX,
|
||||||
|
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) error {
|
||||||
|
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||||
|
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeCall, false)
|
||||||
|
request.TargetId = resp.GetId()
|
||||||
|
|
||||||
|
response.Target.TargetId = resp.GetId()
|
||||||
|
response.Target.Name = name
|
||||||
|
response.Target.Details.ResourceOwner = resp.GetDetails().GetResourceOwner()
|
||||||
|
response.Target.Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
||||||
|
response.Target.Details.Sequence = resp.GetDetails().GetSequence()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
req: &action.GetTargetByIDRequest{},
|
||||||
|
},
|
||||||
|
want: &action.GetTargetByIDResponse{
|
||||||
|
Target: &action.Target{
|
||||||
|
Details: &object.Details{
|
||||||
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.Target_RestCall{
|
||||||
|
RestCall: &action.SetRESTCall{
|
||||||
|
InterruptOnError: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get, call interruptOnError, ok",
|
||||||
|
args: args{
|
||||||
|
ctx: CTX,
|
||||||
|
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) error {
|
||||||
|
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||||
|
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeCall, true)
|
||||||
|
request.TargetId = resp.GetId()
|
||||||
|
|
||||||
|
response.Target.TargetId = resp.GetId()
|
||||||
|
response.Target.Name = name
|
||||||
|
response.Target.Details.ResourceOwner = resp.GetDetails().GetResourceOwner()
|
||||||
|
response.Target.Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
||||||
|
response.Target.Details.Sequence = resp.GetDetails().GetSequence()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
req: &action.GetTargetByIDRequest{},
|
||||||
|
},
|
||||||
|
want: &action.GetTargetByIDResponse{
|
||||||
|
Target: &action.Target{
|
||||||
|
Details: &object.Details{
|
||||||
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.Target_RestCall{
|
||||||
|
RestCall: &action.SetRESTCall{
|
||||||
|
InterruptOnError: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
ExecutionType: &action.Target_InterruptOnError{InterruptOnError: true},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -163,15 +228,11 @@ func TestServer_GetTargetByID(t *testing.T) {
|
|||||||
assert.Error(ttt, getErr, "Error: "+getErr.Error())
|
assert.Error(ttt, getErr, "Error: "+getErr.Error())
|
||||||
} else {
|
} else {
|
||||||
assert.NoError(ttt, getErr)
|
assert.NoError(ttt, getErr)
|
||||||
}
|
|
||||||
if getErr != nil {
|
|
||||||
fmt.Println("Error: " + getErr.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
integration.AssertDetails(t, tt.want.GetTarget(), got.GetTarget())
|
integration.AssertDetails(t, tt.want.GetTarget(), got.GetTarget())
|
||||||
|
|
||||||
assert.Equal(t, tt.want.Target, got.Target)
|
assert.Equal(t, tt.want.Target, got.Target)
|
||||||
|
}
|
||||||
|
|
||||||
}, retryDuration, time.Millisecond*100, "timeout waiting for expected execution result")
|
}, retryDuration, time.Millisecond*100, "timeout waiting for expected execution result")
|
||||||
})
|
})
|
||||||
@ -227,14 +288,14 @@ func TestServer_ListTargets(t *testing.T) {
|
|||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
dep: func(ctx context.Context, request *action.ListTargetsRequest, response *action.ListTargetsResponse) error {
|
dep: func(ctx context.Context, request *action.ListTargetsRequest, response *action.ListTargetsResponse) error {
|
||||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||||
resp := Tester.CreateTargetWithNameAndType(ctx, t, name, false, false)
|
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false)
|
||||||
request.Queries[0].Query = &action.TargetSearchQuery_InTargetIdsQuery{
|
request.Queries[0].Query = &action.TargetSearchQuery_InTargetIdsQuery{
|
||||||
InTargetIdsQuery: &action.InTargetIDsQuery{
|
InTargetIdsQuery: &action.InTargetIDsQuery{
|
||||||
TargetIds: []string{resp.GetId()},
|
TargetIds: []string{resp.GetId()},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
response.Details.Timestamp = resp.GetDetails().GetChangeDate()
|
response.Details.Timestamp = resp.GetDetails().GetChangeDate()
|
||||||
response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
|
//response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
|
||||||
|
|
||||||
response.Result[0].Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
response.Result[0].Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
||||||
response.Result[0].Details.Sequence = resp.GetDetails().GetSequence()
|
response.Result[0].Details.Sequence = resp.GetDetails().GetSequence()
|
||||||
@ -255,9 +316,10 @@ func TestServer_ListTargets(t *testing.T) {
|
|||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
},
|
},
|
||||||
|
Endpoint: "https://example.com",
|
||||||
TargetType: &action.Target_RestWebhook{
|
TargetType: &action.Target_RestWebhook{
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
RestWebhook: &action.SetRESTWebhook{
|
||||||
Url: "https://example.com",
|
InterruptOnError: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
@ -270,7 +332,7 @@ func TestServer_ListTargets(t *testing.T) {
|
|||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
dep: func(ctx context.Context, request *action.ListTargetsRequest, response *action.ListTargetsResponse) error {
|
dep: func(ctx context.Context, request *action.ListTargetsRequest, response *action.ListTargetsResponse) error {
|
||||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||||
resp := Tester.CreateTargetWithNameAndType(ctx, t, name, false, false)
|
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false)
|
||||||
request.Queries[0].Query = &action.TargetSearchQuery_TargetNameQuery{
|
request.Queries[0].Query = &action.TargetSearchQuery_TargetNameQuery{
|
||||||
TargetNameQuery: &action.TargetNameQuery{
|
TargetNameQuery: &action.TargetNameQuery{
|
||||||
TargetName: name,
|
TargetName: name,
|
||||||
@ -298,9 +360,10 @@ func TestServer_ListTargets(t *testing.T) {
|
|||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
},
|
},
|
||||||
|
Endpoint: "https://example.com",
|
||||||
TargetType: &action.Target_RestWebhook{
|
TargetType: &action.Target_RestWebhook{
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
RestWebhook: &action.SetRESTWebhook{
|
||||||
Url: "https://example.com",
|
InterruptOnError: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
@ -316,9 +379,9 @@ func TestServer_ListTargets(t *testing.T) {
|
|||||||
name1 := fmt.Sprint(time.Now().UnixNano() + 1)
|
name1 := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||||
name2 := fmt.Sprint(time.Now().UnixNano() + 3)
|
name2 := fmt.Sprint(time.Now().UnixNano() + 3)
|
||||||
name3 := fmt.Sprint(time.Now().UnixNano() + 5)
|
name3 := fmt.Sprint(time.Now().UnixNano() + 5)
|
||||||
resp1 := Tester.CreateTargetWithNameAndType(ctx, t, name1, false, false)
|
resp1 := Tester.CreateTarget(ctx, t, name1, "https://example.com", domain.TargetTypeWebhook, false)
|
||||||
resp2 := Tester.CreateTargetWithNameAndType(ctx, t, name2, true, false)
|
resp2 := Tester.CreateTarget(ctx, t, name2, "https://example.com", domain.TargetTypeCall, true)
|
||||||
resp3 := Tester.CreateTargetWithNameAndType(ctx, t, name3, false, true)
|
resp3 := Tester.CreateTarget(ctx, t, name3, "https://example.com", domain.TargetTypeAsync, false)
|
||||||
request.Queries[0].Query = &action.TargetSearchQuery_InTargetIdsQuery{
|
request.Queries[0].Query = &action.TargetSearchQuery_InTargetIdsQuery{
|
||||||
InTargetIdsQuery: &action.InTargetIDsQuery{
|
InTargetIdsQuery: &action.InTargetIDsQuery{
|
||||||
TargetIds: []string{resp1.GetId(), resp2.GetId(), resp3.GetId()},
|
TargetIds: []string{resp1.GetId(), resp2.GetId(), resp3.GetId()},
|
||||||
@ -354,9 +417,10 @@ func TestServer_ListTargets(t *testing.T) {
|
|||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
},
|
},
|
||||||
|
Endpoint: "https://example.com",
|
||||||
TargetType: &action.Target_RestWebhook{
|
TargetType: &action.Target_RestWebhook{
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
RestWebhook: &action.SetRESTWebhook{
|
||||||
Url: "https://example.com",
|
InterruptOnError: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
@ -365,25 +429,23 @@ func TestServer_ListTargets(t *testing.T) {
|
|||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
},
|
},
|
||||||
TargetType: &action.Target_RestWebhook{
|
Endpoint: "https://example.com",
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
TargetType: &action.Target_RestCall{
|
||||||
Url: "https://example.com",
|
RestCall: &action.SetRESTCall{
|
||||||
|
InterruptOnError: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
ExecutionType: &action.Target_IsAsync{IsAsync: true},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
},
|
},
|
||||||
TargetType: &action.Target_RestWebhook{
|
Endpoint: "https://example.com",
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
TargetType: &action.Target_RestAsync{
|
||||||
Url: "https://example.com",
|
RestAsync: &action.SetRESTAsync{},
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
ExecutionType: &action.Target_InterruptOnError{InterruptOnError: true},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -422,9 +484,9 @@ func TestServer_ListTargets(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_ListExecutions_Request(t *testing.T) {
|
func TestServer_ListExecutions(t *testing.T) {
|
||||||
ensureFeatureEnabled(t)
|
ensureFeatureEnabled(t)
|
||||||
targetResp := Tester.CreateTarget(CTX, t)
|
targetResp := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false)
|
||||||
|
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@ -446,17 +508,20 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
|||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "list single condition",
|
name: "list request single condition",
|
||||||
args: args{
|
args: args{
|
||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error {
|
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error {
|
||||||
resp := Tester.SetExecution(ctx, t, request.Queries[0].GetInConditionsQuery().GetConditions()[0], []string{targetResp.GetId()}, []string{})
|
cond := request.Queries[0].GetInConditionsQuery().GetConditions()[0]
|
||||||
|
resp := Tester.SetExecution(ctx, t, cond, executionTargetsSingleTarget(targetResp.GetId()))
|
||||||
|
|
||||||
response.Details.Timestamp = resp.GetDetails().GetChangeDate()
|
response.Details.Timestamp = resp.GetDetails().GetChangeDate()
|
||||||
response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
|
// response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
|
||||||
|
|
||||||
|
// Set expected response with used values for SetExecution
|
||||||
response.Result[0].Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
response.Result[0].Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
||||||
response.Result[0].Details.Sequence = resp.GetDetails().GetSequence()
|
response.Result[0].Details.Sequence = resp.GetDetails().GetSequence()
|
||||||
|
response.Result[0].Condition = cond
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
req: &action.ListExecutionsRequest{
|
req: &action.ListExecutionsRequest{
|
||||||
@ -471,8 +536,7 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}},
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
@ -487,18 +551,26 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
|||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
},
|
},
|
||||||
ExecutionId: "request./zitadel.session.v2beta.SessionService/GetSession",
|
Condition: &action.Condition{
|
||||||
Targets: []string{targetResp.GetId()},
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{
|
||||||
|
Condition: &action.RequestExecution_Method{
|
||||||
|
Method: "/zitadel.session.v2beta.SessionService/GetSession",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "list single target",
|
name: "list request single target",
|
||||||
args: args{
|
args: args{
|
||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error {
|
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error {
|
||||||
target := Tester.CreateTarget(ctx, t)
|
target := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false)
|
||||||
// add target as query to the request
|
// add target as query to the request
|
||||||
request.Queries[0] = &action.SearchQuery{
|
request.Queries[0] = &action.SearchQuery{
|
||||||
Query: &action.SearchQuery_TargetQuery{
|
Query: &action.SearchQuery_TargetQuery{
|
||||||
@ -507,7 +579,7 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
resp := Tester.SetExecution(ctx, t, &action.Condition{
|
cond := &action.Condition{
|
||||||
ConditionType: &action.Condition_Request{
|
ConditionType: &action.Condition_Request{
|
||||||
Request: &action.RequestExecution{
|
Request: &action.RequestExecution{
|
||||||
Condition: &action.RequestExecution_Method{
|
Condition: &action.RequestExecution_Method{
|
||||||
@ -515,14 +587,17 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, []string{target.GetId()}, []string{})
|
}
|
||||||
|
targets := executionTargetsSingleTarget(target.GetId())
|
||||||
|
resp := Tester.SetExecution(ctx, t, cond, targets)
|
||||||
|
|
||||||
response.Details.Timestamp = resp.GetDetails().GetChangeDate()
|
response.Details.Timestamp = resp.GetDetails().GetChangeDate()
|
||||||
response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
|
response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
|
||||||
|
|
||||||
response.Result[0].Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
response.Result[0].Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
||||||
response.Result[0].Details.Sequence = resp.GetDetails().GetSequence()
|
response.Result[0].Details.Sequence = resp.GetDetails().GetSequence()
|
||||||
response.Result[0].Targets[0] = target.GetId()
|
response.Result[0].Condition = cond
|
||||||
|
response.Result[0].Targets = targets
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
req: &action.ListExecutionsRequest{
|
req: &action.ListExecutionsRequest{
|
||||||
@ -538,17 +613,17 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
|||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
},
|
},
|
||||||
ExecutionId: "request./zitadel.management.v1.ManagementService/UpdateAction",
|
Condition: &action.Condition{},
|
||||||
Targets: []string{""},
|
Targets: executionTargetsSingleTarget(""),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
name: "list single include",
|
name: "list request single include",
|
||||||
args: args{
|
args: args{
|
||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error {
|
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error {
|
||||||
Tester.SetExecution(ctx, t, &action.Condition{
|
cond := &action.Condition{
|
||||||
ConditionType: &action.Condition_Request{
|
ConditionType: &action.Condition_Request{
|
||||||
Request: &action.RequestExecution{
|
Request: &action.RequestExecution{
|
||||||
Condition: &action.RequestExecution_Method{
|
Condition: &action.RequestExecution_Method{
|
||||||
@ -556,8 +631,11 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, []string{targetResp.GetId()}, []string{})
|
}
|
||||||
resp2 := Tester.SetExecution(ctx, t, &action.Condition{
|
Tester.SetExecution(ctx, t, cond, executionTargetsSingleTarget(targetResp.GetId()))
|
||||||
|
request.Queries[0].GetIncludeQuery().Include = cond
|
||||||
|
|
||||||
|
includeCond := &action.Condition{
|
||||||
ConditionType: &action.Condition_Request{
|
ConditionType: &action.Condition_Request{
|
||||||
Request: &action.RequestExecution{
|
Request: &action.RequestExecution{
|
||||||
Condition: &action.RequestExecution_Method{
|
Condition: &action.RequestExecution_Method{
|
||||||
@ -565,19 +643,23 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, []string{}, []string{"request./zitadel.management.v1.ManagementService/GetAction"})
|
}
|
||||||
|
includeTargets := executionTargetsSingleInclude(cond)
|
||||||
|
resp2 := Tester.SetExecution(ctx, t, includeCond, includeTargets)
|
||||||
|
|
||||||
response.Details.Timestamp = resp2.GetDetails().GetChangeDate()
|
response.Details.Timestamp = resp2.GetDetails().GetChangeDate()
|
||||||
response.Details.ProcessedSequence = resp2.GetDetails().GetSequence()
|
response.Details.ProcessedSequence = resp2.GetDetails().GetSequence()
|
||||||
|
|
||||||
response.Result[0].Details.ChangeDate = resp2.GetDetails().GetChangeDate()
|
response.Result[0].Details.ChangeDate = resp2.GetDetails().GetChangeDate()
|
||||||
response.Result[0].Details.Sequence = resp2.GetDetails().GetSequence()
|
response.Result[0].Details.Sequence = resp2.GetDetails().GetSequence()
|
||||||
|
response.Result[0].Condition = includeCond
|
||||||
|
response.Result[0].Targets = includeTargets
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
req: &action.ListExecutionsRequest{
|
req: &action.ListExecutionsRequest{
|
||||||
Queries: []*action.SearchQuery{{
|
Queries: []*action.SearchQuery{{
|
||||||
Query: &action.SearchQuery_IncludeQuery{
|
Query: &action.SearchQuery_IncludeQuery{
|
||||||
IncludeQuery: &action.IncludeQuery{Include: "request./zitadel.management.v1.ManagementService/GetAction"},
|
IncludeQuery: &action.IncludeQuery{},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
@ -591,8 +673,6 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
|||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
},
|
},
|
||||||
ExecutionId: "request./zitadel.management.v1.ManagementService/ListActions",
|
|
||||||
Includes: []string{"request./zitadel.management.v1.ManagementService/GetAction"},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -603,19 +683,32 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
|||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error {
|
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error {
|
||||||
|
|
||||||
resp1 := Tester.SetExecution(ctx, t, request.Queries[0].GetInConditionsQuery().GetConditions()[0], []string{targetResp.GetId()}, []string{})
|
cond1 := request.Queries[0].GetInConditionsQuery().GetConditions()[0]
|
||||||
|
targets1 := executionTargetsSingleTarget(targetResp.GetId())
|
||||||
|
resp1 := Tester.SetExecution(ctx, t, cond1, targets1)
|
||||||
response.Result[0].Details.ChangeDate = resp1.GetDetails().GetChangeDate()
|
response.Result[0].Details.ChangeDate = resp1.GetDetails().GetChangeDate()
|
||||||
response.Result[0].Details.Sequence = resp1.GetDetails().GetSequence()
|
response.Result[0].Details.Sequence = resp1.GetDetails().GetSequence()
|
||||||
|
response.Result[0].Condition = cond1
|
||||||
|
response.Result[0].Targets = targets1
|
||||||
|
|
||||||
resp2 := Tester.SetExecution(ctx, t, request.Queries[0].GetInConditionsQuery().GetConditions()[1], []string{targetResp.GetId()}, []string{})
|
cond2 := request.Queries[0].GetInConditionsQuery().GetConditions()[1]
|
||||||
|
targets2 := executionTargetsSingleTarget(targetResp.GetId())
|
||||||
|
resp2 := Tester.SetExecution(ctx, t, cond2, targets2)
|
||||||
response.Result[1].Details.ChangeDate = resp2.GetDetails().GetChangeDate()
|
response.Result[1].Details.ChangeDate = resp2.GetDetails().GetChangeDate()
|
||||||
response.Result[1].Details.Sequence = resp2.GetDetails().GetSequence()
|
response.Result[1].Details.Sequence = resp2.GetDetails().GetSequence()
|
||||||
|
response.Result[1].Condition = cond2
|
||||||
|
response.Result[1].Targets = targets2
|
||||||
|
|
||||||
resp3 := Tester.SetExecution(ctx, t, request.Queries[0].GetInConditionsQuery().GetConditions()[2], []string{targetResp.GetId()}, []string{})
|
cond3 := request.Queries[0].GetInConditionsQuery().GetConditions()[2]
|
||||||
response.Details.Timestamp = resp3.GetDetails().GetChangeDate()
|
targets3 := executionTargetsSingleTarget(targetResp.GetId())
|
||||||
response.Details.ProcessedSequence = resp3.GetDetails().GetSequence()
|
resp3 := Tester.SetExecution(ctx, t, cond3, targets3)
|
||||||
response.Result[2].Details.ChangeDate = resp3.GetDetails().GetChangeDate()
|
response.Result[2].Details.ChangeDate = resp3.GetDetails().GetChangeDate()
|
||||||
response.Result[2].Details.Sequence = resp3.GetDetails().GetSequence()
|
response.Result[2].Details.Sequence = resp3.GetDetails().GetSequence()
|
||||||
|
response.Result[2].Condition = cond3
|
||||||
|
response.Result[2].Targets = targets3
|
||||||
|
|
||||||
|
response.Details.Timestamp = resp3.GetDetails().GetChangeDate()
|
||||||
|
response.Details.ProcessedSequence = resp3.GetDetails().GetSequence()
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
req: &action.ListExecutionsRequest{
|
req: &action.ListExecutionsRequest{
|
||||||
@ -665,24 +758,77 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
|||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
},
|
},
|
||||||
ExecutionId: "request./zitadel.session.v2beta.SessionService/GetSession",
|
|
||||||
Targets: []string{targetResp.GetId()},
|
|
||||||
}, {
|
}, {
|
||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
},
|
},
|
||||||
ExecutionId: "request./zitadel.session.v2beta.SessionService/CreateSession",
|
|
||||||
Targets: []string{targetResp.GetId()},
|
|
||||||
}, {
|
}, {
|
||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
},
|
},
|
||||||
ExecutionId: "request./zitadel.session.v2beta.SessionService/SetSession",
|
|
||||||
Targets: []string{targetResp.GetId()},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "list multiple conditions all types",
|
||||||
|
args: args{
|
||||||
|
ctx: CTX,
|
||||||
|
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error {
|
||||||
|
targets := executionTargetsSingleTarget(targetResp.GetId())
|
||||||
|
for i, cond := range request.Queries[0].GetInConditionsQuery().GetConditions() {
|
||||||
|
resp := Tester.SetExecution(ctx, t, cond, targets)
|
||||||
|
response.Result[i].Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
||||||
|
response.Result[i].Details.Sequence = resp.GetDetails().GetSequence()
|
||||||
|
response.Result[i].Condition = cond
|
||||||
|
response.Result[i].Targets = targets
|
||||||
|
|
||||||
|
// filled with info of last sequence
|
||||||
|
response.Details.Timestamp = resp.GetDetails().GetChangeDate()
|
||||||
|
response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
req: &action.ListExecutionsRequest{
|
||||||
|
Queries: []*action.SearchQuery{{
|
||||||
|
Query: &action.SearchQuery_InConditionsQuery{
|
||||||
|
InConditionsQuery: &action.InConditionsQuery{
|
||||||
|
Conditions: []*action.Condition{
|
||||||
|
{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Method{Method: "/zitadel.session.v2beta.SessionService/GetSession"}}}},
|
||||||
|
{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Service{Service: "zitadel.session.v2beta.SessionService"}}}},
|
||||||
|
{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_All{All: true}}}},
|
||||||
|
{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Method{Method: "/zitadel.session.v2beta.SessionService/GetSession"}}}},
|
||||||
|
{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Service{Service: "zitadel.session.v2beta.SessionService"}}}},
|
||||||
|
{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_All{All: true}}}},
|
||||||
|
{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_Event{Event: "user.added"}}}},
|
||||||
|
{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_Group{Group: "user"}}}},
|
||||||
|
{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_All{All: true}}}},
|
||||||
|
{ConditionType: &action.Condition_Function{Function: &action.FunctionExecution{Name: "Action.Flow.Type.ExternalAuthentication.Action.TriggerType.PostAuthentication"}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &action.ListExecutionsResponse{
|
||||||
|
Details: &object.ListDetails{
|
||||||
|
TotalResult: 10,
|
||||||
|
},
|
||||||
|
Result: []*action.Execution{
|
||||||
|
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
||||||
|
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
||||||
|
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
||||||
|
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
||||||
|
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
||||||
|
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
||||||
|
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
||||||
|
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
||||||
|
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
||||||
|
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@ -699,20 +845,33 @@ func TestServer_ListExecutions_Request(t *testing.T) {
|
|||||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||||
got, listErr := Client.ListExecutions(tt.args.ctx, tt.args.req)
|
got, listErr := Client.ListExecutions(tt.args.ctx, tt.args.req)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(ttt, listErr, "Error: "+listErr.Error())
|
assert.Error(t, listErr, "Error: "+listErr.Error())
|
||||||
} else {
|
} else {
|
||||||
assert.NoError(ttt, listErr)
|
assert.NoError(t, listErr)
|
||||||
}
|
}
|
||||||
if listErr != nil {
|
if listErr != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// always first check length, otherwise its failed anyway
|
// always first check length, otherwise its failed anyway
|
||||||
assert.Len(ttt, got.Result, len(tt.want.Result))
|
assert.Len(t, got.Result, len(tt.want.Result))
|
||||||
for i := range tt.want.Result {
|
for i := range tt.want.Result {
|
||||||
assert.Contains(ttt, got.Result, tt.want.Result[i])
|
// as not sorted, all elements have to be checked
|
||||||
|
// workaround as oneof elements can only be checked with assert.EqualExportedValues()
|
||||||
|
if j, found := containExecution(got.Result, tt.want.Result[i]); found {
|
||||||
|
assert.EqualExportedValues(t, tt.want.Result[i], got.Result[j])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
integration.AssertListDetails(t, tt.want, got)
|
integration.AssertListDetails(t, tt.want, got)
|
||||||
}, retryDuration, time.Millisecond*100, "timeout waiting for expected execution result")
|
}, retryDuration, time.Millisecond*100, "timeout waiting for expected execution result")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func containExecution(executionList []*action.Execution, execution *action.Execution) (int, bool) {
|
||||||
|
for i, exec := range executionList {
|
||||||
|
if reflect.DeepEqual(exec.Details, execution.Details) {
|
||||||
|
return i, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
@ -66,5 +66,5 @@ func checkExecutionEnabled(ctx context.Context) error {
|
|||||||
if authz.GetInstance(ctx).Features().Actions {
|
if authz.GetInstance(ctx).Features().Actions {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return zerrors.ThrowPreconditionFailed(nil, "SCHEMA-141bwx3lef", "Errors.action.NotEnabled")
|
return zerrors.ThrowPreconditionFailed(nil, "ACTION-8o6pvqfjhs", "Errors.Action.NotEnabled")
|
||||||
}
|
}
|
||||||
|
@ -58,23 +58,26 @@ func (s *Server) DeleteTarget(ctx context.Context, req *action.DeleteTargetReque
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createTargetToCommand(req *action.CreateTargetRequest) *command.AddTarget {
|
func createTargetToCommand(req *action.CreateTargetRequest) *command.AddTarget {
|
||||||
var targetType domain.TargetType
|
var (
|
||||||
var url string
|
targetType domain.TargetType
|
||||||
|
interruptOnError bool
|
||||||
|
)
|
||||||
switch t := req.GetTargetType().(type) {
|
switch t := req.GetTargetType().(type) {
|
||||||
case *action.CreateTargetRequest_RestWebhook:
|
case *action.CreateTargetRequest_RestWebhook:
|
||||||
targetType = domain.TargetTypeWebhook
|
targetType = domain.TargetTypeWebhook
|
||||||
url = t.RestWebhook.GetUrl()
|
interruptOnError = t.RestWebhook.InterruptOnError
|
||||||
case *action.CreateTargetRequest_RestRequestResponse:
|
case *action.CreateTargetRequest_RestCall:
|
||||||
targetType = domain.TargetTypeRequestResponse
|
targetType = domain.TargetTypeCall
|
||||||
url = t.RestRequestResponse.GetUrl()
|
interruptOnError = t.RestCall.InterruptOnError
|
||||||
|
case *action.CreateTargetRequest_RestAsync:
|
||||||
|
targetType = domain.TargetTypeAsync
|
||||||
}
|
}
|
||||||
return &command.AddTarget{
|
return &command.AddTarget{
|
||||||
Name: req.GetName(),
|
Name: req.GetName(),
|
||||||
TargetType: targetType,
|
TargetType: targetType,
|
||||||
URL: url,
|
Endpoint: req.GetEndpoint(),
|
||||||
Timeout: req.GetTimeout().AsDuration(),
|
Timeout: req.GetTimeout().AsDuration(),
|
||||||
Async: req.GetIsAsync(),
|
InterruptOnError: interruptOnError,
|
||||||
InterruptOnError: req.GetInterruptOnError(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,21 +90,23 @@ func updateTargetToCommand(req *action.UpdateTargetRequest) *command.ChangeTarge
|
|||||||
AggregateID: req.GetTargetId(),
|
AggregateID: req.GetTargetId(),
|
||||||
},
|
},
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
|
Endpoint: req.Endpoint,
|
||||||
}
|
}
|
||||||
|
if req.TargetType != nil {
|
||||||
switch t := req.GetTargetType().(type) {
|
switch t := req.GetTargetType().(type) {
|
||||||
case *action.UpdateTargetRequest_RestWebhook:
|
case *action.UpdateTargetRequest_RestWebhook:
|
||||||
target.TargetType = gu.Ptr(domain.TargetTypeWebhook)
|
target.TargetType = gu.Ptr(domain.TargetTypeWebhook)
|
||||||
target.URL = gu.Ptr(t.RestWebhook.GetUrl())
|
target.InterruptOnError = gu.Ptr(t.RestWebhook.InterruptOnError)
|
||||||
case *action.UpdateTargetRequest_RestRequestResponse:
|
case *action.UpdateTargetRequest_RestCall:
|
||||||
target.TargetType = gu.Ptr(domain.TargetTypeRequestResponse)
|
target.TargetType = gu.Ptr(domain.TargetTypeCall)
|
||||||
target.URL = gu.Ptr(t.RestRequestResponse.GetUrl())
|
target.InterruptOnError = gu.Ptr(t.RestCall.InterruptOnError)
|
||||||
|
case *action.UpdateTargetRequest_RestAsync:
|
||||||
|
target.TargetType = gu.Ptr(domain.TargetTypeAsync)
|
||||||
|
target.InterruptOnError = gu.Ptr(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if req.Timeout != nil {
|
if req.Timeout != nil {
|
||||||
target.Timeout = gu.Ptr(req.GetTimeout().AsDuration())
|
target.Timeout = gu.Ptr(req.GetTimeout().AsDuration())
|
||||||
}
|
}
|
||||||
if req.ExecutionType != nil {
|
|
||||||
target.Async = gu.Ptr(req.GetIsAsync())
|
|
||||||
target.InterruptOnError = gu.Ptr(req.GetInterruptOnError())
|
|
||||||
}
|
|
||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"google.golang.org/protobuf/types/known/durationpb"
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/integration"
|
"github.com/zitadel/zitadel/internal/integration"
|
||||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
||||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
||||||
@ -69,8 +70,8 @@ func TestServer_CreateTarget(t *testing.T) {
|
|||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
req: &action.CreateTargetRequest{
|
req: &action.CreateTargetRequest{
|
||||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
TargetType: &action.CreateTargetRequest_RestRequestResponse{
|
TargetType: &action.CreateTargetRequest_RestCall{
|
||||||
RestRequestResponse: &action.SetRESTRequestResponse{},
|
RestCall: &action.SetRESTCall{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
@ -80,28 +81,24 @@ func TestServer_CreateTarget(t *testing.T) {
|
|||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
req: &action.CreateTargetRequest{
|
req: &action.CreateTargetRequest{
|
||||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
Endpoint: "https://example.com",
|
||||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
TargetType: &action.CreateTargetRequest_RestWebhook{
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
RestWebhook: &action.SetRESTWebhook{},
|
||||||
Url: "https://example.com",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Timeout: nil,
|
Timeout: nil,
|
||||||
ExecutionType: nil,
|
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty execution type, ok",
|
name: "async, ok",
|
||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
req: &action.CreateTargetRequest{
|
req: &action.CreateTargetRequest{
|
||||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
Endpoint: "https://example.com",
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
TargetType: &action.CreateTargetRequest_RestAsync{
|
||||||
Url: "https://example.com",
|
RestAsync: &action.SetRESTAsync{},
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
ExecutionType: nil,
|
|
||||||
},
|
},
|
||||||
want: &action.CreateTargetResponse{
|
want: &action.CreateTargetResponse{
|
||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
@ -111,19 +108,17 @@ func TestServer_CreateTarget(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "async execution, ok",
|
name: "webhook, ok",
|
||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
req: &action.CreateTargetRequest{
|
req: &action.CreateTargetRequest{
|
||||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
Endpoint: "https://example.com",
|
||||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
TargetType: &action.CreateTargetRequest_RestWebhook{
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
RestWebhook: &action.SetRESTWebhook{
|
||||||
Url: "https://example.com",
|
InterruptOnError: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
ExecutionType: &action.CreateTargetRequest_IsAsync{
|
|
||||||
IsAsync: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
want: &action.CreateTargetResponse{
|
want: &action.CreateTargetResponse{
|
||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
@ -133,20 +128,59 @@ func TestServer_CreateTarget(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "interrupt on error execution, ok",
|
name: "webhook, interrupt on error, ok",
|
||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
req: &action.CreateTargetRequest{
|
req: &action.CreateTargetRequest{
|
||||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
Endpoint: "https://example.com",
|
||||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
TargetType: &action.CreateTargetRequest_RestWebhook{
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
RestWebhook: &action.SetRESTWebhook{
|
||||||
Url: "https://example.com",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
|
||||||
ExecutionType: &action.CreateTargetRequest_InterruptOnError{
|
|
||||||
InterruptOnError: true,
|
InterruptOnError: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
},
|
||||||
|
want: &action.CreateTargetResponse{
|
||||||
|
Details: &object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "call, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.CreateTargetRequest{
|
||||||
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.CreateTargetRequest_RestCall{
|
||||||
|
RestCall: &action.SetRESTCall{
|
||||||
|
InterruptOnError: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
},
|
||||||
|
want: &action.CreateTargetResponse{
|
||||||
|
Details: &object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
ResourceOwner: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "call, interruptOnError, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.CreateTargetRequest{
|
||||||
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.CreateTargetRequest_RestCall{
|
||||||
|
RestCall: &action.SetRESTCall{
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
},
|
||||||
want: &action.CreateTargetResponse{
|
want: &action.CreateTargetResponse{
|
||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
ChangeDate: timestamppb.Now(),
|
ChangeDate: timestamppb.Now(),
|
||||||
@ -186,7 +220,7 @@ func TestServer_UpdateTarget(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "missing permission",
|
name: "missing permission",
|
||||||
prepare: func(request *action.UpdateTargetRequest) error {
|
prepare: func(request *action.UpdateTargetRequest) error {
|
||||||
targetID := Tester.CreateTarget(CTX, t).GetId()
|
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||||
request.TargetId = targetID
|
request.TargetId = targetID
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@ -215,7 +249,7 @@ func TestServer_UpdateTarget(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "change name, ok",
|
name: "change name, ok",
|
||||||
prepare: func(request *action.UpdateTargetRequest) error {
|
prepare: func(request *action.UpdateTargetRequest) error {
|
||||||
targetID := Tester.CreateTarget(CTX, t).GetId()
|
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||||
request.TargetId = targetID
|
request.TargetId = targetID
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@ -235,16 +269,16 @@ func TestServer_UpdateTarget(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "change type, ok",
|
name: "change type, ok",
|
||||||
prepare: func(request *action.UpdateTargetRequest) error {
|
prepare: func(request *action.UpdateTargetRequest) error {
|
||||||
targetID := Tester.CreateTarget(CTX, t).GetId()
|
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||||
request.TargetId = targetID
|
request.TargetId = targetID
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
req: &action.UpdateTargetRequest{
|
req: &action.UpdateTargetRequest{
|
||||||
TargetType: &action.UpdateTargetRequest_RestRequestResponse{
|
TargetType: &action.UpdateTargetRequest_RestCall{
|
||||||
RestRequestResponse: &action.SetRESTRequestResponse{
|
RestCall: &action.SetRESTCall{
|
||||||
Url: "https://example.com",
|
InterruptOnError: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -259,18 +293,14 @@ func TestServer_UpdateTarget(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "change url, ok",
|
name: "change url, ok",
|
||||||
prepare: func(request *action.UpdateTargetRequest) error {
|
prepare: func(request *action.UpdateTargetRequest) error {
|
||||||
targetID := Tester.CreateTarget(CTX, t).GetId()
|
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||||
request.TargetId = targetID
|
request.TargetId = targetID
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
req: &action.UpdateTargetRequest{
|
req: &action.UpdateTargetRequest{
|
||||||
TargetType: &action.UpdateTargetRequest_RestWebhook{
|
Endpoint: gu.Ptr("https://example.com/hooks/new"),
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
|
||||||
Url: "https://example.com/hooks/new",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &action.UpdateTargetResponse{
|
want: &action.UpdateTargetResponse{
|
||||||
@ -283,7 +313,7 @@ func TestServer_UpdateTarget(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "change timeout, ok",
|
name: "change timeout, ok",
|
||||||
prepare: func(request *action.UpdateTargetRequest) error {
|
prepare: func(request *action.UpdateTargetRequest) error {
|
||||||
targetID := Tester.CreateTarget(CTX, t).GetId()
|
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||||
request.TargetId = targetID
|
request.TargetId = targetID
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@ -301,17 +331,17 @@ func TestServer_UpdateTarget(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "change execution type, ok",
|
name: "change type async, ok",
|
||||||
prepare: func(request *action.UpdateTargetRequest) error {
|
prepare: func(request *action.UpdateTargetRequest) error {
|
||||||
targetID := Tester.CreateTarget(CTX, t).GetId()
|
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeAsync, false).GetId()
|
||||||
request.TargetId = targetID
|
request.TargetId = targetID
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: CTX,
|
ctx: CTX,
|
||||||
req: &action.UpdateTargetRequest{
|
req: &action.UpdateTargetRequest{
|
||||||
ExecutionType: &action.UpdateTargetRequest_IsAsync{
|
TargetType: &action.UpdateTargetRequest_RestAsync{
|
||||||
IsAsync: true,
|
RestAsync: &action.SetRESTAsync{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -341,7 +371,7 @@ func TestServer_UpdateTarget(t *testing.T) {
|
|||||||
|
|
||||||
func TestServer_DeleteTarget(t *testing.T) {
|
func TestServer_DeleteTarget(t *testing.T) {
|
||||||
ensureFeatureEnabled(t)
|
ensureFeatureEnabled(t)
|
||||||
target := Tester.CreateTarget(CTX, t)
|
target := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
@ -27,32 +27,44 @@ func Test_createTargetToCommand(t *testing.T) {
|
|||||||
args: args{nil},
|
args: args{nil},
|
||||||
want: &command.AddTarget{
|
want: &command.AddTarget{
|
||||||
Name: "",
|
Name: "",
|
||||||
URL: "",
|
Endpoint: "",
|
||||||
Timeout: 0,
|
Timeout: 0,
|
||||||
Async: false,
|
|
||||||
InterruptOnError: false,
|
InterruptOnError: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all fields (async webhook)",
|
name: "all fields (webhook)",
|
||||||
args: args{&action.CreateTargetRequest{
|
args: args{&action.CreateTargetRequest{
|
||||||
Name: "target 1",
|
Name: "target 1",
|
||||||
|
Endpoint: "https://example.com/hooks/1",
|
||||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
TargetType: &action.CreateTargetRequest_RestWebhook{
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
RestWebhook: &action.SetRESTWebhook{},
|
||||||
Url: "https://example.com/hooks/1",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
ExecutionType: &action.CreateTargetRequest_IsAsync{
|
|
||||||
IsAsync: true,
|
|
||||||
},
|
|
||||||
}},
|
}},
|
||||||
want: &command.AddTarget{
|
want: &command.AddTarget{
|
||||||
Name: "target 1",
|
Name: "target 1",
|
||||||
TargetType: domain.TargetTypeWebhook,
|
TargetType: domain.TargetTypeWebhook,
|
||||||
URL: "https://example.com/hooks/1",
|
Endpoint: "https://example.com/hooks/1",
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
InterruptOnError: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all fields (async)",
|
||||||
|
args: args{&action.CreateTargetRequest{
|
||||||
|
Name: "target 1",
|
||||||
|
Endpoint: "https://example.com/hooks/1",
|
||||||
|
TargetType: &action.CreateTargetRequest_RestAsync{
|
||||||
|
RestAsync: &action.SetRESTAsync{},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
}},
|
||||||
|
want: &command.AddTarget{
|
||||||
|
Name: "target 1",
|
||||||
|
TargetType: domain.TargetTypeAsync,
|
||||||
|
Endpoint: "https://example.com/hooks/1",
|
||||||
Timeout: 10 * time.Second,
|
Timeout: 10 * time.Second,
|
||||||
Async: true,
|
|
||||||
InterruptOnError: false,
|
InterruptOnError: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -60,22 +72,19 @@ func Test_createTargetToCommand(t *testing.T) {
|
|||||||
name: "all fields (interrupting response)",
|
name: "all fields (interrupting response)",
|
||||||
args: args{&action.CreateTargetRequest{
|
args: args{&action.CreateTargetRequest{
|
||||||
Name: "target 1",
|
Name: "target 1",
|
||||||
TargetType: &action.CreateTargetRequest_RestRequestResponse{
|
Endpoint: "https://example.com/hooks/1",
|
||||||
RestRequestResponse: &action.SetRESTRequestResponse{
|
TargetType: &action.CreateTargetRequest_RestCall{
|
||||||
Url: "https://example.com/hooks/1",
|
RestCall: &action.SetRESTCall{
|
||||||
|
InterruptOnError: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
ExecutionType: &action.CreateTargetRequest_InterruptOnError{
|
|
||||||
InterruptOnError: true,
|
|
||||||
},
|
|
||||||
}},
|
}},
|
||||||
want: &command.AddTarget{
|
want: &command.AddTarget{
|
||||||
Name: "target 1",
|
Name: "target 1",
|
||||||
TargetType: domain.TargetTypeRequestResponse,
|
TargetType: domain.TargetTypeCall,
|
||||||
URL: "https://example.com/hooks/1",
|
Endpoint: "https://example.com/hooks/1",
|
||||||
Timeout: 10 * time.Second,
|
Timeout: 10 * time.Second,
|
||||||
Async: false,
|
|
||||||
InterruptOnError: true,
|
InterruptOnError: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -108,14 +117,12 @@ func Test_updateTargetToCommand(t *testing.T) {
|
|||||||
Name: nil,
|
Name: nil,
|
||||||
TargetType: nil,
|
TargetType: nil,
|
||||||
Timeout: nil,
|
Timeout: nil,
|
||||||
ExecutionType: nil,
|
|
||||||
}},
|
}},
|
||||||
want: &command.ChangeTarget{
|
want: &command.ChangeTarget{
|
||||||
Name: nil,
|
Name: nil,
|
||||||
TargetType: nil,
|
TargetType: nil,
|
||||||
URL: nil,
|
Endpoint: nil,
|
||||||
Timeout: nil,
|
Timeout: nil,
|
||||||
Async: nil,
|
|
||||||
InterruptOnError: nil,
|
InterruptOnError: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -125,37 +132,70 @@ func Test_updateTargetToCommand(t *testing.T) {
|
|||||||
Name: gu.Ptr(""),
|
Name: gu.Ptr(""),
|
||||||
TargetType: nil,
|
TargetType: nil,
|
||||||
Timeout: durationpb.New(0),
|
Timeout: durationpb.New(0),
|
||||||
ExecutionType: nil,
|
|
||||||
}},
|
}},
|
||||||
want: &command.ChangeTarget{
|
want: &command.ChangeTarget{
|
||||||
Name: gu.Ptr(""),
|
Name: gu.Ptr(""),
|
||||||
TargetType: nil,
|
TargetType: nil,
|
||||||
URL: nil,
|
Endpoint: nil,
|
||||||
Timeout: gu.Ptr(0 * time.Second),
|
Timeout: gu.Ptr(0 * time.Second),
|
||||||
Async: nil,
|
|
||||||
InterruptOnError: nil,
|
InterruptOnError: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all fields (async webhook)",
|
name: "all fields (webhook)",
|
||||||
args: args{&action.UpdateTargetRequest{
|
args: args{&action.UpdateTargetRequest{
|
||||||
Name: gu.Ptr("target 1"),
|
Name: gu.Ptr("target 1"),
|
||||||
|
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||||
TargetType: &action.UpdateTargetRequest_RestWebhook{
|
TargetType: &action.UpdateTargetRequest_RestWebhook{
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
RestWebhook: &action.SetRESTWebhook{
|
||||||
Url: "https://example.com/hooks/1",
|
InterruptOnError: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
ExecutionType: &action.UpdateTargetRequest_IsAsync{
|
|
||||||
IsAsync: true,
|
|
||||||
},
|
|
||||||
}},
|
}},
|
||||||
want: &command.ChangeTarget{
|
want: &command.ChangeTarget{
|
||||||
Name: gu.Ptr("target 1"),
|
Name: gu.Ptr("target 1"),
|
||||||
TargetType: gu.Ptr(domain.TargetTypeWebhook),
|
TargetType: gu.Ptr(domain.TargetTypeWebhook),
|
||||||
URL: gu.Ptr("https://example.com/hooks/1"),
|
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||||
|
Timeout: gu.Ptr(10 * time.Second),
|
||||||
|
InterruptOnError: gu.Ptr(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all fields (webhook interrupt)",
|
||||||
|
args: args{&action.UpdateTargetRequest{
|
||||||
|
Name: gu.Ptr("target 1"),
|
||||||
|
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||||
|
TargetType: &action.UpdateTargetRequest_RestWebhook{
|
||||||
|
RestWebhook: &action.SetRESTWebhook{
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
}},
|
||||||
|
want: &command.ChangeTarget{
|
||||||
|
Name: gu.Ptr("target 1"),
|
||||||
|
TargetType: gu.Ptr(domain.TargetTypeWebhook),
|
||||||
|
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||||
|
Timeout: gu.Ptr(10 * time.Second),
|
||||||
|
InterruptOnError: gu.Ptr(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all fields (async)",
|
||||||
|
args: args{&action.UpdateTargetRequest{
|
||||||
|
Name: gu.Ptr("target 1"),
|
||||||
|
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||||
|
TargetType: &action.UpdateTargetRequest_RestAsync{
|
||||||
|
RestAsync: &action.SetRESTAsync{},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
}},
|
||||||
|
want: &command.ChangeTarget{
|
||||||
|
Name: gu.Ptr("target 1"),
|
||||||
|
TargetType: gu.Ptr(domain.TargetTypeAsync),
|
||||||
|
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||||
Timeout: gu.Ptr(10 * time.Second),
|
Timeout: gu.Ptr(10 * time.Second),
|
||||||
Async: gu.Ptr(true),
|
|
||||||
InterruptOnError: gu.Ptr(false),
|
InterruptOnError: gu.Ptr(false),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -163,22 +203,19 @@ func Test_updateTargetToCommand(t *testing.T) {
|
|||||||
name: "all fields (interrupting response)",
|
name: "all fields (interrupting response)",
|
||||||
args: args{&action.UpdateTargetRequest{
|
args: args{&action.UpdateTargetRequest{
|
||||||
Name: gu.Ptr("target 1"),
|
Name: gu.Ptr("target 1"),
|
||||||
TargetType: &action.UpdateTargetRequest_RestRequestResponse{
|
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||||
RestRequestResponse: &action.SetRESTRequestResponse{
|
TargetType: &action.UpdateTargetRequest_RestCall{
|
||||||
Url: "https://example.com/hooks/1",
|
RestCall: &action.SetRESTCall{
|
||||||
|
InterruptOnError: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
ExecutionType: &action.UpdateTargetRequest_InterruptOnError{
|
|
||||||
InterruptOnError: true,
|
|
||||||
},
|
|
||||||
}},
|
}},
|
||||||
want: &command.ChangeTarget{
|
want: &command.ChangeTarget{
|
||||||
Name: gu.Ptr("target 1"),
|
Name: gu.Ptr("target 1"),
|
||||||
TargetType: gu.Ptr(domain.TargetTypeRequestResponse),
|
TargetType: gu.Ptr(domain.TargetTypeCall),
|
||||||
URL: gu.Ptr("https://example.com/hooks/1"),
|
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||||
Timeout: gu.Ptr(10 * time.Second),
|
Timeout: gu.Ptr(10 * time.Second),
|
||||||
Async: gu.Ptr(false),
|
|
||||||
InterruptOnError: gu.Ptr(true),
|
InterruptOnError: gu.Ptr(true),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
179
internal/api/grpc/server/middleware/execution_interceptor.go
Normal file
179
internal/api/grpc/server/middleware/execution_interceptor.go
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/zitadel/logging"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/execution"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
|
exec_repo "github.com/zitadel/zitadel/internal/repository/execution"
|
||||||
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExecutionHandler(queries *query.Queries) grpc.UnaryServerInterceptor {
|
||||||
|
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||||
|
requestTargets, responseTargets := queryTargets(ctx, queries, info.FullMethod)
|
||||||
|
|
||||||
|
// call targets otherwise return req
|
||||||
|
handledReq, err := executeTargetsForRequest(ctx, requestTargets, info.FullMethod, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := handler(ctx, handledReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return executeTargetsForResponse(ctx, responseTargets, info.FullMethod, handledReq, response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeTargetsForRequest(ctx context.Context, targets []execution.Target, fullMethod string, req interface{}) (_ interface{}, err error) {
|
||||||
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
|
defer span.EndWithError(err)
|
||||||
|
|
||||||
|
// if no targets are found, return without any calls
|
||||||
|
if len(targets) == 0 {
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctxData := authz.GetCtxData(ctx)
|
||||||
|
info := &ContextInfoRequest{
|
||||||
|
FullMethod: fullMethod,
|
||||||
|
InstanceID: authz.GetInstance(ctx).InstanceID(),
|
||||||
|
ProjectID: ctxData.ProjectID,
|
||||||
|
OrgID: ctxData.OrgID,
|
||||||
|
UserID: ctxData.UserID,
|
||||||
|
Request: req,
|
||||||
|
}
|
||||||
|
|
||||||
|
return execution.CallTargets(ctx, targets, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeTargetsForResponse(ctx context.Context, targets []execution.Target, fullMethod string, req, resp interface{}) (_ interface{}, err error) {
|
||||||
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
|
defer span.EndWithError(err)
|
||||||
|
|
||||||
|
// if no targets are found, return without any calls
|
||||||
|
if len(targets) == 0 {
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctxData := authz.GetCtxData(ctx)
|
||||||
|
info := &ContextInfoResponse{
|
||||||
|
FullMethod: fullMethod,
|
||||||
|
InstanceID: authz.GetInstance(ctx).InstanceID(),
|
||||||
|
ProjectID: ctxData.ProjectID,
|
||||||
|
OrgID: ctxData.OrgID,
|
||||||
|
UserID: ctxData.UserID,
|
||||||
|
Request: req,
|
||||||
|
Response: resp,
|
||||||
|
}
|
||||||
|
|
||||||
|
return execution.CallTargets(ctx, targets, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExecutionQueries interface {
|
||||||
|
TargetsByExecutionIDs(ctx context.Context, ids1, ids2 []string) (execution []*query.ExecutionTarget, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryTargets(
|
||||||
|
ctx context.Context,
|
||||||
|
queries ExecutionQueries,
|
||||||
|
fullMethod string,
|
||||||
|
) ([]execution.Target, []execution.Target) {
|
||||||
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
targets, err := queries.TargetsByExecutionIDs(ctx,
|
||||||
|
idsForFullMethod(fullMethod, domain.ExecutionTypeRequest),
|
||||||
|
idsForFullMethod(fullMethod, domain.ExecutionTypeResponse),
|
||||||
|
)
|
||||||
|
requestTargets := make([]execution.Target, 0, len(targets))
|
||||||
|
responseTargets := make([]execution.Target, 0, len(targets))
|
||||||
|
if err != nil {
|
||||||
|
logging.WithFields("fullMethod", fullMethod).WithError(err).Info("unable to query targets")
|
||||||
|
return requestTargets, responseTargets
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, target := range targets {
|
||||||
|
if strings.HasPrefix(target.GetExecutionID(), exec_repo.IDAll(domain.ExecutionTypeRequest)) {
|
||||||
|
requestTargets = append(requestTargets, target)
|
||||||
|
} else if strings.HasPrefix(target.GetExecutionID(), exec_repo.IDAll(domain.ExecutionTypeResponse)) {
|
||||||
|
responseTargets = append(responseTargets, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestTargets, responseTargets
|
||||||
|
}
|
||||||
|
|
||||||
|
func idsForFullMethod(fullMethod string, executionType domain.ExecutionType) []string {
|
||||||
|
return []string{exec_repo.ID(executionType, fullMethod), exec_repo.ID(executionType, serviceFromFullMethod(fullMethod)), exec_repo.IDAll(executionType)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serviceFromFullMethod(s string) string {
|
||||||
|
parts := strings.Split(s, "/")
|
||||||
|
return parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ execution.ContextInfo = &ContextInfoRequest{}
|
||||||
|
|
||||||
|
type ContextInfoRequest struct {
|
||||||
|
FullMethod string `json:"fullMethod,omitempty"`
|
||||||
|
InstanceID string `json:"instanceID,omitempty"`
|
||||||
|
OrgID string `json:"orgID,omitempty"`
|
||||||
|
ProjectID string `json:"projectID,omitempty"`
|
||||||
|
UserID string `json:"userID,omitempty"`
|
||||||
|
Request interface{} `json:"request,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContextInfoRequest) GetHTTPRequestBody() []byte {
|
||||||
|
data, err := json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContextInfoRequest) SetHTTPResponseBody(resp []byte) error {
|
||||||
|
return json.Unmarshal(resp, c.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContextInfoRequest) GetContent() interface{} {
|
||||||
|
return c.Request
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ execution.ContextInfo = &ContextInfoResponse{}
|
||||||
|
|
||||||
|
type ContextInfoResponse struct {
|
||||||
|
FullMethod string `json:"fullMethod,omitempty"`
|
||||||
|
InstanceID string `json:"instanceID,omitempty"`
|
||||||
|
OrgID string `json:"orgID,omitempty"`
|
||||||
|
ProjectID string `json:"projectID,omitempty"`
|
||||||
|
UserID string `json:"userID,omitempty"`
|
||||||
|
Request interface{} `json:"request,omitempty"`
|
||||||
|
Response interface{} `json:"response,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContextInfoResponse) GetHTTPRequestBody() []byte {
|
||||||
|
data, err := json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContextInfoResponse) SetHTTPResponseBody(resp []byte) error {
|
||||||
|
return json.Unmarshal(resp, c.Response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContextInfoResponse) GetContent() interface{} {
|
||||||
|
return c.Response
|
||||||
|
}
|
@ -0,0 +1,778 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/execution"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ execution.Target = &mockExecutionTarget{}
|
||||||
|
|
||||||
|
type mockExecutionTarget struct {
|
||||||
|
InstanceID string
|
||||||
|
ExecutionID string
|
||||||
|
TargetID string
|
||||||
|
TargetType domain.TargetType
|
||||||
|
Endpoint string
|
||||||
|
Timeout time.Duration
|
||||||
|
InterruptOnError bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *mockExecutionTarget) SetEndpoint(endpoint string) {
|
||||||
|
e.Endpoint = endpoint
|
||||||
|
}
|
||||||
|
func (e *mockExecutionTarget) IsInterruptOnError() bool {
|
||||||
|
return e.InterruptOnError
|
||||||
|
}
|
||||||
|
func (e *mockExecutionTarget) GetEndpoint() string {
|
||||||
|
return e.Endpoint
|
||||||
|
}
|
||||||
|
func (e *mockExecutionTarget) GetTargetType() domain.TargetType {
|
||||||
|
return e.TargetType
|
||||||
|
}
|
||||||
|
func (e *mockExecutionTarget) GetTimeout() time.Duration {
|
||||||
|
return e.Timeout
|
||||||
|
}
|
||||||
|
func (e *mockExecutionTarget) GetTargetID() string {
|
||||||
|
return e.TargetID
|
||||||
|
}
|
||||||
|
func (e *mockExecutionTarget) GetExecutionID() string {
|
||||||
|
return e.ExecutionID
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockContentRequest struct {
|
||||||
|
Content string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockContentRequest(content string) *mockContentRequest {
|
||||||
|
return &mockContentRequest{
|
||||||
|
Content: content,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockContextInfoRequest(fullMethod, request string) *ContextInfoRequest {
|
||||||
|
return &ContextInfoRequest{
|
||||||
|
FullMethod: fullMethod,
|
||||||
|
Request: newMockContentRequest(request),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockContextInfoResponse(fullMethod, request, response string) *ContextInfoResponse {
|
||||||
|
return &ContextInfoResponse{
|
||||||
|
FullMethod: fullMethod,
|
||||||
|
Request: newMockContentRequest(request),
|
||||||
|
Response: newMockContentRequest(response),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) {
|
||||||
|
type target struct {
|
||||||
|
reqBody execution.ContextInfo
|
||||||
|
sleep time.Duration
|
||||||
|
statusCode int
|
||||||
|
respBody interface{}
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
|
executionTargets []execution.Target
|
||||||
|
targets []target
|
||||||
|
fullMethod string
|
||||||
|
req interface{}
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
want interface{}
|
||||||
|
wantErr bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"target, executionTargets nil",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
fullMethod: "/service/method",
|
||||||
|
executionTargets: nil,
|
||||||
|
req: newMockContentRequest("request"),
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
want: newMockContentRequest("request"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target, executionTargets empty",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
fullMethod: "/service/method",
|
||||||
|
executionTargets: []execution.Target{},
|
||||||
|
req: newMockContentRequest("request"),
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
want: newMockContentRequest("request"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target, not reachable",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
fullMethod: "/service/method",
|
||||||
|
executionTargets: []execution.Target{
|
||||||
|
&mockExecutionTarget{
|
||||||
|
InstanceID: "instance",
|
||||||
|
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||||
|
TargetID: "target",
|
||||||
|
TargetType: domain.TargetTypeCall,
|
||||||
|
Timeout: time.Minute,
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
targets: []target{},
|
||||||
|
req: newMockContentRequest("content"),
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target, error without interrupt",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
fullMethod: "/service/method",
|
||||||
|
executionTargets: []execution.Target{
|
||||||
|
&mockExecutionTarget{
|
||||||
|
InstanceID: "instance",
|
||||||
|
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||||
|
TargetID: "target",
|
||||||
|
TargetType: domain.TargetTypeCall,
|
||||||
|
Timeout: time.Minute,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
targets: []target{
|
||||||
|
{
|
||||||
|
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||||
|
respBody: newMockContentRequest("content1"),
|
||||||
|
sleep: 0,
|
||||||
|
statusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
req: newMockContentRequest("content"),
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
want: newMockContentRequest("content"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target, interruptOnError",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
fullMethod: "/service/method",
|
||||||
|
executionTargets: []execution.Target{
|
||||||
|
&mockExecutionTarget{
|
||||||
|
InstanceID: "instance",
|
||||||
|
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||||
|
TargetID: "target",
|
||||||
|
TargetType: domain.TargetTypeCall,
|
||||||
|
Timeout: time.Minute,
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
targets: []target{
|
||||||
|
{
|
||||||
|
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||||
|
respBody: newMockContentRequest("content1"),
|
||||||
|
sleep: 0,
|
||||||
|
statusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
req: newMockContentRequest("content"),
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target, timeout",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
fullMethod: "/service/method",
|
||||||
|
executionTargets: []execution.Target{
|
||||||
|
&mockExecutionTarget{
|
||||||
|
InstanceID: "instance",
|
||||||
|
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||||
|
TargetID: "target",
|
||||||
|
TargetType: domain.TargetTypeCall,
|
||||||
|
Timeout: time.Second,
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
targets: []target{
|
||||||
|
{
|
||||||
|
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||||
|
respBody: newMockContentRequest("content1"),
|
||||||
|
sleep: 5 * time.Second,
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
req: newMockContentRequest("content"),
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target, wrong request",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
fullMethod: "/service/method",
|
||||||
|
executionTargets: []execution.Target{
|
||||||
|
&mockExecutionTarget{
|
||||||
|
InstanceID: "instance",
|
||||||
|
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||||
|
TargetID: "target",
|
||||||
|
TargetType: domain.TargetTypeCall,
|
||||||
|
Timeout: time.Second,
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
targets: []target{
|
||||||
|
{reqBody: newMockContextInfoRequest("/service/method", "wrong")},
|
||||||
|
},
|
||||||
|
req: newMockContentRequest("content"),
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target, ok",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
fullMethod: "/service/method",
|
||||||
|
executionTargets: []execution.Target{
|
||||||
|
&mockExecutionTarget{
|
||||||
|
InstanceID: "instance",
|
||||||
|
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||||
|
TargetID: "target",
|
||||||
|
TargetType: domain.TargetTypeCall,
|
||||||
|
Timeout: time.Minute,
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
targets: []target{
|
||||||
|
{
|
||||||
|
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||||
|
respBody: newMockContentRequest("content1"),
|
||||||
|
sleep: 0,
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
req: newMockContentRequest("content"),
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
want: newMockContentRequest("content1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target async, timeout",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
fullMethod: "/service/method",
|
||||||
|
executionTargets: []execution.Target{
|
||||||
|
&mockExecutionTarget{
|
||||||
|
InstanceID: "instance",
|
||||||
|
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||||
|
TargetID: "target",
|
||||||
|
TargetType: domain.TargetTypeAsync,
|
||||||
|
Timeout: time.Second,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
targets: []target{
|
||||||
|
{
|
||||||
|
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||||
|
respBody: newMockContentRequest("content1"),
|
||||||
|
sleep: 5 * time.Second,
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
req: newMockContentRequest("content"),
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
want: newMockContentRequest("content"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target async, ok",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
fullMethod: "/service/method",
|
||||||
|
executionTargets: []execution.Target{
|
||||||
|
&mockExecutionTarget{
|
||||||
|
InstanceID: "instance",
|
||||||
|
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||||
|
TargetID: "target",
|
||||||
|
TargetType: domain.TargetTypeAsync,
|
||||||
|
Timeout: time.Minute,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
targets: []target{
|
||||||
|
{
|
||||||
|
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||||
|
respBody: newMockContentRequest("content1"),
|
||||||
|
sleep: 0,
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
req: newMockContentRequest("content"),
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
want: newMockContentRequest("content"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"webhook, error",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
fullMethod: "/service/method",
|
||||||
|
executionTargets: []execution.Target{
|
||||||
|
&mockExecutionTarget{
|
||||||
|
InstanceID: "instance",
|
||||||
|
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||||
|
TargetID: "target",
|
||||||
|
TargetType: domain.TargetTypeWebhook,
|
||||||
|
Timeout: time.Minute,
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
targets: []target{
|
||||||
|
{
|
||||||
|
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||||
|
sleep: 0,
|
||||||
|
statusCode: http.StatusInternalServerError,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
req: newMockContentRequest("content"),
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"webhook, timeout",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
fullMethod: "/service/method",
|
||||||
|
executionTargets: []execution.Target{
|
||||||
|
&mockExecutionTarget{
|
||||||
|
InstanceID: "instance",
|
||||||
|
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||||
|
TargetID: "target",
|
||||||
|
TargetType: domain.TargetTypeWebhook,
|
||||||
|
Timeout: time.Second,
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
targets: []target{
|
||||||
|
{
|
||||||
|
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||||
|
respBody: newMockContentRequest("content1"),
|
||||||
|
sleep: 5 * time.Second,
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
req: newMockContentRequest("content"),
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"webhook, ok",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
fullMethod: "/service/method",
|
||||||
|
executionTargets: []execution.Target{
|
||||||
|
&mockExecutionTarget{
|
||||||
|
InstanceID: "instance",
|
||||||
|
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||||
|
TargetID: "target",
|
||||||
|
TargetType: domain.TargetTypeWebhook,
|
||||||
|
Timeout: time.Minute,
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
targets: []target{
|
||||||
|
{
|
||||||
|
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||||
|
respBody: newMockContentRequest("content1"),
|
||||||
|
sleep: 0,
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
req: newMockContentRequest("content"),
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
want: newMockContentRequest("content"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with includes, interruptOnError",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
fullMethod: "/service/method",
|
||||||
|
executionTargets: []execution.Target{
|
||||||
|
&mockExecutionTarget{
|
||||||
|
InstanceID: "instance",
|
||||||
|
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||||
|
TargetID: "target1",
|
||||||
|
TargetType: domain.TargetTypeCall,
|
||||||
|
Timeout: time.Minute,
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
&mockExecutionTarget{
|
||||||
|
InstanceID: "instance",
|
||||||
|
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||||
|
TargetID: "target2",
|
||||||
|
TargetType: domain.TargetTypeCall,
|
||||||
|
Timeout: time.Minute,
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
&mockExecutionTarget{
|
||||||
|
InstanceID: "instance",
|
||||||
|
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||||
|
TargetID: "target3",
|
||||||
|
TargetType: domain.TargetTypeCall,
|
||||||
|
Timeout: time.Minute,
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
targets: []target{
|
||||||
|
{
|
||||||
|
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||||
|
respBody: newMockContentRequest("content1"),
|
||||||
|
sleep: 0,
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reqBody: newMockContextInfoRequest("/service/method", "content1"),
|
||||||
|
respBody: newMockContentRequest("content2"),
|
||||||
|
sleep: 0,
|
||||||
|
statusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reqBody: newMockContextInfoRequest("/service/method", "content2"),
|
||||||
|
respBody: newMockContentRequest("content3"),
|
||||||
|
sleep: 0,
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
req: newMockContentRequest("content"),
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with includes, timeout",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
fullMethod: "/service/method",
|
||||||
|
executionTargets: []execution.Target{
|
||||||
|
&mockExecutionTarget{
|
||||||
|
InstanceID: "instance",
|
||||||
|
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||||
|
TargetID: "target1",
|
||||||
|
TargetType: domain.TargetTypeCall,
|
||||||
|
Timeout: time.Minute,
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
&mockExecutionTarget{
|
||||||
|
InstanceID: "instance",
|
||||||
|
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||||
|
TargetID: "target2",
|
||||||
|
TargetType: domain.TargetTypeCall,
|
||||||
|
Timeout: time.Second,
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
&mockExecutionTarget{
|
||||||
|
InstanceID: "instance",
|
||||||
|
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||||
|
TargetID: "target3",
|
||||||
|
TargetType: domain.TargetTypeCall,
|
||||||
|
Timeout: time.Second,
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
targets: []target{
|
||||||
|
{
|
||||||
|
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||||
|
respBody: newMockContentRequest("content1"),
|
||||||
|
sleep: 0,
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reqBody: newMockContextInfoRequest("/service/method", "content1"),
|
||||||
|
respBody: newMockContentRequest("content2"),
|
||||||
|
sleep: 5 * time.Second,
|
||||||
|
statusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reqBody: newMockContextInfoRequest("/service/method", "content2"),
|
||||||
|
respBody: newMockContentRequest("content3"),
|
||||||
|
sleep: 5 * time.Second,
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
req: newMockContentRequest("content"),
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
closeFuncs := make([]func(), len(tt.args.targets))
|
||||||
|
for i, target := range tt.args.targets {
|
||||||
|
url, closeF := testServerCall(
|
||||||
|
target.reqBody,
|
||||||
|
target.sleep,
|
||||||
|
target.statusCode,
|
||||||
|
target.respBody,
|
||||||
|
)
|
||||||
|
|
||||||
|
et := tt.args.executionTargets[i].(*mockExecutionTarget)
|
||||||
|
et.SetEndpoint(url)
|
||||||
|
closeFuncs[i] = closeF
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := executeTargetsForRequest(
|
||||||
|
tt.args.ctx,
|
||||||
|
tt.args.executionTargets,
|
||||||
|
tt.args.fullMethod,
|
||||||
|
tt.args.req,
|
||||||
|
)
|
||||||
|
|
||||||
|
if tt.res.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.res.want, resp)
|
||||||
|
|
||||||
|
for _, closeF := range closeFuncs {
|
||||||
|
closeF()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testServerCall(
|
||||||
|
reqBody interface{},
|
||||||
|
sleep time.Duration,
|
||||||
|
statusCode int,
|
||||||
|
respBody interface{},
|
||||||
|
) (string, func()) {
|
||||||
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data, err := json.Marshal(reqBody)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sentBody, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(data, sentBody) {
|
||||||
|
http.Error(w, "error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode != http.StatusOK {
|
||||||
|
http.Error(w, "error", statusCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(sleep)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
resp, err := json.Marshal(respBody)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := io.WriteString(w, string(resp)); err != nil {
|
||||||
|
http.Error(w, "error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(handler))
|
||||||
|
|
||||||
|
return server.URL, server.Close
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_executeTargetsForGRPCFullMethod_response(t *testing.T) {
|
||||||
|
type target struct {
|
||||||
|
reqBody execution.ContextInfo
|
||||||
|
sleep time.Duration
|
||||||
|
statusCode int
|
||||||
|
respBody interface{}
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
|
executionTargets []execution.Target
|
||||||
|
targets []target
|
||||||
|
fullMethod string
|
||||||
|
req interface{}
|
||||||
|
resp interface{}
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
want interface{}
|
||||||
|
wantErr bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"target, executionTargets nil",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
fullMethod: "/service/method",
|
||||||
|
executionTargets: nil,
|
||||||
|
req: newMockContentRequest("request"),
|
||||||
|
resp: newMockContentRequest("response"),
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
want: newMockContentRequest("response"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target, executionTargets empty",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
fullMethod: "/service/method",
|
||||||
|
executionTargets: []execution.Target{},
|
||||||
|
req: newMockContentRequest("request"),
|
||||||
|
resp: newMockContentRequest("response"),
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
want: newMockContentRequest("response"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target, empty response",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
fullMethod: "/service/method",
|
||||||
|
executionTargets: []execution.Target{
|
||||||
|
&mockExecutionTarget{
|
||||||
|
InstanceID: "instance",
|
||||||
|
ExecutionID: "request./zitadel.session.v2beta.SessionService/SetSession",
|
||||||
|
TargetID: "target",
|
||||||
|
TargetType: domain.TargetTypeCall,
|
||||||
|
Timeout: time.Minute,
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
targets: []target{
|
||||||
|
{
|
||||||
|
reqBody: newMockContextInfoRequest("/service/method", "content"),
|
||||||
|
respBody: newMockContentRequest(""),
|
||||||
|
sleep: 0,
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
req: []byte{},
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target, ok",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
fullMethod: "/service/method",
|
||||||
|
executionTargets: []execution.Target{
|
||||||
|
&mockExecutionTarget{
|
||||||
|
InstanceID: "instance",
|
||||||
|
ExecutionID: "response./zitadel.session.v2beta.SessionService/SetSession",
|
||||||
|
TargetID: "target",
|
||||||
|
TargetType: domain.TargetTypeCall,
|
||||||
|
Timeout: time.Minute,
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
targets: []target{
|
||||||
|
{
|
||||||
|
reqBody: newMockContextInfoResponse("/service/method", "request", "response"),
|
||||||
|
respBody: newMockContentRequest("response1"),
|
||||||
|
sleep: 0,
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
req: newMockContentRequest("request"),
|
||||||
|
resp: newMockContentRequest("response"),
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
want: newMockContentRequest("response1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
closeFuncs := make([]func(), len(tt.args.targets))
|
||||||
|
for i, target := range tt.args.targets {
|
||||||
|
url, closeF := testServerCall(
|
||||||
|
target.reqBody,
|
||||||
|
target.sleep,
|
||||||
|
target.statusCode,
|
||||||
|
target.respBody,
|
||||||
|
)
|
||||||
|
|
||||||
|
et := tt.args.executionTargets[i].(*mockExecutionTarget)
|
||||||
|
et.SetEndpoint(url)
|
||||||
|
closeFuncs[i] = closeF
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := executeTargetsForResponse(
|
||||||
|
tt.args.ctx,
|
||||||
|
tt.args.executionTargets,
|
||||||
|
tt.args.fullMethod,
|
||||||
|
tt.args.req,
|
||||||
|
tt.args.resp,
|
||||||
|
)
|
||||||
|
|
||||||
|
if tt.res.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.res.want, resp)
|
||||||
|
|
||||||
|
for _, closeF := range closeFuncs {
|
||||||
|
closeF()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -58,6 +58,7 @@ func CreateServer(
|
|||||||
middleware.AuthorizationInterceptor(verifier, authConfig),
|
middleware.AuthorizationInterceptor(verifier, authConfig),
|
||||||
middleware.TranslationHandler(),
|
middleware.TranslationHandler(),
|
||||||
middleware.QuotaExhaustedInterceptor(accessSvc, system_pb.SystemService_ServiceDesc.ServiceName),
|
middleware.QuotaExhaustedInterceptor(accessSvc, system_pb.SystemService_ServiceDesc.ServiceName),
|
||||||
|
middleware.ExecutionHandler(queries),
|
||||||
middleware.ValidationHandler(),
|
middleware.ValidationHandler(),
|
||||||
middleware.ServiceHandler(),
|
middleware.ServiceHandler(),
|
||||||
middleware.ActivityInterceptor(),
|
middleware.ActivityInterceptor(),
|
||||||
|
@ -2,6 +2,7 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||||
@ -9,6 +10,10 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/zerrors"
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EventGroupSuffix = ".*"
|
||||||
|
)
|
||||||
|
|
||||||
type ExecutionAPICondition struct {
|
type ExecutionAPICondition struct {
|
||||||
Method string
|
Method string
|
||||||
Service string
|
Service string
|
||||||
@ -134,7 +139,11 @@ func (e *ExecutionEventCondition) ID() string {
|
|||||||
return execution.ID(domain.ExecutionTypeEvent, e.Event)
|
return execution.ID(domain.ExecutionTypeEvent, e.Event)
|
||||||
}
|
}
|
||||||
if e.Group != "" {
|
if e.Group != "" {
|
||||||
return execution.ID(domain.ExecutionTypeEvent, e.Group)
|
group := e.Group
|
||||||
|
if !strings.HasSuffix(e.Group, EventGroupSuffix) {
|
||||||
|
group += EventGroupSuffix
|
||||||
|
}
|
||||||
|
return execution.ID(domain.ExecutionTypeEvent, group)
|
||||||
}
|
}
|
||||||
if e.All {
|
if e.All {
|
||||||
return execution.IDAll(domain.ExecutionTypeEvent)
|
return execution.IDAll(domain.ExecutionTypeEvent)
|
||||||
@ -168,25 +177,43 @@ func (c *Commands) SetExecutionEvent(ctx context.Context, cond *ExecutionEventCo
|
|||||||
type SetExecution struct {
|
type SetExecution struct {
|
||||||
models.ObjectRoot
|
models.ObjectRoot
|
||||||
|
|
||||||
Targets []string
|
Targets []*execution.Target
|
||||||
Includes []string
|
}
|
||||||
|
|
||||||
|
func (t SetExecution) GetIncludes() []string {
|
||||||
|
includes := make([]string, 0)
|
||||||
|
for i := range t.Targets {
|
||||||
|
if t.Targets[i].Type == domain.ExecutionTargetTypeInclude {
|
||||||
|
includes = append(includes, t.Targets[i].Target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return includes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t SetExecution) GetTargets() []string {
|
||||||
|
targets := make([]string, 0)
|
||||||
|
for i := range t.Targets {
|
||||||
|
if t.Targets[i].Type == domain.ExecutionTargetTypeTarget {
|
||||||
|
targets = append(targets, t.Targets[i].Target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return targets
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *SetExecution) IsValid() error {
|
func (e *SetExecution) IsValid() error {
|
||||||
if len(e.Targets) == 0 && len(e.Includes) == 0 {
|
if len(e.Targets) == 0 {
|
||||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-56bteot2uj", "Errors.Execution.NoTargets")
|
return zerrors.ThrowInvalidArgument(nil, "COMMAND-56bteot2uj", "Errors.Execution.NoTargets")
|
||||||
}
|
}
|
||||||
if len(e.Targets) > 0 && len(e.Includes) > 0 {
|
|
||||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-5zleae34r1", "Errors.Execution.Invalid")
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *SetExecution) Existing(c *Commands, ctx context.Context, resourceOwner string) error {
|
func (e *SetExecution) Existing(c *Commands, ctx context.Context, resourceOwner string) error {
|
||||||
if len(e.Targets) > 0 && !c.existsTargetsByIDs(ctx, e.Targets, resourceOwner) {
|
targets := e.GetTargets()
|
||||||
|
if len(targets) > 0 && !c.existsTargetsByIDs(ctx, targets, resourceOwner) {
|
||||||
return zerrors.ThrowNotFound(nil, "COMMAND-17e8fq1ggk", "Errors.Target.NotFound")
|
return zerrors.ThrowNotFound(nil, "COMMAND-17e8fq1ggk", "Errors.Target.NotFound")
|
||||||
}
|
}
|
||||||
if len(e.Includes) > 0 && !c.existsExecutionsByIDs(ctx, e.Includes, resourceOwner) {
|
includes := e.GetIncludes()
|
||||||
|
if len(includes) > 0 && !c.existsExecutionsByIDs(ctx, includes, resourceOwner) {
|
||||||
return zerrors.ThrowNotFound(nil, "COMMAND-slgj0l4cdz", "Errors.Execution.IncludeNotFound")
|
return zerrors.ThrowNotFound(nil, "COMMAND-slgj0l4cdz", "Errors.Execution.IncludeNotFound")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -206,11 +233,10 @@ func (c *Commands) setExecution(ctx context.Context, set *SetExecution, resource
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.pushAppendAndReduce(ctx, wm, execution.NewSetEvent(
|
if err := c.pushAppendAndReduce(ctx, wm, execution.NewSetEventV2(
|
||||||
ctx,
|
ctx,
|
||||||
ExecutionAggregateFromWriteModel(&wm.WriteModel),
|
ExecutionAggregateFromWriteModel(&wm.WriteModel),
|
||||||
set.Targets,
|
set.Targets,
|
||||||
set.Includes,
|
|
||||||
)); err != nil {
|
)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -12,10 +12,11 @@ type ExecutionWriteModel struct {
|
|||||||
|
|
||||||
Targets []string
|
Targets []string
|
||||||
Includes []string
|
Includes []string
|
||||||
|
ExecutionTargets []*execution.Target
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ExecutionWriteModel) Exists() bool {
|
func (e *ExecutionWriteModel) Exists() bool {
|
||||||
return len(e.Targets) > 0 || len(e.Includes) > 0
|
return len(e.ExecutionTargets) > 0 || len(e.Includes) > 0 || len(e.Targets) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewExecutionWriteModel(id string, resourceOwner string) *ExecutionWriteModel {
|
func NewExecutionWriteModel(id string, resourceOwner string) *ExecutionWriteModel {
|
||||||
@ -34,9 +35,12 @@ func (wm *ExecutionWriteModel) Reduce() error {
|
|||||||
case *execution.SetEvent:
|
case *execution.SetEvent:
|
||||||
wm.Targets = e.Targets
|
wm.Targets = e.Targets
|
||||||
wm.Includes = e.Includes
|
wm.Includes = e.Includes
|
||||||
|
case *execution.SetEventV2:
|
||||||
|
wm.ExecutionTargets = e.Targets
|
||||||
case *execution.RemovedEvent:
|
case *execution.RemovedEvent:
|
||||||
wm.Targets = nil
|
wm.Targets = nil
|
||||||
wm.Includes = nil
|
wm.Includes = nil
|
||||||
|
wm.ExecutionTargets = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return wm.WriteModel.Reduce()
|
return wm.WriteModel.Reduce()
|
||||||
@ -49,6 +53,7 @@ func (wm *ExecutionWriteModel) Query() *eventstore.SearchQueryBuilder {
|
|||||||
AggregateTypes(execution.AggregateType).
|
AggregateTypes(execution.AggregateType).
|
||||||
AggregateIDs(wm.AggregateID).
|
AggregateIDs(wm.AggregateID).
|
||||||
EventTypes(execution.SetEventType,
|
EventTypes(execution.SetEventType,
|
||||||
|
execution.SetEventV2Type,
|
||||||
execution.RemovedEventType).
|
execution.RemovedEventType).
|
||||||
Builder()
|
Builder()
|
||||||
}
|
}
|
||||||
@ -91,6 +96,10 @@ func (wm *ExecutionsExistWriteModel) Reduce() error {
|
|||||||
if !slices.Contains(wm.existingIDs, e.Aggregate().ID) {
|
if !slices.Contains(wm.existingIDs, e.Aggregate().ID) {
|
||||||
wm.existingIDs = append(wm.existingIDs, e.Aggregate().ID)
|
wm.existingIDs = append(wm.existingIDs, e.Aggregate().ID)
|
||||||
}
|
}
|
||||||
|
case *execution.SetEventV2:
|
||||||
|
if !slices.Contains(wm.existingIDs, e.Aggregate().ID) {
|
||||||
|
wm.existingIDs = append(wm.existingIDs, e.Aggregate().ID)
|
||||||
|
}
|
||||||
case *execution.RemovedEvent:
|
case *execution.RemovedEvent:
|
||||||
i := slices.Index(wm.existingIDs, e.Aggregate().ID)
|
i := slices.Index(wm.existingIDs, e.Aggregate().ID)
|
||||||
if i >= 0 {
|
if i >= 0 {
|
||||||
@ -108,6 +117,7 @@ func (wm *ExecutionsExistWriteModel) Query() *eventstore.SearchQueryBuilder {
|
|||||||
AggregateTypes(execution.AggregateType).
|
AggregateTypes(execution.AggregateType).
|
||||||
AggregateIDs(wm.ids...).
|
AggregateIDs(wm.ids...).
|
||||||
EventTypes(execution.SetEventType,
|
EventTypes(execution.SetEventType,
|
||||||
|
execution.SetEventV2Type,
|
||||||
execution.RemovedEventType).
|
execution.RemovedEventType).
|
||||||
Builder()
|
Builder()
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
"github.com/zitadel/zitadel/internal/repository/execution"
|
"github.com/zitadel/zitadel/internal/repository/execution"
|
||||||
)
|
)
|
||||||
@ -32,10 +33,12 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
|||||||
eventstore: expectEventstore(
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution", "org1"),
|
execution.NewAggregate("execution", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -53,10 +56,12 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
|||||||
eventstore: expectEventstore(
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution", "org1"),
|
execution.NewAggregate("execution", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
@ -65,10 +70,12 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution", "org1"),
|
execution.NewAggregate("execution", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -91,10 +98,12 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution", "org1"),
|
execution.NewAggregate("execution", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -112,10 +121,12 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
|||||||
eventstore: expectEventstore(
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution", "org1"),
|
execution.NewAggregate("execution", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
@ -138,24 +149,30 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
|||||||
eventstore: expectEventstore(
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution1", "org1"),
|
execution.NewAggregate("execution1", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution2", "org1"),
|
execution.NewAggregate("execution2", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution3", "org1"),
|
execution.NewAggregate("execution3", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -174,17 +191,21 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
|||||||
eventstore: expectEventstore(
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution1", "org1"),
|
execution.NewAggregate("execution1", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution2", "org1"),
|
execution.NewAggregate("execution2", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
@ -193,10 +214,12 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution3", "org1"),
|
execution.NewAggregate("execution3", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -214,17 +237,21 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
|||||||
eventstore: expectEventstore(
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution1", "org1"),
|
execution.NewAggregate("execution1", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution2", "org1"),
|
execution.NewAggregate("execution2", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
@ -233,10 +260,12 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution3", "org1"),
|
execution.NewAggregate("execution3", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -254,24 +283,30 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
|||||||
eventstore: expectEventstore(
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution1", "org1"),
|
execution.NewAggregate("execution1", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution2", "org1"),
|
execution.NewAggregate("execution2", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution3", "org1"),
|
execution.NewAggregate("execution3", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
@ -299,24 +334,30 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution1", "org1"),
|
execution.NewAggregate("execution1", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution2", "org1"),
|
execution.NewAggregate("execution2", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution3", "org1"),
|
execution.NewAggregate("execution3", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -334,10 +375,12 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
|||||||
eventstore: expectEventstore(
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution1", "org1"),
|
execution.NewAggregate("execution1", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
@ -346,17 +389,21 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution2", "org1"),
|
execution.NewAggregate("execution2", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution3", "org1"),
|
execution.NewAggregate("execution3", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
@ -385,10 +432,12 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
|||||||
eventstore: expectEventstore(
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution1", "org1"),
|
execution.NewAggregate("execution1", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
@ -397,17 +446,21 @@ func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution2", "org1"),
|
execution.NewAggregate("execution2", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
execution.NewSetEvent(context.Background(),
|
execution.NewSetEventV2(context.Background(),
|
||||||
execution.NewAggregate("execution3", "org1"),
|
execution.NewAggregate("execution3", "org1"),
|
||||||
[]string{"target"},
|
[]*execution.Target{
|
||||||
[]string{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -16,9 +16,8 @@ type AddTarget struct {
|
|||||||
|
|
||||||
Name string
|
Name string
|
||||||
TargetType domain.TargetType
|
TargetType domain.TargetType
|
||||||
URL string
|
Endpoint string
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
Async bool
|
|
||||||
InterruptOnError bool
|
InterruptOnError bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,9 +28,9 @@ func (a *AddTarget) IsValid() error {
|
|||||||
if a.Timeout == 0 {
|
if a.Timeout == 0 {
|
||||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-39f35d8uri", "Errors.Target.NoTimeout")
|
return zerrors.ThrowInvalidArgument(nil, "COMMAND-39f35d8uri", "Errors.Target.NoTimeout")
|
||||||
}
|
}
|
||||||
_, err := url.Parse(a.URL)
|
_, err := url.Parse(a.Endpoint)
|
||||||
if err != nil || a.URL == "" {
|
if err != nil || a.Endpoint == "" {
|
||||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-1r2k6qo6wg", "Errors.Target.InvalidURL")
|
return zerrors.ThrowInvalidArgument(err, "COMMAND-1r2k6qo6wg", "Errors.Target.InvalidURL")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -65,9 +64,8 @@ func (c *Commands) AddTarget(ctx context.Context, add *AddTarget, resourceOwner
|
|||||||
TargetAggregateFromWriteModel(&wm.WriteModel),
|
TargetAggregateFromWriteModel(&wm.WriteModel),
|
||||||
add.Name,
|
add.Name,
|
||||||
add.TargetType,
|
add.TargetType,
|
||||||
add.URL,
|
add.Endpoint,
|
||||||
add.Timeout,
|
add.Timeout,
|
||||||
add.Async,
|
|
||||||
add.InterruptOnError,
|
add.InterruptOnError,
|
||||||
))
|
))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -84,9 +82,8 @@ type ChangeTarget struct {
|
|||||||
|
|
||||||
Name *string
|
Name *string
|
||||||
TargetType *domain.TargetType
|
TargetType *domain.TargetType
|
||||||
URL *string
|
Endpoint *string
|
||||||
Timeout *time.Duration
|
Timeout *time.Duration
|
||||||
Async *bool
|
|
||||||
InterruptOnError *bool
|
InterruptOnError *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,10 +97,10 @@ func (a *ChangeTarget) IsValid() error {
|
|||||||
if a.Timeout != nil && *a.Timeout == 0 {
|
if a.Timeout != nil && *a.Timeout == 0 {
|
||||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-08b39vdi57", "Errors.Target.NoTimeout")
|
return zerrors.ThrowInvalidArgument(nil, "COMMAND-08b39vdi57", "Errors.Target.NoTimeout")
|
||||||
}
|
}
|
||||||
if a.URL != nil {
|
if a.Endpoint != nil {
|
||||||
_, err := url.Parse(*a.URL)
|
_, err := url.Parse(*a.Endpoint)
|
||||||
if err != nil || *a.URL == "" {
|
if err != nil || *a.Endpoint == "" {
|
||||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-jsbaera7b6", "Errors.Target.InvalidURL")
|
return zerrors.ThrowInvalidArgument(err, "COMMAND-jsbaera7b6", "Errors.Target.InvalidURL")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -130,9 +127,8 @@ func (c *Commands) ChangeTarget(ctx context.Context, change *ChangeTarget, resou
|
|||||||
TargetAggregateFromWriteModel(&existing.WriteModel),
|
TargetAggregateFromWriteModel(&existing.WriteModel),
|
||||||
change.Name,
|
change.Name,
|
||||||
change.TargetType,
|
change.TargetType,
|
||||||
change.URL,
|
change.Endpoint,
|
||||||
change.Timeout,
|
change.Timeout,
|
||||||
change.Async,
|
|
||||||
change.InterruptOnError)
|
change.InterruptOnError)
|
||||||
if changedEvent == nil {
|
if changedEvent == nil {
|
||||||
return writeModelToObjectDetails(&existing.WriteModel), nil
|
return writeModelToObjectDetails(&existing.WriteModel), nil
|
||||||
|
@ -15,9 +15,8 @@ type TargetWriteModel struct {
|
|||||||
|
|
||||||
Name string
|
Name string
|
||||||
TargetType domain.TargetType
|
TargetType domain.TargetType
|
||||||
URL string
|
Endpoint string
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
Async bool
|
|
||||||
InterruptOnError bool
|
InterruptOnError bool
|
||||||
|
|
||||||
State domain.TargetState
|
State domain.TargetState
|
||||||
@ -39,9 +38,8 @@ func (wm *TargetWriteModel) Reduce() error {
|
|||||||
case *target.AddedEvent:
|
case *target.AddedEvent:
|
||||||
wm.Name = e.Name
|
wm.Name = e.Name
|
||||||
wm.TargetType = e.TargetType
|
wm.TargetType = e.TargetType
|
||||||
wm.URL = e.URL
|
wm.Endpoint = e.Endpoint
|
||||||
wm.Timeout = e.Timeout
|
wm.Timeout = e.Timeout
|
||||||
wm.Async = e.Async
|
|
||||||
wm.State = domain.TargetActive
|
wm.State = domain.TargetActive
|
||||||
case *target.ChangedEvent:
|
case *target.ChangedEvent:
|
||||||
if e.Name != nil {
|
if e.Name != nil {
|
||||||
@ -50,15 +48,12 @@ func (wm *TargetWriteModel) Reduce() error {
|
|||||||
if e.TargetType != nil {
|
if e.TargetType != nil {
|
||||||
wm.TargetType = *e.TargetType
|
wm.TargetType = *e.TargetType
|
||||||
}
|
}
|
||||||
if e.URL != nil {
|
if e.Endpoint != nil {
|
||||||
wm.URL = *e.URL
|
wm.Endpoint = *e.Endpoint
|
||||||
}
|
}
|
||||||
if e.Timeout != nil {
|
if e.Timeout != nil {
|
||||||
wm.Timeout = *e.Timeout
|
wm.Timeout = *e.Timeout
|
||||||
}
|
}
|
||||||
if e.Async != nil {
|
|
||||||
wm.Async = *e.Async
|
|
||||||
}
|
|
||||||
if e.InterruptOnError != nil {
|
if e.InterruptOnError != nil {
|
||||||
wm.InterruptOnError = *e.InterruptOnError
|
wm.InterruptOnError = *e.InterruptOnError
|
||||||
}
|
}
|
||||||
@ -86,9 +81,8 @@ func (wm *TargetWriteModel) NewChangedEvent(
|
|||||||
agg *eventstore.Aggregate,
|
agg *eventstore.Aggregate,
|
||||||
name *string,
|
name *string,
|
||||||
targetType *domain.TargetType,
|
targetType *domain.TargetType,
|
||||||
url *string,
|
endpoint *string,
|
||||||
timeout *time.Duration,
|
timeout *time.Duration,
|
||||||
async *bool,
|
|
||||||
interruptOnError *bool,
|
interruptOnError *bool,
|
||||||
) *target.ChangedEvent {
|
) *target.ChangedEvent {
|
||||||
changes := make([]target.Changes, 0)
|
changes := make([]target.Changes, 0)
|
||||||
@ -98,15 +92,12 @@ func (wm *TargetWriteModel) NewChangedEvent(
|
|||||||
if targetType != nil && wm.TargetType != *targetType {
|
if targetType != nil && wm.TargetType != *targetType {
|
||||||
changes = append(changes, target.ChangeTargetType(*targetType))
|
changes = append(changes, target.ChangeTargetType(*targetType))
|
||||||
}
|
}
|
||||||
if url != nil && wm.URL != *url {
|
if endpoint != nil && wm.Endpoint != *endpoint {
|
||||||
changes = append(changes, target.ChangeURL(*url))
|
changes = append(changes, target.ChangeEndpoint(*endpoint))
|
||||||
}
|
}
|
||||||
if timeout != nil && wm.Timeout != *timeout {
|
if timeout != nil && wm.Timeout != *timeout {
|
||||||
changes = append(changes, target.ChangeTimeout(*timeout))
|
changes = append(changes, target.ChangeTimeout(*timeout))
|
||||||
}
|
}
|
||||||
if async != nil && wm.Async != *async {
|
|
||||||
changes = append(changes, target.ChangeAsync(*async))
|
|
||||||
}
|
|
||||||
if interruptOnError != nil && wm.InterruptOnError != *interruptOnError {
|
if interruptOnError != nil && wm.InterruptOnError != *interruptOnError {
|
||||||
changes = append(changes, target.ChangeInterruptOnError(*interruptOnError))
|
changes = append(changes, target.ChangeInterruptOnError(*interruptOnError))
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ func targetAddEvent(aggID, resourceOwner string) *target.AddedEvent {
|
|||||||
"https://example.com",
|
"https://example.com",
|
||||||
time.Second,
|
time.Second,
|
||||||
false,
|
false,
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
|
|
||||||
func TestCommands_AddTarget(t *testing.T) {
|
func TestCommands_AddTarget(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||||
idGenerator id.Generator
|
idGenerator id.Generator
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
@ -41,7 +41,7 @@ func TestCommands_AddTarget(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"no resourceowner, error",
|
"no resourceowner, error",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t),
|
eventstore: expectEventstore(),
|
||||||
},
|
},
|
||||||
args{
|
args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@ -55,12 +55,12 @@ func TestCommands_AddTarget(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"no name, error",
|
"no name, error",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t),
|
eventstore: expectEventstore(),
|
||||||
},
|
},
|
||||||
args{
|
args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
add: &AddTarget{},
|
add: &AddTarget{},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "instance",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
err: zerrors.IsErrorInvalidArgument,
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
@ -69,50 +69,50 @@ func TestCommands_AddTarget(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"no timeout, error",
|
"no timeout, error",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t),
|
eventstore: expectEventstore(),
|
||||||
},
|
},
|
||||||
args{
|
args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
add: &AddTarget{
|
add: &AddTarget{
|
||||||
Name: "name",
|
Name: "name",
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "instance",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
err: zerrors.IsErrorInvalidArgument,
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"no url, error",
|
"no Endpoint, error",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t),
|
eventstore: expectEventstore(),
|
||||||
},
|
},
|
||||||
args{
|
args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
add: &AddTarget{
|
add: &AddTarget{
|
||||||
Name: "name",
|
Name: "name",
|
||||||
Timeout: time.Second,
|
Timeout: time.Second,
|
||||||
URL: "",
|
Endpoint: "",
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "instance",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
err: zerrors.IsErrorInvalidArgument,
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"no parsable url, error",
|
"no parsable Endpoint, error",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t),
|
eventstore: expectEventstore(),
|
||||||
},
|
},
|
||||||
args{
|
args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
add: &AddTarget{
|
add: &AddTarget{
|
||||||
Name: "name",
|
Name: "name",
|
||||||
Timeout: time.Second,
|
Timeout: time.Second,
|
||||||
URL: "://",
|
Endpoint: "://",
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "instance",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
err: zerrors.IsErrorInvalidArgument,
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
@ -121,18 +121,17 @@ func TestCommands_AddTarget(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"unique constraint failed, error",
|
"unique constraint failed, error",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
expectPushFailed(
|
expectPushFailed(
|
||||||
zerrors.ThrowPreconditionFailed(nil, "id", "name already exists"),
|
zerrors.ThrowPreconditionFailed(nil, "id", "name already exists"),
|
||||||
target.NewAddedEvent(context.Background(),
|
target.NewAddedEvent(context.Background(),
|
||||||
target.NewAggregate("id1", "org1"),
|
target.NewAggregate("id1", "instance"),
|
||||||
"name",
|
"name",
|
||||||
domain.TargetTypeWebhook,
|
domain.TargetTypeWebhook,
|
||||||
"https://example.com",
|
"https://example.com",
|
||||||
time.Second,
|
time.Second,
|
||||||
false,
|
false,
|
||||||
false,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -142,11 +141,11 @@ func TestCommands_AddTarget(t *testing.T) {
|
|||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
add: &AddTarget{
|
add: &AddTarget{
|
||||||
Name: "name",
|
Name: "name",
|
||||||
URL: "https://example.com",
|
Endpoint: "https://example.com",
|
||||||
Timeout: time.Second,
|
Timeout: time.Second,
|
||||||
TargetType: domain.TargetTypeWebhook,
|
TargetType: domain.TargetTypeWebhook,
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "instance",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
err: zerrors.IsPreconditionFailed,
|
err: zerrors.IsPreconditionFailed,
|
||||||
@ -155,16 +154,10 @@ func TestCommands_AddTarget(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"already existing",
|
"already existing",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
target.NewAddedEvent(context.Background(),
|
eventFromEventPusher(
|
||||||
target.NewAggregate("id1", "org1"),
|
targetAddEvent("target", "instance"),
|
||||||
"name",
|
|
||||||
domain.TargetTypeWebhook,
|
|
||||||
"https://example.com",
|
|
||||||
time.Second,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -176,9 +169,9 @@ func TestCommands_AddTarget(t *testing.T) {
|
|||||||
Name: "name",
|
Name: "name",
|
||||||
TargetType: domain.TargetTypeWebhook,
|
TargetType: domain.TargetTypeWebhook,
|
||||||
Timeout: time.Second,
|
Timeout: time.Second,
|
||||||
URL: "https://example.com",
|
Endpoint: "https://example.com",
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "instance",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
err: zerrors.IsErrorAlreadyExists,
|
err: zerrors.IsErrorAlreadyExists,
|
||||||
@ -187,18 +180,10 @@ func TestCommands_AddTarget(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"push ok",
|
"push ok",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
target.NewAddedEvent(context.Background(),
|
targetAddEvent("id1", "instance"),
|
||||||
target.NewAggregate("id1", "org1"),
|
|
||||||
"name",
|
|
||||||
domain.TargetTypeWebhook,
|
|
||||||
"https://example.com",
|
|
||||||
time.Second,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
idGenerator: mock.ExpectID(t, "id1"),
|
idGenerator: mock.ExpectID(t, "id1"),
|
||||||
@ -209,32 +194,28 @@ func TestCommands_AddTarget(t *testing.T) {
|
|||||||
Name: "name",
|
Name: "name",
|
||||||
TargetType: domain.TargetTypeWebhook,
|
TargetType: domain.TargetTypeWebhook,
|
||||||
Timeout: time.Second,
|
Timeout: time.Second,
|
||||||
URL: "https://example.com",
|
Endpoint: "https://example.com",
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "instance",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
id: "id1",
|
id: "id1",
|
||||||
details: &domain.ObjectDetails{
|
details: &domain.ObjectDetails{
|
||||||
ResourceOwner: "org1",
|
ResourceOwner: "instance",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"push full ok",
|
"push full ok",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
target.NewAddedEvent(context.Background(),
|
func() eventstore.Command {
|
||||||
target.NewAggregate("id1", "org1"),
|
event := targetAddEvent("id1", "instance")
|
||||||
"name",
|
event.InterruptOnError = true
|
||||||
domain.TargetTypeWebhook,
|
return event
|
||||||
"https://example.com",
|
}(),
|
||||||
time.Second,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
idGenerator: mock.ExpectID(t, "id1"),
|
idGenerator: mock.ExpectID(t, "id1"),
|
||||||
@ -244,17 +225,16 @@ func TestCommands_AddTarget(t *testing.T) {
|
|||||||
add: &AddTarget{
|
add: &AddTarget{
|
||||||
Name: "name",
|
Name: "name",
|
||||||
TargetType: domain.TargetTypeWebhook,
|
TargetType: domain.TargetTypeWebhook,
|
||||||
URL: "https://example.com",
|
Endpoint: "https://example.com",
|
||||||
Timeout: time.Second,
|
Timeout: time.Second,
|
||||||
Async: true,
|
|
||||||
InterruptOnError: true,
|
InterruptOnError: true,
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "instance",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
id: "id1",
|
id: "id1",
|
||||||
details: &domain.ObjectDetails{
|
details: &domain.ObjectDetails{
|
||||||
ResourceOwner: "org1",
|
ResourceOwner: "instance",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -262,7 +242,7 @@ func TestCommands_AddTarget(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
c := &Commands{
|
c := &Commands{
|
||||||
eventstore: tt.fields.eventstore,
|
eventstore: tt.fields.eventstore(t),
|
||||||
idGenerator: tt.fields.idGenerator,
|
idGenerator: tt.fields.idGenerator,
|
||||||
}
|
}
|
||||||
details, err := c.AddTarget(tt.args.ctx, tt.args.add, tt.args.resourceOwner)
|
details, err := c.AddTarget(tt.args.ctx, tt.args.add, tt.args.resourceOwner)
|
||||||
@ -282,7 +262,7 @@ func TestCommands_AddTarget(t *testing.T) {
|
|||||||
|
|
||||||
func TestCommands_ChangeTarget(t *testing.T) {
|
func TestCommands_ChangeTarget(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@ -302,7 +282,7 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"resourceowner missing, error",
|
"resourceowner missing, error",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t),
|
eventstore: expectEventstore(),
|
||||||
},
|
},
|
||||||
args{
|
args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@ -316,12 +296,12 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"id missing, error",
|
"id missing, error",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t),
|
eventstore: expectEventstore(),
|
||||||
},
|
},
|
||||||
args{
|
args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
change: &ChangeTarget{},
|
change: &ChangeTarget{},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "instance",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
err: zerrors.IsErrorInvalidArgument,
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
@ -330,14 +310,14 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"name empty, error",
|
"name empty, error",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t),
|
eventstore: expectEventstore(),
|
||||||
},
|
},
|
||||||
args{
|
args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
change: &ChangeTarget{
|
change: &ChangeTarget{
|
||||||
Name: gu.Ptr(""),
|
Name: gu.Ptr(""),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "instance",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
err: zerrors.IsErrorInvalidArgument,
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
@ -346,46 +326,46 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"timeout empty, error",
|
"timeout empty, error",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t),
|
eventstore: expectEventstore(),
|
||||||
},
|
},
|
||||||
args{
|
args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
change: &ChangeTarget{
|
change: &ChangeTarget{
|
||||||
Timeout: gu.Ptr(time.Duration(0)),
|
Timeout: gu.Ptr(time.Duration(0)),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "instance",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
err: zerrors.IsErrorInvalidArgument,
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url empty, error",
|
"Endpoint empty, error",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t),
|
eventstore: expectEventstore(),
|
||||||
},
|
},
|
||||||
args{
|
args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
change: &ChangeTarget{
|
change: &ChangeTarget{
|
||||||
URL: gu.Ptr(""),
|
Endpoint: gu.Ptr(""),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "instance",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
err: zerrors.IsErrorInvalidArgument,
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url not parsable, error",
|
"Endpoint not parsable, error",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t),
|
eventstore: expectEventstore(),
|
||||||
},
|
},
|
||||||
args{
|
args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
change: &ChangeTarget{
|
change: &ChangeTarget{
|
||||||
URL: gu.Ptr("://"),
|
Endpoint: gu.Ptr("://"),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "instance",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
err: zerrors.IsErrorInvalidArgument,
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
@ -394,7 +374,7 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"not found, error",
|
"not found, error",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -406,7 +386,7 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Name: gu.Ptr("name"),
|
Name: gu.Ptr("name"),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "instance",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
err: zerrors.IsNotFound,
|
err: zerrors.IsNotFound,
|
||||||
@ -415,18 +395,10 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"no changes",
|
"no changes",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
target.NewAddedEvent(context.Background(),
|
targetAddEvent("target", "instance"),
|
||||||
target.NewAggregate("id1", "org1"),
|
|
||||||
"name",
|
|
||||||
domain.TargetTypeWebhook,
|
|
||||||
"https://example.com",
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -439,35 +411,27 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
|||||||
},
|
},
|
||||||
TargetType: gu.Ptr(domain.TargetTypeWebhook),
|
TargetType: gu.Ptr(domain.TargetTypeWebhook),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "instance",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
details: &domain.ObjectDetails{
|
details: &domain.ObjectDetails{
|
||||||
ResourceOwner: "org1",
|
ResourceOwner: "instance",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"unique constraint failed, error",
|
"unique constraint failed, error",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
target.NewAddedEvent(context.Background(),
|
targetAddEvent("target", "instance"),
|
||||||
target.NewAggregate("id1", "org1"),
|
|
||||||
"name",
|
|
||||||
domain.TargetTypeWebhook,
|
|
||||||
"https://example.com",
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectPushFailed(
|
expectPushFailed(
|
||||||
zerrors.ThrowPreconditionFailed(nil, "id", "name already exists"),
|
zerrors.ThrowPreconditionFailed(nil, "id", "name already exists"),
|
||||||
target.NewChangedEvent(context.Background(),
|
target.NewChangedEvent(context.Background(),
|
||||||
target.NewAggregate("id1", "org1"),
|
target.NewAggregate("id1", "instance"),
|
||||||
[]target.Changes{
|
[]target.Changes{
|
||||||
target.ChangeName("name", "name2"),
|
target.ChangeName("name", "name2"),
|
||||||
},
|
},
|
||||||
@ -483,7 +447,7 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Name: gu.Ptr("name2"),
|
Name: gu.Ptr("name2"),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "instance",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
err: zerrors.IsPreconditionFailed,
|
err: zerrors.IsPreconditionFailed,
|
||||||
@ -492,23 +456,15 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"push ok",
|
"push ok",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
target.NewAddedEvent(context.Background(),
|
targetAddEvent("id1", "instance"),
|
||||||
target.NewAggregate("id1", "org1"),
|
|
||||||
"name",
|
|
||||||
domain.TargetTypeWebhook,
|
|
||||||
"https://example.com",
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectPush(
|
expectPush(
|
||||||
target.NewChangedEvent(context.Background(),
|
target.NewChangedEvent(context.Background(),
|
||||||
target.NewAggregate("id1", "org1"),
|
target.NewAggregate("id1", "instance"),
|
||||||
[]target.Changes{
|
[]target.Changes{
|
||||||
target.ChangeName("name", "name2"),
|
target.ChangeName("name", "name2"),
|
||||||
},
|
},
|
||||||
@ -524,40 +480,31 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Name: gu.Ptr("name2"),
|
Name: gu.Ptr("name2"),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "instance",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
details: &domain.ObjectDetails{
|
details: &domain.ObjectDetails{
|
||||||
ResourceOwner: "org1",
|
ResourceOwner: "instance",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"push full ok",
|
"push full ok",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
target.NewAddedEvent(context.Background(),
|
targetAddEvent("id1", "instance"),
|
||||||
target.NewAggregate("id1", "org1"),
|
|
||||||
"name",
|
|
||||||
domain.TargetTypeWebhook,
|
|
||||||
"https://example.com",
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectPush(
|
expectPush(
|
||||||
target.NewChangedEvent(context.Background(),
|
target.NewChangedEvent(context.Background(),
|
||||||
target.NewAggregate("id1", "org1"),
|
target.NewAggregate("id1", "instance"),
|
||||||
[]target.Changes{
|
[]target.Changes{
|
||||||
target.ChangeName("name", "name2"),
|
target.ChangeName("name", "name2"),
|
||||||
target.ChangeURL("https://example2.com"),
|
target.ChangeEndpoint("https://example2.com"),
|
||||||
target.ChangeTargetType(domain.TargetTypeRequestResponse),
|
target.ChangeTargetType(domain.TargetTypeCall),
|
||||||
target.ChangeTimeout(time.Second),
|
target.ChangeTimeout(10 * time.Second),
|
||||||
target.ChangeAsync(true),
|
|
||||||
target.ChangeInterruptOnError(true),
|
target.ChangeInterruptOnError(true),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -571,17 +518,16 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
|||||||
AggregateID: "id1",
|
AggregateID: "id1",
|
||||||
},
|
},
|
||||||
Name: gu.Ptr("name2"),
|
Name: gu.Ptr("name2"),
|
||||||
URL: gu.Ptr("https://example2.com"),
|
Endpoint: gu.Ptr("https://example2.com"),
|
||||||
TargetType: gu.Ptr(domain.TargetTypeRequestResponse),
|
TargetType: gu.Ptr(domain.TargetTypeCall),
|
||||||
Timeout: gu.Ptr(time.Second),
|
Timeout: gu.Ptr(10 * time.Second),
|
||||||
Async: gu.Ptr(true),
|
|
||||||
InterruptOnError: gu.Ptr(true),
|
InterruptOnError: gu.Ptr(true),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "instance",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
details: &domain.ObjectDetails{
|
details: &domain.ObjectDetails{
|
||||||
ResourceOwner: "org1",
|
ResourceOwner: "instance",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -589,7 +535,7 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
c := &Commands{
|
c := &Commands{
|
||||||
eventstore: tt.fields.eventstore,
|
eventstore: tt.fields.eventstore(t),
|
||||||
}
|
}
|
||||||
details, err := c.ChangeTarget(tt.args.ctx, tt.args.change, tt.args.resourceOwner)
|
details, err := c.ChangeTarget(tt.args.ctx, tt.args.change, tt.args.resourceOwner)
|
||||||
if tt.res.err == nil {
|
if tt.res.err == nil {
|
||||||
@ -607,7 +553,7 @@ func TestCommands_ChangeTarget(t *testing.T) {
|
|||||||
|
|
||||||
func TestCommands_DeleteTarget(t *testing.T) {
|
func TestCommands_DeleteTarget(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@ -627,12 +573,12 @@ func TestCommands_DeleteTarget(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"id missing, error",
|
"id missing, error",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t),
|
eventstore: expectEventstore(),
|
||||||
},
|
},
|
||||||
args{
|
args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
id: "",
|
id: "",
|
||||||
resourceOwner: "org1",
|
resourceOwner: "instance",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
err: zerrors.IsErrorInvalidArgument,
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
@ -641,14 +587,14 @@ func TestCommands_DeleteTarget(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"not found, error",
|
"not found, error",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
args{
|
args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
id: "id1",
|
id: "id1",
|
||||||
resourceOwner: "org1",
|
resourceOwner: "instance",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
err: zerrors.IsNotFound,
|
err: zerrors.IsNotFound,
|
||||||
@ -657,23 +603,15 @@ func TestCommands_DeleteTarget(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"remove ok",
|
"remove ok",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
target.NewAddedEvent(context.Background(),
|
targetAddEvent("id1", "instance"),
|
||||||
target.NewAggregate("id1", "org1"),
|
|
||||||
"name",
|
|
||||||
domain.TargetTypeWebhook,
|
|
||||||
"https://example.com",
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectPush(
|
expectPush(
|
||||||
target.NewRemovedEvent(context.Background(),
|
target.NewRemovedEvent(context.Background(),
|
||||||
target.NewAggregate("id1", "org1"),
|
target.NewAggregate("id1", "instance"),
|
||||||
"name",
|
"name",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -682,11 +620,11 @@ func TestCommands_DeleteTarget(t *testing.T) {
|
|||||||
args{
|
args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
id: "id1",
|
id: "id1",
|
||||||
resourceOwner: "org1",
|
resourceOwner: "instance",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
details: &domain.ObjectDetails{
|
details: &domain.ObjectDetails{
|
||||||
ResourceOwner: "org1",
|
ResourceOwner: "instance",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -694,7 +632,7 @@ func TestCommands_DeleteTarget(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
c := &Commands{
|
c := &Commands{
|
||||||
eventstore: tt.fields.eventstore,
|
eventstore: tt.fields.eventstore(t),
|
||||||
}
|
}
|
||||||
details, err := c.DeleteTarget(tt.args.ctx, tt.args.id, tt.args.resourceOwner)
|
details, err := c.DeleteTarget(tt.args.ctx, tt.args.id, tt.args.resourceOwner)
|
||||||
if tt.res.err == nil {
|
if tt.res.err == nil {
|
||||||
|
@ -31,3 +31,17 @@ func (e ExecutionType) String() string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ExecutionTargetType uint
|
||||||
|
|
||||||
|
func (s ExecutionTargetType) Valid() bool {
|
||||||
|
return s < executionTargetTypeStateCount
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ExecutionTargetTypeUnspecified ExecutionTargetType = iota
|
||||||
|
ExecutionTargetTypeInclude
|
||||||
|
ExecutionTargetTypeTarget
|
||||||
|
|
||||||
|
executionTargetTypeStateCount
|
||||||
|
)
|
||||||
|
@ -4,7 +4,8 @@ type TargetType uint
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
TargetTypeWebhook TargetType = iota
|
TargetTypeWebhook TargetType = iota
|
||||||
TargetTypeRequestResponse
|
TargetTypeCall
|
||||||
|
TargetTypeAsync
|
||||||
)
|
)
|
||||||
|
|
||||||
type TargetState int32
|
type TargetState int32
|
||||||
|
122
internal/execution/execution.go
Normal file
122
internal/execution/execution.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
package execution
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/zitadel/logging"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||||
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ContextInfo interface {
|
||||||
|
GetHTTPRequestBody() []byte
|
||||||
|
GetContent() interface{}
|
||||||
|
SetHTTPResponseBody([]byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Target interface {
|
||||||
|
GetTargetID() string
|
||||||
|
IsInterruptOnError() bool
|
||||||
|
GetEndpoint() string
|
||||||
|
GetTargetType() domain.TargetType
|
||||||
|
GetTimeout() time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallTargets call a list of targets in order with handling of error and responses
|
||||||
|
func CallTargets(
|
||||||
|
ctx context.Context,
|
||||||
|
targets []Target,
|
||||||
|
info ContextInfo,
|
||||||
|
) (_ interface{}, err error) {
|
||||||
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
|
defer span.EndWithError(err)
|
||||||
|
|
||||||
|
for _, target := range targets {
|
||||||
|
// call the type of target
|
||||||
|
resp, err := CallTarget(ctx, target, info)
|
||||||
|
// handle error if interrupt is set
|
||||||
|
if err != nil && target.IsInterruptOnError() {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(resp) > 0 {
|
||||||
|
// error in unmarshalling
|
||||||
|
if err := info.SetHTTPResponseBody(resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return info.GetContent(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContextInfoRequest interface {
|
||||||
|
GetHTTPRequestBody() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallTarget call the desired type of target with handling of responses
|
||||||
|
func CallTarget(
|
||||||
|
ctx context.Context,
|
||||||
|
target Target,
|
||||||
|
info ContextInfoRequest,
|
||||||
|
) (res []byte, err error) {
|
||||||
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
|
defer span.EndWithError(err)
|
||||||
|
|
||||||
|
switch target.GetTargetType() {
|
||||||
|
// get request, ignore response and return request and error for handling in list of targets
|
||||||
|
case domain.TargetTypeWebhook:
|
||||||
|
return nil, webhook(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody())
|
||||||
|
// get request, return response and error
|
||||||
|
case domain.TargetTypeCall:
|
||||||
|
return call(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody())
|
||||||
|
case domain.TargetTypeAsync:
|
||||||
|
go func(target Target, info ContextInfoRequest) {
|
||||||
|
if _, err := call(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody()); err != nil {
|
||||||
|
logging.WithFields("target", target.GetTargetID()).OnError(err).Info(err)
|
||||||
|
}
|
||||||
|
}(target, info)
|
||||||
|
return nil, nil
|
||||||
|
default:
|
||||||
|
return nil, zerrors.ThrowInternal(nil, "EXEC-auqnansr2m", "Errors.Execution.Unknown")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// webhook call a webhook, ignore the response but return the errror
|
||||||
|
func webhook(ctx context.Context, url string, timeout time.Duration, body []byte) error {
|
||||||
|
_, err := call(ctx, url, timeout, body)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// call function to do a post HTTP request to a desired url with timeout
|
||||||
|
func call(ctx context.Context, url string, timeout time.Duration, body []byte) (_ []byte, err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
|
defer func() {
|
||||||
|
cancel()
|
||||||
|
span.EndWithError(err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
client := http.DefaultClient
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Check for success between 200 and 299, redirect 300 to 399 is handled by the client, return error with statusCode >= 400
|
||||||
|
if resp.StatusCode >= 200 && resp.StatusCode <= 299 {
|
||||||
|
return io.ReadAll(resp.Body)
|
||||||
|
}
|
||||||
|
return nil, zerrors.ThrowUnknown(nil, "EXEC-dra6yamk98", "Errors.Execution.Failed")
|
||||||
|
}
|
347
internal/execution/execution_test.go
Normal file
347
internal/execution/execution_test.go
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
package execution
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Target = &mockTarget{}
|
||||||
|
|
||||||
|
type mockTarget struct {
|
||||||
|
InstanceID string
|
||||||
|
ExecutionID string
|
||||||
|
TargetID string
|
||||||
|
TargetType domain.TargetType
|
||||||
|
Endpoint string
|
||||||
|
Timeout time.Duration
|
||||||
|
InterruptOnError bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *mockTarget) GetTargetID() string {
|
||||||
|
return e.TargetID
|
||||||
|
}
|
||||||
|
func (e *mockTarget) IsInterruptOnError() bool {
|
||||||
|
return e.InterruptOnError
|
||||||
|
}
|
||||||
|
func (e *mockTarget) GetEndpoint() string {
|
||||||
|
return e.Endpoint
|
||||||
|
}
|
||||||
|
func (e *mockTarget) GetTargetType() domain.TargetType {
|
||||||
|
return e.TargetType
|
||||||
|
}
|
||||||
|
func (e *mockTarget) GetTimeout() time.Duration {
|
||||||
|
return e.Timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Call(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
timeout time.Duration
|
||||||
|
sleep time.Duration
|
||||||
|
method string
|
||||||
|
body []byte
|
||||||
|
respBody []byte
|
||||||
|
statusCode int
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
body []byte
|
||||||
|
wantErr bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"not ok status",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
timeout: time.Minute,
|
||||||
|
sleep: time.Second,
|
||||||
|
method: http.MethodPost,
|
||||||
|
body: []byte("{\"request\": \"values\"}"),
|
||||||
|
respBody: []byte("{\"response\": \"values\"}"),
|
||||||
|
statusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timeout",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
timeout: time.Second,
|
||||||
|
sleep: time.Second,
|
||||||
|
method: http.MethodPost,
|
||||||
|
body: []byte("{\"request\": \"values\"}"),
|
||||||
|
respBody: []byte("{\"response\": \"values\"}"),
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ok",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
timeout: time.Minute,
|
||||||
|
sleep: time.Second,
|
||||||
|
method: http.MethodPost,
|
||||||
|
body: []byte("{\"request\": \"values\"}"),
|
||||||
|
respBody: []byte("{\"response\": \"values\"}"),
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
body: []byte("{\"response\": \"values\"}"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
respBody, err := testServerCall(t,
|
||||||
|
tt.args.method,
|
||||||
|
tt.args.body,
|
||||||
|
tt.args.sleep,
|
||||||
|
tt.args.statusCode,
|
||||||
|
tt.args.respBody,
|
||||||
|
testCall(tt.args.ctx, tt.args.timeout, tt.args.body),
|
||||||
|
)
|
||||||
|
if tt.res.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, respBody)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.res.body, respBody)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCall(ctx context.Context, timeout time.Duration, body []byte) func(string) ([]byte, error) {
|
||||||
|
return func(url string) ([]byte, error) {
|
||||||
|
return call(ctx, url, timeout, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCallTarget(ctx context.Context,
|
||||||
|
target *mockTarget,
|
||||||
|
info ContextInfoRequest,
|
||||||
|
) func(string) ([]byte, error) {
|
||||||
|
return func(url string) (r []byte, err error) {
|
||||||
|
target.Endpoint = url
|
||||||
|
return CallTarget(ctx, target, info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testServerCall(
|
||||||
|
t *testing.T,
|
||||||
|
method string,
|
||||||
|
body []byte,
|
||||||
|
timeout time.Duration,
|
||||||
|
statusCode int,
|
||||||
|
respBody []byte,
|
||||||
|
call func(string) ([]byte, error),
|
||||||
|
) ([]byte, error) {
|
||||||
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
checkRequest(t, r, method, body)
|
||||||
|
|
||||||
|
if statusCode != http.StatusOK {
|
||||||
|
http.Error(w, "error", statusCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(timeout)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
if _, err := io.WriteString(w, string(respBody)); err != nil {
|
||||||
|
http.Error(w, "error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(handler))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
return call(server.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkRequest(t *testing.T, sent *http.Request, method string, expectedBody []byte) {
|
||||||
|
sentBody, err := io.ReadAll(sent.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expectedBody, sentBody)
|
||||||
|
require.Equal(t, method, sent.Method)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ContextInfoRequest = &mockContextInfoRequest{}
|
||||||
|
|
||||||
|
type request struct {
|
||||||
|
Request string `json:"request"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockContextInfoRequest struct {
|
||||||
|
Request *request `json:"request"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockContextInfoRequest(s string) *mockContextInfoRequest {
|
||||||
|
return &mockContextInfoRequest{&request{s}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockContextInfoRequest) GetHTTPRequestBody() []byte {
|
||||||
|
data, _ := json.Marshal(c)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockContextInfoRequest) GetContent() []byte {
|
||||||
|
data, _ := json.Marshal(c.Request)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_CallTarget(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
target *mockTarget
|
||||||
|
sleep time.Duration
|
||||||
|
|
||||||
|
info ContextInfoRequest
|
||||||
|
|
||||||
|
method string
|
||||||
|
body []byte
|
||||||
|
|
||||||
|
respBody []byte
|
||||||
|
statusCode int
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
body []byte
|
||||||
|
wantErr bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"unknown targettype, error",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
sleep: time.Second,
|
||||||
|
method: http.MethodPost,
|
||||||
|
info: newMockContextInfoRequest("content1"),
|
||||||
|
target: &mockTarget{
|
||||||
|
TargetType: 4,
|
||||||
|
},
|
||||||
|
body: []byte("{\"request\":{\"request\":\"content1\"}}"),
|
||||||
|
respBody: []byte("{\"request\":\"content2\"}"),
|
||||||
|
statusCode: http.StatusInternalServerError,
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"webhook, error",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
sleep: time.Second,
|
||||||
|
method: http.MethodPost,
|
||||||
|
info: newMockContextInfoRequest("content1"),
|
||||||
|
target: &mockTarget{
|
||||||
|
TargetType: domain.TargetTypeWebhook,
|
||||||
|
Timeout: time.Minute,
|
||||||
|
},
|
||||||
|
body: []byte("{\"request\":{\"request\":\"content1\"}}"),
|
||||||
|
respBody: []byte("{\"request\":\"content2\"}"),
|
||||||
|
statusCode: http.StatusInternalServerError,
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"webhook, ok",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
sleep: time.Second,
|
||||||
|
method: http.MethodPost,
|
||||||
|
info: newMockContextInfoRequest("content1"),
|
||||||
|
target: &mockTarget{
|
||||||
|
TargetType: domain.TargetTypeWebhook,
|
||||||
|
Timeout: time.Minute,
|
||||||
|
},
|
||||||
|
body: []byte("{\"request\":{\"request\":\"content1\"}}"),
|
||||||
|
respBody: []byte("{\"request\":\"content2\"}"),
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
body: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"request response, error",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
sleep: time.Second,
|
||||||
|
method: http.MethodPost,
|
||||||
|
info: newMockContextInfoRequest("content1"),
|
||||||
|
target: &mockTarget{
|
||||||
|
TargetType: domain.TargetTypeCall,
|
||||||
|
Timeout: time.Minute,
|
||||||
|
},
|
||||||
|
body: []byte("{\"request\":{\"request\":\"content1\"}}"),
|
||||||
|
respBody: []byte("{\"request\":\"content2\"}"),
|
||||||
|
statusCode: http.StatusInternalServerError,
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"request response, ok",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
sleep: time.Second,
|
||||||
|
method: http.MethodPost,
|
||||||
|
info: newMockContextInfoRequest("content1"),
|
||||||
|
target: &mockTarget{
|
||||||
|
TargetType: domain.TargetTypeCall,
|
||||||
|
Timeout: time.Minute,
|
||||||
|
},
|
||||||
|
body: []byte("{\"request\":{\"request\":\"content1\"}}"),
|
||||||
|
respBody: []byte("{\"request\":\"content2\"}"),
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
body: []byte("{\"request\":\"content2\"}"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
respBody, err := testServerCall(t,
|
||||||
|
tt.args.method,
|
||||||
|
tt.args.body,
|
||||||
|
tt.args.sleep,
|
||||||
|
tt.args.statusCode,
|
||||||
|
tt.args.respBody,
|
||||||
|
testCallTarget(tt.args.ctx, tt.args.target, tt.args.info),
|
||||||
|
)
|
||||||
|
if tt.res.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.res.body, respBody)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -65,7 +65,9 @@ func AssertListDetails[D ListDetailsMsg](t testing.TB, expected, actual D) {
|
|||||||
|
|
||||||
assert.Equal(t, wantDetails.GetTotalResult(), gotDetails.GetTotalResult())
|
assert.Equal(t, wantDetails.GetTotalResult(), gotDetails.GetTotalResult())
|
||||||
|
|
||||||
|
if wantDetails.GetTimestamp() != nil {
|
||||||
gotCD := gotDetails.GetTimestamp().AsTime()
|
gotCD := gotDetails.GetTimestamp().AsTime()
|
||||||
wantCD := time.Now()
|
wantCD := time.Now()
|
||||||
assert.WithinRange(t, gotCD, wantCD.Add(-time.Minute), wantCD.Add(time.Minute))
|
assert.WithinRange(t, gotCD, wantCD.Add(-time.Minute), wantCD.Add(time.Minute))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -522,39 +522,32 @@ func (s *Tester) CreateProjectMembership(t *testing.T, ctx context.Context, proj
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Tester) CreateTarget(ctx context.Context, t *testing.T) *action.CreateTargetResponse {
|
func (s *Tester) CreateTarget(ctx context.Context, t *testing.T, name, endpoint string, ty domain.TargetType, interrupt bool) *action.CreateTargetResponse {
|
||||||
|
nameSet := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||||
|
if name != "" {
|
||||||
|
nameSet = name
|
||||||
|
}
|
||||||
req := &action.CreateTargetRequest{
|
req := &action.CreateTargetRequest{
|
||||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
Name: nameSet,
|
||||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
Endpoint: endpoint,
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
|
||||||
Url: "https://example.com",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
}
|
}
|
||||||
target, err := s.Client.ActionV3.CreateTarget(ctx, req)
|
switch ty {
|
||||||
require.NoError(t, err)
|
case domain.TargetTypeWebhook:
|
||||||
return target
|
req.TargetType = &action.CreateTargetRequest_RestWebhook{
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Tester) CreateTargetWithNameAndType(ctx context.Context, t *testing.T, name string, async bool, interrupt bool) *action.CreateTargetResponse {
|
|
||||||
req := &action.CreateTargetRequest{
|
|
||||||
Name: name,
|
|
||||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
RestWebhook: &action.SetRESTWebhook{
|
||||||
Url: "https://example.com",
|
InterruptOnError: interrupt,
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
case domain.TargetTypeCall:
|
||||||
|
req.TargetType = &action.CreateTargetRequest_RestCall{
|
||||||
|
RestCall: &action.SetRESTCall{
|
||||||
|
InterruptOnError: interrupt,
|
||||||
},
|
},
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
|
||||||
}
|
}
|
||||||
if async {
|
case domain.TargetTypeAsync:
|
||||||
req.ExecutionType = &action.CreateTargetRequest_IsAsync{
|
req.TargetType = &action.CreateTargetRequest_RestAsync{
|
||||||
IsAsync: true,
|
RestAsync: &action.SetRESTAsync{},
|
||||||
}
|
|
||||||
}
|
|
||||||
if interrupt {
|
|
||||||
req.ExecutionType = &action.CreateTargetRequest_InterruptOnError{
|
|
||||||
InterruptOnError: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
target, err := s.Client.ActionV3.CreateTarget(ctx, req)
|
target, err := s.Client.ActionV3.CreateTarget(ctx, req)
|
||||||
@ -562,16 +555,22 @@ func (s *Tester) CreateTargetWithNameAndType(ctx context.Context, t *testing.T,
|
|||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Tester) SetExecution(ctx context.Context, t *testing.T, cond *action.Condition, targets []string, includes []string) *action.SetExecutionResponse {
|
func (s *Tester) SetExecution(ctx context.Context, t *testing.T, cond *action.Condition, targets []*action.ExecutionTargetType) *action.SetExecutionResponse {
|
||||||
target, err := s.Client.ActionV3.SetExecution(ctx, &action.SetExecutionRequest{
|
target, err := s.Client.ActionV3.SetExecution(ctx, &action.SetExecutionRequest{
|
||||||
Condition: cond,
|
Condition: cond,
|
||||||
Targets: targets,
|
Targets: targets,
|
||||||
Includes: includes,
|
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Tester) DeleteExecution(ctx context.Context, t *testing.T, cond *action.Condition) {
|
||||||
|
_, err := s.Client.ActionV3.DeleteExecution(ctx, &action.DeleteExecutionRequest{
|
||||||
|
Condition: cond,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Tester) CreateUserSchema(ctx context.Context, t *testing.T) *schema.CreateUserSchemaResponse {
|
func (s *Tester) CreateUserSchema(ctx context.Context, t *testing.T) *schema.CreateUserSchemaResponse {
|
||||||
return s.CreateUserSchemaWithType(ctx, t, fmt.Sprint(time.Now().UnixNano()+1))
|
return s.CreateUserSchemaWithType(ctx, t, fmt.Sprint(time.Now().UnixNano()+1))
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,10 @@ package query
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
sq "github.com/Masterminds/squirrel"
|
sq "github.com/Masterminds/squirrel"
|
||||||
|
|
||||||
@ -11,6 +14,8 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/database"
|
"github.com/zitadel/zitadel/internal/database"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/query/projection"
|
"github.com/zitadel/zitadel/internal/query/projection"
|
||||||
|
exec "github.com/zitadel/zitadel/internal/repository/execution"
|
||||||
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||||
"github.com/zitadel/zitadel/internal/zerrors"
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -23,18 +28,10 @@ var (
|
|||||||
name: projection.ExecutionIDCol,
|
name: projection.ExecutionIDCol,
|
||||||
table: executionTable,
|
table: executionTable,
|
||||||
}
|
}
|
||||||
ExecutionColumnCreationDate = Column{
|
|
||||||
name: projection.ExecutionCreationDateCol,
|
|
||||||
table: executionTable,
|
|
||||||
}
|
|
||||||
ExecutionColumnChangeDate = Column{
|
ExecutionColumnChangeDate = Column{
|
||||||
name: projection.ExecutionChangeDateCol,
|
name: projection.ExecutionChangeDateCol,
|
||||||
table: executionTable,
|
table: executionTable,
|
||||||
}
|
}
|
||||||
ExecutionColumnResourceOwner = Column{
|
|
||||||
name: projection.ExecutionResourceOwnerCol,
|
|
||||||
table: executionTable,
|
|
||||||
}
|
|
||||||
ExecutionColumnInstanceID = Column{
|
ExecutionColumnInstanceID = Column{
|
||||||
name: projection.ExecutionInstanceIDCol,
|
name: projection.ExecutionInstanceIDCol,
|
||||||
table: executionTable,
|
table: executionTable,
|
||||||
@ -43,14 +40,33 @@ var (
|
|||||||
name: projection.ExecutionSequenceCol,
|
name: projection.ExecutionSequenceCol,
|
||||||
table: executionTable,
|
table: executionTable,
|
||||||
}
|
}
|
||||||
ExecutionColumnTargets = Column{
|
|
||||||
name: projection.ExecutionTargetsCol,
|
executionTargetsTable = table{
|
||||||
table: executionTable,
|
name: projection.ExecutionTable + "_" + projection.ExecutionTargetSuffix,
|
||||||
|
instanceIDCol: projection.ExecutionTargetInstanceIDCol,
|
||||||
}
|
}
|
||||||
ExecutionColumnIncludes = Column{
|
executionTargetsTableAlias = executionTargetsTable.setAlias("execution_targets")
|
||||||
name: projection.ExecutionIncludesCol,
|
ExecutionTargetsColumnInstanceID = Column{
|
||||||
table: executionTable,
|
name: projection.ExecutionTargetInstanceIDCol,
|
||||||
|
table: executionTargetsTableAlias,
|
||||||
}
|
}
|
||||||
|
ExecutionTargetsColumnExecutionID = Column{
|
||||||
|
name: projection.ExecutionTargetExecutionIDCol,
|
||||||
|
table: executionTargetsTableAlias,
|
||||||
|
}
|
||||||
|
executionTargetsListCol = Column{
|
||||||
|
name: "targets",
|
||||||
|
table: executionTargetsTableAlias,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//go:embed execution_targets.sql
|
||||||
|
executionTargetsQuery string
|
||||||
|
//go:embed targets_by_execution_id.sql
|
||||||
|
TargetsByExecutionIDQuery string
|
||||||
|
//go:embed targets_by_execution_ids.sql
|
||||||
|
TargetsByExecutionIDsQuery string
|
||||||
)
|
)
|
||||||
|
|
||||||
type Executions struct {
|
type Executions struct {
|
||||||
@ -66,8 +82,7 @@ type Execution struct {
|
|||||||
ID string
|
ID string
|
||||||
domain.ObjectDetails
|
domain.ObjectDetails
|
||||||
|
|
||||||
Targets database.TextArray[string]
|
Targets []*exec.Target
|
||||||
Includes database.TextArray[string]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExecutionSearchQueries struct {
|
type ExecutionSearchQueries struct {
|
||||||
@ -108,39 +123,211 @@ func NewExecutionTypeSearchQuery(t domain.ExecutionType) (SearchQuery, error) {
|
|||||||
return NewTextQuery(ExecutionColumnID, t.String(), TextStartsWith)
|
return NewTextQuery(ExecutionColumnID, t.String(), TextStartsWith)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewExecutionTargetSearchQuery(value string) (SearchQuery, error) {
|
func NewTargetSearchQuery(target string) (SearchQuery, error) {
|
||||||
return NewTextQuery(ExecutionColumnTargets, value, TextListContains)
|
data, err := targetItemJSONB(domain.ExecutionTargetTypeTarget, target)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewListContains(executionTargetsListCol, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewExecutionIncludeSearchQuery(value string) (SearchQuery, error) {
|
func NewIncludeSearchQuery(include string) (SearchQuery, error) {
|
||||||
return NewTextQuery(ExecutionColumnIncludes, value, TextListContains)
|
data, err := targetItemJSONB(domain.ExecutionTargetTypeInclude, include)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewListContains(executionTargetsListCol, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshall executionTargets into the same JSONB structure as in the SQL queries
|
||||||
|
func targetItemJSONB(t domain.ExecutionTargetType, targetItem string) ([]byte, error) {
|
||||||
|
var target *executionTarget
|
||||||
|
switch t {
|
||||||
|
case domain.ExecutionTargetTypeTarget:
|
||||||
|
target = &executionTarget{Target: targetItem}
|
||||||
|
case domain.ExecutionTargetTypeInclude:
|
||||||
|
target = &executionTarget{Include: targetItem}
|
||||||
|
case domain.ExecutionTargetTypeUnspecified:
|
||||||
|
return nil, nil
|
||||||
|
default:
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return json.Marshal([]*executionTarget{target})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TargetsByExecutionID query list of targets for best match of a list of IDs, for example:
|
||||||
|
// [ "request/zitadel.action.v3alpha.ActionService/GetTargetByID",
|
||||||
|
// "request/zitadel.action.v3alpha.ActionService",
|
||||||
|
// "request" ]
|
||||||
|
func (q *Queries) TargetsByExecutionID(ctx context.Context, ids []string) (execution []*ExecutionTarget, err error) {
|
||||||
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
|
defer func() { span.End() }()
|
||||||
|
|
||||||
|
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||||
|
if instanceID == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = q.client.QueryContext(ctx,
|
||||||
|
func(rows *sql.Rows) error {
|
||||||
|
execution, err = scanExecutionTargets(rows)
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
TargetsByExecutionIDQuery,
|
||||||
|
instanceID,
|
||||||
|
database.TextArray[string](ids),
|
||||||
|
)
|
||||||
|
return execution, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TargetsByExecutionIDs query list of targets for best matches of 2 separate lists of IDs, combined for performance, for example:
|
||||||
|
// [ "request/zitadel.action.v3alpha.ActionService/GetTargetByID",
|
||||||
|
// "request/zitadel.action.v3alpha.ActionService",
|
||||||
|
// "request" ]
|
||||||
|
// and
|
||||||
|
// [ "response/zitadel.action.v3alpha.ActionService/GetTargetByID",
|
||||||
|
// "response/zitadel.action.v3alpha.ActionService",
|
||||||
|
// "response" ]
|
||||||
|
func (q *Queries) TargetsByExecutionIDs(ctx context.Context, ids1, ids2 []string) (execution []*ExecutionTarget, err error) {
|
||||||
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
|
defer func() { span.End() }()
|
||||||
|
|
||||||
|
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||||
|
if instanceID == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = q.client.QueryContext(ctx,
|
||||||
|
func(rows *sql.Rows) error {
|
||||||
|
execution, err = scanExecutionTargets(rows)
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
TargetsByExecutionIDsQuery,
|
||||||
|
instanceID,
|
||||||
|
database.TextArray[string](ids1),
|
||||||
|
database.TextArray[string](ids2),
|
||||||
|
)
|
||||||
|
return execution, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareExecutionQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(row *sql.Row) (*Execution, error)) {
|
||||||
|
return sq.Select(
|
||||||
|
ExecutionColumnInstanceID.identifier(),
|
||||||
|
ExecutionColumnID.identifier(),
|
||||||
|
ExecutionColumnChangeDate.identifier(),
|
||||||
|
ExecutionColumnSequence.identifier(),
|
||||||
|
executionTargetsListCol.identifier(),
|
||||||
|
).From(executionTable.identifier()).
|
||||||
|
Join("(" + executionTargetsQuery + ") AS " + executionTargetsTableAlias.alias + " ON " +
|
||||||
|
ExecutionTargetsColumnInstanceID.identifier() + " = " + ExecutionColumnInstanceID.identifier() + " AND " +
|
||||||
|
ExecutionTargetsColumnExecutionID.identifier() + " = " + ExecutionColumnID.identifier(),
|
||||||
|
).
|
||||||
|
PlaceholderFormat(sq.Dollar),
|
||||||
|
scanExecution
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareExecutionsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(rows *sql.Rows) (*Executions, error)) {
|
func prepareExecutionsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(rows *sql.Rows) (*Executions, error)) {
|
||||||
return sq.Select(
|
return sq.Select(
|
||||||
|
ExecutionColumnInstanceID.identifier(),
|
||||||
ExecutionColumnID.identifier(),
|
ExecutionColumnID.identifier(),
|
||||||
ExecutionColumnChangeDate.identifier(),
|
ExecutionColumnChangeDate.identifier(),
|
||||||
ExecutionColumnResourceOwner.identifier(),
|
|
||||||
ExecutionColumnSequence.identifier(),
|
ExecutionColumnSequence.identifier(),
|
||||||
ExecutionColumnTargets.identifier(),
|
executionTargetsListCol.identifier(),
|
||||||
ExecutionColumnIncludes.identifier(),
|
|
||||||
countColumn.identifier(),
|
countColumn.identifier(),
|
||||||
).From(executionTable.identifier()).
|
).From(executionTable.identifier()).
|
||||||
|
Join("(" + executionTargetsQuery + ") AS " + executionTargetsTableAlias.alias + " ON " +
|
||||||
|
ExecutionTargetsColumnInstanceID.identifier() + " = " + ExecutionColumnInstanceID.identifier() + " AND " +
|
||||||
|
ExecutionTargetsColumnExecutionID.identifier() + " = " + ExecutionColumnID.identifier(),
|
||||||
|
).
|
||||||
PlaceholderFormat(sq.Dollar),
|
PlaceholderFormat(sq.Dollar),
|
||||||
func(rows *sql.Rows) (*Executions, error) {
|
scanExecutions
|
||||||
executions := make([]*Execution, 0)
|
}
|
||||||
var count uint64
|
|
||||||
for rows.Next() {
|
type executionTarget struct {
|
||||||
|
Position int `json:"position,omitempty"`
|
||||||
|
Include string `json:"include,omitempty"`
|
||||||
|
Target string `json:"target,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanExecution(row *sql.Row) (*Execution, error) {
|
||||||
execution := new(Execution)
|
execution := new(Execution)
|
||||||
err := rows.Scan(
|
targets := make([]byte, 0)
|
||||||
|
|
||||||
|
err := row.Scan(
|
||||||
|
&execution.ResourceOwner,
|
||||||
&execution.ID,
|
&execution.ID,
|
||||||
&execution.EventDate,
|
&execution.EventDate,
|
||||||
&execution.ResourceOwner,
|
|
||||||
&execution.Sequence,
|
&execution.Sequence,
|
||||||
&execution.Targets,
|
&targets,
|
||||||
&execution.Includes,
|
)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, zerrors.ThrowNotFound(err, "QUERY-qzn1xycesh", "Errors.Execution.NotFound")
|
||||||
|
}
|
||||||
|
return nil, zerrors.ThrowInternal(err, "QUERY-f8sjvm4tb8", "Errors.Internal")
|
||||||
|
}
|
||||||
|
|
||||||
|
executionTargets := make([]*executionTarget, 0)
|
||||||
|
if err := json.Unmarshal(targets, &executionTargets); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
execution.Targets = make([]*exec.Target, len(executionTargets))
|
||||||
|
for i := range executionTargets {
|
||||||
|
if executionTargets[i].Target != "" {
|
||||||
|
execution.Targets[i] = &exec.Target{Type: domain.ExecutionTargetTypeTarget, Target: executionTargets[i].Target}
|
||||||
|
}
|
||||||
|
if executionTargets[i].Include != "" {
|
||||||
|
execution.Targets[i] = &exec.Target{Type: domain.ExecutionTargetTypeInclude, Target: executionTargets[i].Include}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return execution, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func executionTargetsUnmarshal(data []byte) ([]*exec.Target, error) {
|
||||||
|
executionTargets := make([]*executionTarget, 0)
|
||||||
|
if err := json.Unmarshal(data, &executionTargets); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
targets := make([]*exec.Target, len(executionTargets))
|
||||||
|
// position starts with 1
|
||||||
|
for _, item := range executionTargets {
|
||||||
|
if item.Target != "" {
|
||||||
|
targets[item.Position-1] = &exec.Target{Type: domain.ExecutionTargetTypeTarget, Target: item.Target}
|
||||||
|
}
|
||||||
|
if item.Include != "" {
|
||||||
|
targets[item.Position-1] = &exec.Target{Type: domain.ExecutionTargetTypeInclude, Target: item.Include}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return targets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanExecutions(rows *sql.Rows) (*Executions, error) {
|
||||||
|
executions := make([]*Execution, 0)
|
||||||
|
var count uint64
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
execution := new(Execution)
|
||||||
|
targets := make([]byte, 0)
|
||||||
|
|
||||||
|
err := rows.Scan(
|
||||||
|
&execution.ResourceOwner,
|
||||||
|
&execution.ID,
|
||||||
|
&execution.EventDate,
|
||||||
|
&execution.Sequence,
|
||||||
|
&targets,
|
||||||
&count,
|
&count,
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, zerrors.ThrowNotFound(err, "QUERY-tbrmno85vp", "Errors.Execution.NotFound")
|
||||||
|
}
|
||||||
|
return nil, zerrors.ThrowInternal(err, "QUERY-tyw2ydsj84", "Errors.Internal")
|
||||||
|
}
|
||||||
|
|
||||||
|
execution.Targets, err = executionTargetsUnmarshal(targets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -148,7 +335,7 @@ func prepareExecutionsQuery(ctx context.Context, db prepareDatabase) (sq.SelectB
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := rows.Close(); err != nil {
|
if err := rows.Close(); err != nil {
|
||||||
return nil, zerrors.ThrowInternal(err, "QUERY-72xfx5jlj7", "Errors.Query.CloseRows")
|
return nil, zerrors.ThrowInternal(err, "QUERY-yhka3fs3mw", "Errors.Query.CloseRows")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Executions{
|
return &Executions{
|
||||||
@ -158,34 +345,79 @@ func prepareExecutionsQuery(ctx context.Context, db prepareDatabase) (sq.SelectB
|
|||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ExecutionTarget struct {
|
||||||
|
InstanceID string
|
||||||
|
ExecutionID string
|
||||||
|
TargetID string
|
||||||
|
TargetType domain.TargetType
|
||||||
|
Endpoint string
|
||||||
|
Timeout time.Duration
|
||||||
|
InterruptOnError bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareExecutionQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(row *sql.Row) (*Execution, error)) {
|
func (e *ExecutionTarget) GetExecutionID() string {
|
||||||
return sq.Select(
|
return e.ExecutionID
|
||||||
ExecutionColumnID.identifier(),
|
}
|
||||||
ExecutionColumnChangeDate.identifier(),
|
func (e *ExecutionTarget) GetTargetID() string {
|
||||||
ExecutionColumnResourceOwner.identifier(),
|
return e.TargetID
|
||||||
ExecutionColumnSequence.identifier(),
|
}
|
||||||
ExecutionColumnTargets.identifier(),
|
func (e *ExecutionTarget) IsInterruptOnError() bool {
|
||||||
ExecutionColumnIncludes.identifier(),
|
return e.InterruptOnError
|
||||||
).From(executionTable.identifier()).
|
}
|
||||||
PlaceholderFormat(sq.Dollar),
|
func (e *ExecutionTarget) GetEndpoint() string {
|
||||||
func(row *sql.Row) (*Execution, error) {
|
return e.Endpoint
|
||||||
execution := new(Execution)
|
}
|
||||||
err := row.Scan(
|
func (e *ExecutionTarget) GetTargetType() domain.TargetType {
|
||||||
&execution.ID,
|
return e.TargetType
|
||||||
&execution.EventDate,
|
}
|
||||||
&execution.ResourceOwner,
|
func (e *ExecutionTarget) GetTimeout() time.Duration {
|
||||||
&execution.Sequence,
|
return e.Timeout
|
||||||
&execution.Targets,
|
}
|
||||||
&execution.Includes,
|
|
||||||
|
func scanExecutionTargets(rows *sql.Rows) ([]*ExecutionTarget, error) {
|
||||||
|
targets := make([]*ExecutionTarget, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
target := new(ExecutionTarget)
|
||||||
|
|
||||||
|
var (
|
||||||
|
instanceID = &sql.NullString{}
|
||||||
|
executionID = &sql.NullString{}
|
||||||
|
targetID = &sql.NullString{}
|
||||||
|
targetType = &sql.NullInt32{}
|
||||||
|
endpoint = &sql.NullString{}
|
||||||
|
timeout = &sql.NullInt64{}
|
||||||
|
interruptOnError = &sql.NullBool{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
err := rows.Scan(
|
||||||
|
executionID,
|
||||||
|
instanceID,
|
||||||
|
targetID,
|
||||||
|
targetType,
|
||||||
|
endpoint,
|
||||||
|
timeout,
|
||||||
|
interruptOnError,
|
||||||
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
return nil, err
|
||||||
return nil, zerrors.ThrowNotFound(err, "QUERY-qzn1xycesh", "Errors.Execution.NotFound")
|
|
||||||
}
|
}
|
||||||
return nil, zerrors.ThrowInternal(err, "QUERY-f8sjvm4tb8", "Errors.Internal")
|
|
||||||
|
target.InstanceID = instanceID.String
|
||||||
|
target.ExecutionID = executionID.String
|
||||||
|
target.TargetID = targetID.String
|
||||||
|
target.TargetType = domain.TargetType(targetType.Int32)
|
||||||
|
target.Endpoint = endpoint.String
|
||||||
|
target.Timeout = time.Duration(timeout.Int64)
|
||||||
|
target.InterruptOnError = interruptOnError.Bool
|
||||||
|
|
||||||
|
targets = append(targets, target)
|
||||||
}
|
}
|
||||||
return execution, nil
|
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, zerrors.ThrowInternal(err, "QUERY-37ardr0pki", "Errors.Query.CloseRows")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return targets, nil
|
||||||
}
|
}
|
||||||
|
11
internal/query/execution_targets.sql
Normal file
11
internal/query/execution_targets.sql
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
SELECT instance_id,
|
||||||
|
execution_id,
|
||||||
|
JSONB_AGG(
|
||||||
|
JSON_OBJECT(
|
||||||
|
'position' : position,
|
||||||
|
'include' : include,
|
||||||
|
'target' : target_id
|
||||||
|
)
|
||||||
|
) as targets
|
||||||
|
FROM projections.executions1_targets
|
||||||
|
GROUP BY instance_id, execution_id
|
@ -8,44 +8,56 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/database"
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
exec "github.com/zitadel/zitadel/internal/repository/execution"
|
||||||
"github.com/zitadel/zitadel/internal/zerrors"
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
prepareExecutionsStmt = `SELECT projections.executions.id,` +
|
prepareExecutionsStmt = `SELECT projections.executions1.instance_id,` +
|
||||||
` projections.executions.change_date,` +
|
` projections.executions1.id,` +
|
||||||
` projections.executions.resource_owner,` +
|
` projections.executions1.change_date,` +
|
||||||
` projections.executions.sequence,` +
|
` projections.executions1.sequence,` +
|
||||||
` projections.executions.targets,` +
|
` execution_targets.targets,` +
|
||||||
` projections.executions.includes,` +
|
|
||||||
` COUNT(*) OVER ()` +
|
` COUNT(*) OVER ()` +
|
||||||
` FROM projections.executions`
|
` FROM projections.executions1` +
|
||||||
|
` JOIN (` +
|
||||||
|
`SELECT instance_id, execution_id, JSONB_AGG( JSON_OBJECT( 'position' : position, 'include' : include, 'target' : target_id ) ) as targets` +
|
||||||
|
` FROM projections.executions1_targets` +
|
||||||
|
` GROUP BY instance_id, execution_id` +
|
||||||
|
`)` +
|
||||||
|
` AS execution_targets` +
|
||||||
|
` ON execution_targets.instance_id = projections.executions1.instance_id` +
|
||||||
|
` AND execution_targets.execution_id = projections.executions1.id`
|
||||||
prepareExecutionsCols = []string{
|
prepareExecutionsCols = []string{
|
||||||
|
"instance_id",
|
||||||
"id",
|
"id",
|
||||||
"change_date",
|
"change_date",
|
||||||
"resource_owner",
|
|
||||||
"sequence",
|
"sequence",
|
||||||
"targets",
|
"targets",
|
||||||
"includes",
|
|
||||||
"count",
|
"count",
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareExecutionStmt = `SELECT projections.executions.id,` +
|
prepareExecutionStmt = `SELECT projections.executions1.instance_id,` +
|
||||||
` projections.executions.change_date,` +
|
` projections.executions1.id,` +
|
||||||
` projections.executions.resource_owner,` +
|
` projections.executions1.change_date,` +
|
||||||
` projections.executions.sequence,` +
|
` projections.executions1.sequence,` +
|
||||||
` projections.executions.targets,` +
|
` execution_targets.targets` +
|
||||||
` projections.executions.includes` +
|
` FROM projections.executions1` +
|
||||||
` FROM projections.executions`
|
` JOIN (` +
|
||||||
|
`SELECT instance_id, execution_id, JSONB_AGG( JSON_OBJECT( 'position' : position, 'include' : include, 'target' : target_id ) ) as targets` +
|
||||||
|
` FROM projections.executions1_targets` +
|
||||||
|
` GROUP BY instance_id, execution_id` +
|
||||||
|
`)` +
|
||||||
|
` AS execution_targets` +
|
||||||
|
` ON execution_targets.instance_id = projections.executions1.instance_id` +
|
||||||
|
` AND execution_targets.execution_id = projections.executions1.id`
|
||||||
prepareExecutionCols = []string{
|
prepareExecutionCols = []string{
|
||||||
|
"instance_id",
|
||||||
"id",
|
"id",
|
||||||
"change_date",
|
"change_date",
|
||||||
"resource_owner",
|
|
||||||
"sequence",
|
"sequence",
|
||||||
"targets",
|
"targets",
|
||||||
"includes",
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -81,12 +93,11 @@ func Test_ExecutionPrepares(t *testing.T) {
|
|||||||
prepareExecutionsCols,
|
prepareExecutionsCols,
|
||||||
[][]driver.Value{
|
[][]driver.Value{
|
||||||
{
|
{
|
||||||
|
"ro",
|
||||||
"id",
|
"id",
|
||||||
testNow,
|
testNow,
|
||||||
"ro",
|
|
||||||
uint64(20211109),
|
uint64(20211109),
|
||||||
database.TextArray[string]{"target"},
|
[]byte(`[{"position" : 1, "target" : "target"}, {"position" : 2, "include" : "include"}]`),
|
||||||
database.TextArray[string]{"include"},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -103,8 +114,10 @@ func Test_ExecutionPrepares(t *testing.T) {
|
|||||||
ResourceOwner: "ro",
|
ResourceOwner: "ro",
|
||||||
Sequence: 20211109,
|
Sequence: 20211109,
|
||||||
},
|
},
|
||||||
Targets: database.TextArray[string]{"target"},
|
Targets: []*exec.Target{
|
||||||
Includes: database.TextArray[string]{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -118,20 +131,18 @@ func Test_ExecutionPrepares(t *testing.T) {
|
|||||||
prepareExecutionsCols,
|
prepareExecutionsCols,
|
||||||
[][]driver.Value{
|
[][]driver.Value{
|
||||||
{
|
{
|
||||||
|
"ro",
|
||||||
"id-1",
|
"id-1",
|
||||||
testNow,
|
testNow,
|
||||||
"ro",
|
|
||||||
uint64(20211109),
|
uint64(20211109),
|
||||||
database.TextArray[string]{"target1"},
|
[]byte(`[{"position" : 1, "target" : "target"}, {"position" : 2, "include" : "include"}]`),
|
||||||
database.TextArray[string]{"include1"},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"ro",
|
||||||
"id-2",
|
"id-2",
|
||||||
testNow,
|
testNow,
|
||||||
"ro",
|
|
||||||
uint64(20211110),
|
uint64(20211110),
|
||||||
database.TextArray[string]{"target2"},
|
[]byte(`[{"position" : 2, "target" : "target"}, {"position" : 1, "include" : "include"}]`),
|
||||||
database.TextArray[string]{"include2"},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -148,8 +159,10 @@ func Test_ExecutionPrepares(t *testing.T) {
|
|||||||
ResourceOwner: "ro",
|
ResourceOwner: "ro",
|
||||||
Sequence: 20211109,
|
Sequence: 20211109,
|
||||||
},
|
},
|
||||||
Targets: database.TextArray[string]{"target1"},
|
Targets: []*exec.Target{
|
||||||
Includes: database.TextArray[string]{"include1"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "id-2",
|
ID: "id-2",
|
||||||
@ -158,8 +171,10 @@ func Test_ExecutionPrepares(t *testing.T) {
|
|||||||
ResourceOwner: "ro",
|
ResourceOwner: "ro",
|
||||||
Sequence: 20211110,
|
Sequence: 20211110,
|
||||||
},
|
},
|
||||||
Targets: database.TextArray[string]{"target2"},
|
Targets: []*exec.Target{
|
||||||
Includes: database.TextArray[string]{"include2"},
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -207,12 +222,11 @@ func Test_ExecutionPrepares(t *testing.T) {
|
|||||||
regexp.QuoteMeta(prepareExecutionStmt),
|
regexp.QuoteMeta(prepareExecutionStmt),
|
||||||
prepareExecutionCols,
|
prepareExecutionCols,
|
||||||
[]driver.Value{
|
[]driver.Value{
|
||||||
|
"ro",
|
||||||
"id",
|
"id",
|
||||||
testNow,
|
testNow,
|
||||||
"ro",
|
|
||||||
uint64(20211109),
|
uint64(20211109),
|
||||||
database.TextArray[string]{"target"},
|
[]byte(`[{"position" : 1, "target" : "target"}, {"position" : 2, "include" : "include"}]`),
|
||||||
database.TextArray[string]{"include"},
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -223,8 +237,10 @@ func Test_ExecutionPrepares(t *testing.T) {
|
|||||||
ResourceOwner: "ro",
|
ResourceOwner: "ro",
|
||||||
Sequence: 20211109,
|
Sequence: 20211109,
|
||||||
},
|
},
|
||||||
Targets: database.TextArray[string]{"target"},
|
Targets: []*exec.Target{
|
||||||
Includes: database.TextArray[string]{"include"},
|
{Type: domain.ExecutionTargetTypeTarget, Target: "target"},
|
||||||
|
{Type: domain.ExecutionTargetTypeInclude, Target: "include"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,7 @@ package projection
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
old_handler "github.com/zitadel/zitadel/internal/eventstore/handler"
|
old_handler "github.com/zitadel/zitadel/internal/eventstore/handler"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
||||||
@ -11,15 +12,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ExecutionTable = "projections.executions"
|
ExecutionTable = "projections.executions1"
|
||||||
ExecutionIDCol = "id"
|
ExecutionIDCol = "id"
|
||||||
ExecutionCreationDateCol = "creation_date"
|
ExecutionCreationDateCol = "creation_date"
|
||||||
ExecutionChangeDateCol = "change_date"
|
ExecutionChangeDateCol = "change_date"
|
||||||
ExecutionResourceOwnerCol = "resource_owner"
|
|
||||||
ExecutionInstanceIDCol = "instance_id"
|
ExecutionInstanceIDCol = "instance_id"
|
||||||
ExecutionSequenceCol = "sequence"
|
ExecutionSequenceCol = "sequence"
|
||||||
ExecutionTargetsCol = "targets"
|
|
||||||
ExecutionIncludesCol = "includes"
|
ExecutionTargetSuffix = "targets"
|
||||||
|
ExecutionTargetExecutionIDCol = "execution_id"
|
||||||
|
ExecutionTargetInstanceIDCol = "instance_id"
|
||||||
|
ExecutionTargetPositionCol = "position"
|
||||||
|
ExecutionTargetTargetIDCol = "target_id"
|
||||||
|
ExecutionTargetIncludeCol = "include"
|
||||||
)
|
)
|
||||||
|
|
||||||
type executionProjection struct{}
|
type executionProjection struct{}
|
||||||
@ -33,19 +38,28 @@ func (*executionProjection) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (*executionProjection) Init() *old_handler.Check {
|
func (*executionProjection) Init() *old_handler.Check {
|
||||||
return handler.NewTableCheck(
|
return handler.NewMultiTableCheck(
|
||||||
handler.NewTable([]*handler.InitColumn{
|
handler.NewTable([]*handler.InitColumn{
|
||||||
handler.NewColumn(ExecutionIDCol, handler.ColumnTypeText),
|
handler.NewColumn(ExecutionIDCol, handler.ColumnTypeText),
|
||||||
handler.NewColumn(ExecutionCreationDateCol, handler.ColumnTypeTimestamp),
|
handler.NewColumn(ExecutionCreationDateCol, handler.ColumnTypeTimestamp),
|
||||||
handler.NewColumn(ExecutionChangeDateCol, handler.ColumnTypeTimestamp),
|
handler.NewColumn(ExecutionChangeDateCol, handler.ColumnTypeTimestamp),
|
||||||
handler.NewColumn(ExecutionResourceOwnerCol, handler.ColumnTypeText),
|
|
||||||
handler.NewColumn(ExecutionInstanceIDCol, handler.ColumnTypeText),
|
|
||||||
handler.NewColumn(ExecutionSequenceCol, handler.ColumnTypeInt64),
|
handler.NewColumn(ExecutionSequenceCol, handler.ColumnTypeInt64),
|
||||||
handler.NewColumn(ExecutionTargetsCol, handler.ColumnTypeTextArray, handler.Nullable()),
|
handler.NewColumn(ExecutionInstanceIDCol, handler.ColumnTypeText),
|
||||||
handler.NewColumn(ExecutionIncludesCol, handler.ColumnTypeTextArray, handler.Nullable()),
|
|
||||||
},
|
},
|
||||||
handler.NewPrimaryKey(ExecutionInstanceIDCol, ExecutionIDCol),
|
handler.NewPrimaryKey(ExecutionInstanceIDCol, ExecutionIDCol),
|
||||||
),
|
),
|
||||||
|
handler.NewSuffixedTable([]*handler.InitColumn{
|
||||||
|
handler.NewColumn(ExecutionTargetInstanceIDCol, handler.ColumnTypeText),
|
||||||
|
handler.NewColumn(ExecutionTargetExecutionIDCol, handler.ColumnTypeText),
|
||||||
|
handler.NewColumn(ExecutionTargetPositionCol, handler.ColumnTypeInt64),
|
||||||
|
handler.NewColumn(ExecutionTargetIncludeCol, handler.ColumnTypeText, handler.Nullable()),
|
||||||
|
handler.NewColumn(ExecutionTargetTargetIDCol, handler.ColumnTypeText, handler.Nullable()),
|
||||||
|
},
|
||||||
|
handler.NewPrimaryKey(ExecutionTargetInstanceIDCol, ExecutionTargetExecutionIDCol, ExecutionTargetPositionCol),
|
||||||
|
ExecutionTargetSuffix,
|
||||||
|
handler.WithForeignKey(handler.NewForeignKey("execution", []string{ExecutionTargetInstanceIDCol, ExecutionTargetExecutionIDCol}, []string{ExecutionInstanceIDCol, ExecutionIDCol})),
|
||||||
|
handler.WithIndex(handler.NewIndex("execution", []string{ExecutionTargetInstanceIDCol, ExecutionTargetExecutionIDCol})),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +69,7 @@ func (p *executionProjection) Reducers() []handler.AggregateReducer {
|
|||||||
Aggregate: exec.AggregateType,
|
Aggregate: exec.AggregateType,
|
||||||
EventReducers: []handler.EventReducer{
|
EventReducers: []handler.EventReducer{
|
||||||
{
|
{
|
||||||
Event: exec.SetEventType,
|
Event: exec.SetEventV2Type,
|
||||||
Reduce: p.reduceExecutionSet,
|
Reduce: p.reduceExecutionSet,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -77,21 +91,65 @@ func (p *executionProjection) Reducers() []handler.AggregateReducer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *executionProjection) reduceExecutionSet(event eventstore.Event) (*handler.Statement, error) {
|
func (p *executionProjection) reduceExecutionSet(event eventstore.Event) (*handler.Statement, error) {
|
||||||
e, err := assertEvent[*exec.SetEvent](event)
|
e, err := assertEvent[*exec.SetEventV2](event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
columns := []handler.Column{
|
|
||||||
|
stmts := []func(eventstore.Event) handler.Exec{
|
||||||
|
handler.AddUpsertStatement(
|
||||||
|
[]handler.Column{
|
||||||
|
handler.NewCol(ExecutionInstanceIDCol, e.Aggregate().InstanceID),
|
||||||
|
handler.NewCol(ExecutionIDCol, e.Aggregate().ID),
|
||||||
|
},
|
||||||
|
[]handler.Column{
|
||||||
handler.NewCol(ExecutionInstanceIDCol, e.Aggregate().InstanceID),
|
handler.NewCol(ExecutionInstanceIDCol, e.Aggregate().InstanceID),
|
||||||
handler.NewCol(ExecutionIDCol, e.Aggregate().ID),
|
handler.NewCol(ExecutionIDCol, e.Aggregate().ID),
|
||||||
handler.NewCol(ExecutionResourceOwnerCol, e.Aggregate().ResourceOwner),
|
|
||||||
handler.NewCol(ExecutionCreationDateCol, handler.OnlySetValueOnInsert(ExecutionTable, e.CreationDate())),
|
handler.NewCol(ExecutionCreationDateCol, handler.OnlySetValueOnInsert(ExecutionTable, e.CreationDate())),
|
||||||
handler.NewCol(ExecutionChangeDateCol, e.CreationDate()),
|
handler.NewCol(ExecutionChangeDateCol, e.CreationDate()),
|
||||||
handler.NewCol(ExecutionSequenceCol, e.Sequence()),
|
handler.NewCol(ExecutionSequenceCol, e.Sequence()),
|
||||||
handler.NewCol(ExecutionTargetsCol, e.Targets),
|
},
|
||||||
handler.NewCol(ExecutionIncludesCol, e.Includes),
|
),
|
||||||
|
// cleanup execution targets to re-insert them
|
||||||
|
handler.AddDeleteStatement(
|
||||||
|
[]handler.Condition{
|
||||||
|
handler.NewCond(ExecutionTargetInstanceIDCol, e.Aggregate().InstanceID),
|
||||||
|
handler.NewCond(ExecutionTargetExecutionIDCol, e.Aggregate().ID),
|
||||||
|
},
|
||||||
|
handler.WithTableSuffix(ExecutionTargetSuffix),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
return handler.NewUpsertStatement(e, columns[0:2], columns), nil
|
|
||||||
|
if len(e.Targets) > 0 {
|
||||||
|
for i, target := range e.Targets {
|
||||||
|
var targetStr, includeStr string
|
||||||
|
switch target.Type {
|
||||||
|
case domain.ExecutionTargetTypeTarget:
|
||||||
|
targetStr = target.Target
|
||||||
|
case domain.ExecutionTargetTypeInclude:
|
||||||
|
includeStr = target.Target
|
||||||
|
case domain.ExecutionTargetTypeUnspecified:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
stmts = append(stmts,
|
||||||
|
handler.AddCreateStatement(
|
||||||
|
[]handler.Column{
|
||||||
|
handler.NewCol(ExecutionTargetInstanceIDCol, e.Aggregate().InstanceID),
|
||||||
|
handler.NewCol(ExecutionTargetExecutionIDCol, e.Aggregate().ID),
|
||||||
|
handler.NewCol(ExecutionTargetPositionCol, i+1),
|
||||||
|
handler.NewCol(ExecutionTargetIncludeCol, includeStr),
|
||||||
|
handler.NewCol(ExecutionTargetTargetIDCol, targetStr),
|
||||||
|
},
|
||||||
|
handler.WithTableSuffix(ExecutionTargetSuffix),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler.NewMultiStatement(e, stmts...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *executionProjection) reduceExecutionRemoved(event eventstore.Event) (*handler.Statement, error) {
|
func (p *executionProjection) reduceExecutionRemoved(event eventstore.Event) (*handler.Statement, error) {
|
||||||
@ -99,8 +157,8 @@ func (p *executionProjection) reduceExecutionRemoved(event eventstore.Event) (*h
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return handler.NewDeleteStatement(
|
|
||||||
e,
|
return handler.NewDeleteStatement(e,
|
||||||
[]handler.Condition{
|
[]handler.Condition{
|
||||||
handler.NewCond(ExecutionInstanceIDCol, e.Aggregate().InstanceID),
|
handler.NewCond(ExecutionInstanceIDCol, e.Aggregate().InstanceID),
|
||||||
handler.NewCond(ExecutionIDCol, e.Aggregate().ID),
|
handler.NewCond(ExecutionIDCol, e.Aggregate().ID),
|
||||||
|
@ -25,11 +25,11 @@ func TestExecutionProjection_reduces(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
event: getEvent(
|
event: getEvent(
|
||||||
testEvent(
|
testEvent(
|
||||||
exec.SetEventType,
|
exec.SetEventV2Type,
|
||||||
exec.AggregateType,
|
exec.AggregateType,
|
||||||
[]byte(`{"targets": ["target"], "includes": ["include"]}`),
|
[]byte(`{"targets": [{"type":2,"target":"target"},{"type":1,"target":"include"}]}`),
|
||||||
),
|
),
|
||||||
eventstore.GenericEventMapper[exec.SetEvent],
|
eventstore.GenericEventMapper[exec.SetEventV2],
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
reduce: (&executionProjection{}).reduceExecutionSet,
|
reduce: (&executionProjection{}).reduceExecutionSet,
|
||||||
@ -39,16 +39,40 @@ func TestExecutionProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.executions (instance_id, id, resource_owner, creation_date, change_date, sequence, targets, includes) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (instance_id, id) DO UPDATE SET (resource_owner, creation_date, change_date, sequence, targets, includes) = (EXCLUDED.resource_owner, projections.executions.creation_date, EXCLUDED.change_date, EXCLUDED.sequence, EXCLUDED.targets, EXCLUDED.includes)",
|
expectedStmt: "INSERT INTO projections.executions1 (instance_id, id, creation_date, change_date, sequence) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (instance_id, id) DO UPDATE SET (creation_date, change_date, sequence) = (projections.executions1.creation_date, EXCLUDED.change_date, EXCLUDED.sequence)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"instance-id",
|
"instance-id",
|
||||||
"agg-id",
|
"agg-id",
|
||||||
"ro-id",
|
|
||||||
anyArg{},
|
anyArg{},
|
||||||
anyArg{},
|
anyArg{},
|
||||||
uint64(15),
|
uint64(15),
|
||||||
[]string{"target"},
|
},
|
||||||
[]string{"include"},
|
},
|
||||||
|
{
|
||||||
|
expectedStmt: "DELETE FROM projections.executions1_targets WHERE (instance_id = $1) AND (execution_id = $2)",
|
||||||
|
expectedArgs: []interface{}{
|
||||||
|
"instance-id",
|
||||||
|
"agg-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expectedStmt: "INSERT INTO projections.executions1_targets (instance_id, execution_id, position, include, target_id) VALUES ($1, $2, $3, $4, $5)",
|
||||||
|
expectedArgs: []interface{}{
|
||||||
|
"instance-id",
|
||||||
|
"agg-id",
|
||||||
|
1,
|
||||||
|
"",
|
||||||
|
"target",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expectedStmt: "INSERT INTO projections.executions1_targets (instance_id, execution_id, position, include, target_id) VALUES ($1, $2, $3, $4, $5)",
|
||||||
|
expectedArgs: []interface{}{
|
||||||
|
"instance-id",
|
||||||
|
"agg-id",
|
||||||
|
2,
|
||||||
|
"include",
|
||||||
|
"",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -74,7 +98,7 @@ func TestExecutionProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "DELETE FROM projections.executions WHERE (instance_id = $1) AND (id = $2)",
|
expectedStmt: "DELETE FROM projections.executions1 WHERE (instance_id = $1) AND (id = $2)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"instance-id",
|
"instance-id",
|
||||||
"agg-id",
|
"agg-id",
|
||||||
@ -103,7 +127,7 @@ func TestExecutionProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "DELETE FROM projections.executions WHERE (instance_id = $1)",
|
expectedStmt: "DELETE FROM projections.executions1 WHERE (instance_id = $1)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"agg-id",
|
"agg-id",
|
||||||
},
|
},
|
||||||
|
@ -269,8 +269,8 @@ func newProjectionsList() {
|
|||||||
RestrictionsProjection,
|
RestrictionsProjection,
|
||||||
SystemFeatureProjection,
|
SystemFeatureProjection,
|
||||||
InstanceFeatureProjection,
|
InstanceFeatureProjection,
|
||||||
ExecutionProjection,
|
|
||||||
TargetProjection,
|
TargetProjection,
|
||||||
|
ExecutionProjection,
|
||||||
UserSchemaProjection,
|
UserSchemaProjection,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TargetTable = "projections.targets"
|
TargetTable = "projections.targets1"
|
||||||
TargetIDCol = "id"
|
TargetIDCol = "id"
|
||||||
TargetCreationDateCol = "creation_date"
|
TargetCreationDateCol = "creation_date"
|
||||||
TargetChangeDateCol = "change_date"
|
TargetChangeDateCol = "change_date"
|
||||||
@ -20,9 +20,8 @@ const (
|
|||||||
TargetSequenceCol = "sequence"
|
TargetSequenceCol = "sequence"
|
||||||
TargetNameCol = "name"
|
TargetNameCol = "name"
|
||||||
TargetTargetType = "target_type"
|
TargetTargetType = "target_type"
|
||||||
TargetURLCol = "url"
|
TargetEndpointCol = "endpoint"
|
||||||
TargetTimeoutCol = "timeout"
|
TargetTimeoutCol = "timeout"
|
||||||
TargetAsyncCol = "async"
|
|
||||||
TargetInterruptOnErrorCol = "interrupt_on_error"
|
TargetInterruptOnErrorCol = "interrupt_on_error"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -47,10 +46,9 @@ func (*targetProjection) Init() *old_handler.Check {
|
|||||||
handler.NewColumn(TargetTargetType, handler.ColumnTypeEnum),
|
handler.NewColumn(TargetTargetType, handler.ColumnTypeEnum),
|
||||||
handler.NewColumn(TargetSequenceCol, handler.ColumnTypeInt64),
|
handler.NewColumn(TargetSequenceCol, handler.ColumnTypeInt64),
|
||||||
handler.NewColumn(TargetNameCol, handler.ColumnTypeText),
|
handler.NewColumn(TargetNameCol, handler.ColumnTypeText),
|
||||||
handler.NewColumn(TargetURLCol, handler.ColumnTypeText, handler.Default("")),
|
handler.NewColumn(TargetEndpointCol, handler.ColumnTypeText),
|
||||||
handler.NewColumn(TargetTimeoutCol, handler.ColumnTypeInt64, handler.Default(0)),
|
handler.NewColumn(TargetTimeoutCol, handler.ColumnTypeInt64),
|
||||||
handler.NewColumn(TargetAsyncCol, handler.ColumnTypeBool, handler.Default(false)),
|
handler.NewColumn(TargetInterruptOnErrorCol, handler.ColumnTypeBool),
|
||||||
handler.NewColumn(TargetInterruptOnErrorCol, handler.ColumnTypeBool, handler.Default(false)),
|
|
||||||
},
|
},
|
||||||
handler.NewPrimaryKey(TargetInstanceIDCol, TargetIDCol),
|
handler.NewPrimaryKey(TargetInstanceIDCol, TargetIDCol),
|
||||||
),
|
),
|
||||||
@ -103,10 +101,9 @@ func (p *targetProjection) reduceTargetAdded(event eventstore.Event) (*handler.S
|
|||||||
handler.NewCol(TargetChangeDateCol, e.CreationDate()),
|
handler.NewCol(TargetChangeDateCol, e.CreationDate()),
|
||||||
handler.NewCol(TargetSequenceCol, e.Sequence()),
|
handler.NewCol(TargetSequenceCol, e.Sequence()),
|
||||||
handler.NewCol(TargetNameCol, e.Name),
|
handler.NewCol(TargetNameCol, e.Name),
|
||||||
handler.NewCol(TargetURLCol, e.URL),
|
handler.NewCol(TargetEndpointCol, e.Endpoint),
|
||||||
handler.NewCol(TargetTargetType, e.TargetType),
|
handler.NewCol(TargetTargetType, e.TargetType),
|
||||||
handler.NewCol(TargetTimeoutCol, e.Timeout),
|
handler.NewCol(TargetTimeoutCol, e.Timeout),
|
||||||
handler.NewCol(TargetAsyncCol, e.Async),
|
|
||||||
handler.NewCol(TargetInterruptOnErrorCol, e.InterruptOnError),
|
handler.NewCol(TargetInterruptOnErrorCol, e.InterruptOnError),
|
||||||
},
|
},
|
||||||
), nil
|
), nil
|
||||||
@ -128,15 +125,12 @@ func (p *targetProjection) reduceTargetChanged(event eventstore.Event) (*handler
|
|||||||
if e.TargetType != nil {
|
if e.TargetType != nil {
|
||||||
values = append(values, handler.NewCol(TargetTargetType, *e.TargetType))
|
values = append(values, handler.NewCol(TargetTargetType, *e.TargetType))
|
||||||
}
|
}
|
||||||
if e.URL != nil {
|
if e.Endpoint != nil {
|
||||||
values = append(values, handler.NewCol(TargetURLCol, *e.URL))
|
values = append(values, handler.NewCol(TargetEndpointCol, *e.Endpoint))
|
||||||
}
|
}
|
||||||
if e.Timeout != nil {
|
if e.Timeout != nil {
|
||||||
values = append(values, handler.NewCol(TargetTimeoutCol, *e.Timeout))
|
values = append(values, handler.NewCol(TargetTimeoutCol, *e.Timeout))
|
||||||
}
|
}
|
||||||
if e.Async != nil {
|
|
||||||
values = append(values, handler.NewCol(TargetAsyncCol, *e.Async))
|
|
||||||
}
|
|
||||||
if e.InterruptOnError != nil {
|
if e.InterruptOnError != nil {
|
||||||
values = append(values, handler.NewCol(TargetInterruptOnErrorCol, *e.InterruptOnError))
|
values = append(values, handler.NewCol(TargetInterruptOnErrorCol, *e.InterruptOnError))
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ func TestTargetProjection_reduces(t *testing.T) {
|
|||||||
testEvent(
|
testEvent(
|
||||||
target.AddedEventType,
|
target.AddedEventType,
|
||||||
target.AggregateType,
|
target.AggregateType,
|
||||||
[]byte(`{"name": "name", "targetType":0, "url":"https://example.com", "timeout": 3000000000, "async": true, "interruptOnError": true}`),
|
[]byte(`{"name": "name", "targetType":0, "endpoint":"https://example.com", "timeout": 3000000000, "async": true, "interruptOnError": true}`),
|
||||||
),
|
),
|
||||||
eventstore.GenericEventMapper[target.AddedEvent],
|
eventstore.GenericEventMapper[target.AddedEvent],
|
||||||
),
|
),
|
||||||
@ -41,7 +41,7 @@ func TestTargetProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.targets (instance_id, resource_owner, id, creation_date, change_date, sequence, name, url, target_type, timeout, async, interrupt_on_error) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)",
|
expectedStmt: "INSERT INTO projections.targets1 (instance_id, resource_owner, id, creation_date, change_date, sequence, name, endpoint, target_type, timeout, interrupt_on_error) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"instance-id",
|
"instance-id",
|
||||||
"ro-id",
|
"ro-id",
|
||||||
@ -54,7 +54,6 @@ func TestTargetProjection_reduces(t *testing.T) {
|
|||||||
domain.TargetTypeWebhook,
|
domain.TargetTypeWebhook,
|
||||||
3 * time.Second,
|
3 * time.Second,
|
||||||
true,
|
true,
|
||||||
true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -68,7 +67,7 @@ func TestTargetProjection_reduces(t *testing.T) {
|
|||||||
testEvent(
|
testEvent(
|
||||||
target.ChangedEventType,
|
target.ChangedEventType,
|
||||||
target.AggregateType,
|
target.AggregateType,
|
||||||
[]byte(`{"name": "name2", "targetType":0, "url":"https://example.com", "timeout": 3000000000, "async": true, "interruptOnError": true}`),
|
[]byte(`{"name": "name2", "targetType":0, "endpoint":"https://example.com", "timeout": 3000000000, "async": true, "interruptOnError": true}`),
|
||||||
),
|
),
|
||||||
eventstore.GenericEventMapper[target.ChangedEvent],
|
eventstore.GenericEventMapper[target.ChangedEvent],
|
||||||
),
|
),
|
||||||
@ -80,7 +79,7 @@ func TestTargetProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.targets SET (change_date, sequence, resource_owner, name, target_type, url, timeout, async, interrupt_on_error) = ($1, $2, $3, $4, $5, $6, $7, $8, $9) WHERE (instance_id = $10) AND (id = $11)",
|
expectedStmt: "UPDATE projections.targets1 SET (change_date, sequence, resource_owner, name, target_type, endpoint, timeout, interrupt_on_error) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (instance_id = $9) AND (id = $10)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
uint64(15),
|
uint64(15),
|
||||||
@ -90,7 +89,6 @@ func TestTargetProjection_reduces(t *testing.T) {
|
|||||||
"https://example.com",
|
"https://example.com",
|
||||||
3 * time.Second,
|
3 * time.Second,
|
||||||
true,
|
true,
|
||||||
true,
|
|
||||||
"instance-id",
|
"instance-id",
|
||||||
"agg-id",
|
"agg-id",
|
||||||
},
|
},
|
||||||
@ -118,7 +116,7 @@ func TestTargetProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "DELETE FROM projections.targets WHERE (instance_id = $1) AND (id = $2)",
|
expectedStmt: "DELETE FROM projections.targets1 WHERE (instance_id = $1) AND (id = $2)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"instance-id",
|
"instance-id",
|
||||||
"agg-id",
|
"agg-id",
|
||||||
@ -147,7 +145,7 @@ func TestTargetProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "DELETE FROM projections.targets WHERE (instance_id = $1)",
|
expectedStmt: "DELETE FROM projections.targets1 WHERE (instance_id = $1)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"agg-id",
|
"agg-id",
|
||||||
},
|
},
|
||||||
|
@ -478,31 +478,31 @@ func (q *SubSelect) comp() sq.Sqlizer {
|
|||||||
return selectQuery
|
return selectQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListQuery struct {
|
type listQuery struct {
|
||||||
Column Column
|
Column Column
|
||||||
Data interface{}
|
Data interface{}
|
||||||
Compare ListComparison
|
Compare ListComparison
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewListQuery(column Column, value interface{}, compare ListComparison) (*ListQuery, error) {
|
func NewListQuery(column Column, value interface{}, compare ListComparison) (*listQuery, error) {
|
||||||
if compare < 0 || compare >= listCompareMax {
|
if compare < 0 || compare >= listCompareMax {
|
||||||
return nil, ErrInvalidCompare
|
return nil, ErrInvalidCompare
|
||||||
}
|
}
|
||||||
if column.isZero() {
|
if column.isZero() {
|
||||||
return nil, ErrMissingColumn
|
return nil, ErrMissingColumn
|
||||||
}
|
}
|
||||||
return &ListQuery{
|
return &listQuery{
|
||||||
Column: column,
|
Column: column,
|
||||||
Data: value,
|
Data: value,
|
||||||
Compare: compare,
|
Compare: compare,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *ListQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
func (q *listQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
||||||
return query.Where(q.comp())
|
return query.Where(q.comp())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *ListQuery) comp() sq.Sqlizer {
|
func (q *listQuery) comp() sq.Sqlizer {
|
||||||
if q.Compare != ListIn {
|
if q.Compare != ListIn {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -517,7 +517,7 @@ func (q *ListQuery) comp() sq.Sqlizer {
|
|||||||
return sq.Eq{q.Column.identifier(): q.Data}
|
return sq.Eq{q.Column.identifier(): q.Data}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *ListQuery) Col() Column {
|
func (q *listQuery) Col() Column {
|
||||||
return q.Column
|
return q.Column
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -720,6 +720,25 @@ type listContains struct {
|
|||||||
args interface{}
|
args interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewListContains(c Column, value interface{}) (*listContains, error) {
|
||||||
|
return &listContains{
|
||||||
|
col: c,
|
||||||
|
args: value,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *listContains) Col() Column {
|
||||||
|
return q.col
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *listContains) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
||||||
|
return query.Where(q.comp())
|
||||||
|
}
|
||||||
|
|
||||||
func (q *listContains) ToSql() (string, []interface{}, error) {
|
func (q *listContains) ToSql() (string, []interface{}, error) {
|
||||||
return q.col.identifier() + " @> ? ", []interface{}{q.args}, nil
|
return q.col.identifier() + " @> ? ", []interface{}{q.args}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *listContains) comp() sq.Sqlizer {
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
@ -521,7 +521,7 @@ func TestNewListQuery(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
want *ListQuery
|
want *listQuery
|
||||||
wantErr func(error) bool
|
wantErr func(error) bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@ -575,7 +575,7 @@ func TestNewListQuery(t *testing.T) {
|
|||||||
data: []interface{}{"hurst"},
|
data: []interface{}{"hurst"},
|
||||||
compare: ListIn,
|
compare: ListIn,
|
||||||
},
|
},
|
||||||
want: &ListQuery{
|
want: &listQuery{
|
||||||
Column: testCol,
|
Column: testCol,
|
||||||
Data: []interface{}{"hurst"},
|
Data: []interface{}{"hurst"},
|
||||||
Compare: ListIn,
|
Compare: ListIn,
|
||||||
@ -588,7 +588,7 @@ func TestNewListQuery(t *testing.T) {
|
|||||||
data: &SubSelect{Column: testCol, Queries: []SearchQuery{&textQuery{testCol, "horst1", TextEquals}}},
|
data: &SubSelect{Column: testCol, Queries: []SearchQuery{&textQuery{testCol, "horst1", TextEquals}}},
|
||||||
compare: ListIn,
|
compare: ListIn,
|
||||||
},
|
},
|
||||||
want: &ListQuery{
|
want: &listQuery{
|
||||||
Column: testCol,
|
Column: testCol,
|
||||||
Data: &SubSelect{Column: testCol, Queries: []SearchQuery{&textQuery{testCol, "horst1", TextEquals}}},
|
Data: &SubSelect{Column: testCol, Queries: []SearchQuery{&textQuery{testCol, "horst1", TextEquals}}},
|
||||||
Compare: ListIn,
|
Compare: ListIn,
|
||||||
@ -751,7 +751,7 @@ func TestListQuery_comp(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
s := &ListQuery{
|
s := &listQuery{
|
||||||
Column: tt.fields.Column,
|
Column: tt.fields.Column,
|
||||||
Data: tt.fields.Data,
|
Data: tt.fields.Data,
|
||||||
Compare: tt.fields.Compare,
|
Compare: tt.fields.Compare,
|
||||||
|
@ -52,17 +52,13 @@ var (
|
|||||||
table: targetTable,
|
table: targetTable,
|
||||||
}
|
}
|
||||||
TargetColumnURL = Column{
|
TargetColumnURL = Column{
|
||||||
name: projection.TargetURLCol,
|
name: projection.TargetEndpointCol,
|
||||||
table: targetTable,
|
table: targetTable,
|
||||||
}
|
}
|
||||||
TargetColumnTimeout = Column{
|
TargetColumnTimeout = Column{
|
||||||
name: projection.TargetTimeoutCol,
|
name: projection.TargetTimeoutCol,
|
||||||
table: targetTable,
|
table: targetTable,
|
||||||
}
|
}
|
||||||
TargetColumnAsync = Column{
|
|
||||||
name: projection.TargetAsyncCol,
|
|
||||||
table: targetTable,
|
|
||||||
}
|
|
||||||
TargetColumnInterruptOnError = Column{
|
TargetColumnInterruptOnError = Column{
|
||||||
name: projection.TargetInterruptOnErrorCol,
|
name: projection.TargetInterruptOnErrorCol,
|
||||||
table: targetTable,
|
table: targetTable,
|
||||||
@ -84,9 +80,8 @@ type Target struct {
|
|||||||
|
|
||||||
Name string
|
Name string
|
||||||
TargetType domain.TargetType
|
TargetType domain.TargetType
|
||||||
URL string
|
Endpoint string
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
Async bool
|
|
||||||
InterruptOnError bool
|
InterruptOnError bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +133,6 @@ func prepareTargetsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuil
|
|||||||
TargetColumnTargetType.identifier(),
|
TargetColumnTargetType.identifier(),
|
||||||
TargetColumnTimeout.identifier(),
|
TargetColumnTimeout.identifier(),
|
||||||
TargetColumnURL.identifier(),
|
TargetColumnURL.identifier(),
|
||||||
TargetColumnAsync.identifier(),
|
|
||||||
TargetColumnInterruptOnError.identifier(),
|
TargetColumnInterruptOnError.identifier(),
|
||||||
countColumn.identifier(),
|
countColumn.identifier(),
|
||||||
).From(targetTable.identifier()).
|
).From(targetTable.identifier()).
|
||||||
@ -156,8 +150,7 @@ func prepareTargetsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuil
|
|||||||
&target.Name,
|
&target.Name,
|
||||||
&target.TargetType,
|
&target.TargetType,
|
||||||
&target.Timeout,
|
&target.Timeout,
|
||||||
&target.URL,
|
&target.Endpoint,
|
||||||
&target.Async,
|
|
||||||
&target.InterruptOnError,
|
&target.InterruptOnError,
|
||||||
&count,
|
&count,
|
||||||
)
|
)
|
||||||
@ -190,7 +183,6 @@ func prepareTargetQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuild
|
|||||||
TargetColumnTargetType.identifier(),
|
TargetColumnTargetType.identifier(),
|
||||||
TargetColumnTimeout.identifier(),
|
TargetColumnTimeout.identifier(),
|
||||||
TargetColumnURL.identifier(),
|
TargetColumnURL.identifier(),
|
||||||
TargetColumnAsync.identifier(),
|
|
||||||
TargetColumnInterruptOnError.identifier(),
|
TargetColumnInterruptOnError.identifier(),
|
||||||
).From(targetTable.identifier()).
|
).From(targetTable.identifier()).
|
||||||
PlaceholderFormat(sq.Dollar),
|
PlaceholderFormat(sq.Dollar),
|
||||||
@ -204,8 +196,7 @@ func prepareTargetQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuild
|
|||||||
&target.Name,
|
&target.Name,
|
||||||
&target.TargetType,
|
&target.TargetType,
|
||||||
&target.Timeout,
|
&target.Timeout,
|
||||||
&target.URL,
|
&target.Endpoint,
|
||||||
&target.Async,
|
|
||||||
&target.InterruptOnError,
|
&target.InterruptOnError,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -14,18 +14,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
prepareTargetsStmt = `SELECT projections.targets.id,` +
|
prepareTargetsStmt = `SELECT projections.targets1.id,` +
|
||||||
` projections.targets.change_date,` +
|
` projections.targets1.change_date,` +
|
||||||
` projections.targets.resource_owner,` +
|
` projections.targets1.resource_owner,` +
|
||||||
` projections.targets.sequence,` +
|
` projections.targets1.sequence,` +
|
||||||
` projections.targets.name,` +
|
` projections.targets1.name,` +
|
||||||
` projections.targets.target_type,` +
|
` projections.targets1.target_type,` +
|
||||||
` projections.targets.timeout,` +
|
` projections.targets1.timeout,` +
|
||||||
` projections.targets.url,` +
|
` projections.targets1.endpoint,` +
|
||||||
` projections.targets.async,` +
|
` projections.targets1.interrupt_on_error,` +
|
||||||
` projections.targets.interrupt_on_error,` +
|
|
||||||
` COUNT(*) OVER ()` +
|
` COUNT(*) OVER ()` +
|
||||||
` FROM projections.targets`
|
` FROM projections.targets1`
|
||||||
prepareTargetsCols = []string{
|
prepareTargetsCols = []string{
|
||||||
"id",
|
"id",
|
||||||
"change_date",
|
"change_date",
|
||||||
@ -34,23 +33,21 @@ var (
|
|||||||
"name",
|
"name",
|
||||||
"target_type",
|
"target_type",
|
||||||
"timeout",
|
"timeout",
|
||||||
"url",
|
"endpoint",
|
||||||
"async",
|
|
||||||
"interrupt_on_error",
|
"interrupt_on_error",
|
||||||
"count",
|
"count",
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareTargetStmt = `SELECT projections.targets.id,` +
|
prepareTargetStmt = `SELECT projections.targets1.id,` +
|
||||||
` projections.targets.change_date,` +
|
` projections.targets1.change_date,` +
|
||||||
` projections.targets.resource_owner,` +
|
` projections.targets1.resource_owner,` +
|
||||||
` projections.targets.sequence,` +
|
` projections.targets1.sequence,` +
|
||||||
` projections.targets.name,` +
|
` projections.targets1.name,` +
|
||||||
` projections.targets.target_type,` +
|
` projections.targets1.target_type,` +
|
||||||
` projections.targets.timeout,` +
|
` projections.targets1.timeout,` +
|
||||||
` projections.targets.url,` +
|
` projections.targets1.endpoint,` +
|
||||||
` projections.targets.async,` +
|
` projections.targets1.interrupt_on_error` +
|
||||||
` projections.targets.interrupt_on_error` +
|
` FROM projections.targets1`
|
||||||
` FROM projections.targets`
|
|
||||||
prepareTargetCols = []string{
|
prepareTargetCols = []string{
|
||||||
"id",
|
"id",
|
||||||
"change_date",
|
"change_date",
|
||||||
@ -59,8 +56,7 @@ var (
|
|||||||
"name",
|
"name",
|
||||||
"target_type",
|
"target_type",
|
||||||
"timeout",
|
"timeout",
|
||||||
"url",
|
"endpoint",
|
||||||
"async",
|
|
||||||
"interrupt_on_error",
|
"interrupt_on_error",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -106,7 +102,6 @@ func Test_TargetPrepares(t *testing.T) {
|
|||||||
1 * time.Second,
|
1 * time.Second,
|
||||||
"https://example.com",
|
"https://example.com",
|
||||||
true,
|
true,
|
||||||
true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -126,8 +121,7 @@ func Test_TargetPrepares(t *testing.T) {
|
|||||||
Name: "target-name",
|
Name: "target-name",
|
||||||
TargetType: domain.TargetTypeWebhook,
|
TargetType: domain.TargetTypeWebhook,
|
||||||
Timeout: 1 * time.Second,
|
Timeout: 1 * time.Second,
|
||||||
URL: "https://example.com",
|
Endpoint: "https://example.com",
|
||||||
Async: true,
|
|
||||||
InterruptOnError: true,
|
InterruptOnError: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -151,7 +145,6 @@ func Test_TargetPrepares(t *testing.T) {
|
|||||||
1 * time.Second,
|
1 * time.Second,
|
||||||
"https://example.com",
|
"https://example.com",
|
||||||
true,
|
true,
|
||||||
false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id-2",
|
"id-2",
|
||||||
@ -163,14 +156,24 @@ func Test_TargetPrepares(t *testing.T) {
|
|||||||
1 * time.Second,
|
1 * time.Second,
|
||||||
"https://example.com",
|
"https://example.com",
|
||||||
false,
|
false,
|
||||||
true,
|
},
|
||||||
|
{
|
||||||
|
"id-3",
|
||||||
|
testNow,
|
||||||
|
"ro",
|
||||||
|
uint64(20211110),
|
||||||
|
"target-name3",
|
||||||
|
domain.TargetTypeAsync,
|
||||||
|
1 * time.Second,
|
||||||
|
"https://example.com",
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
object: &Targets{
|
object: &Targets{
|
||||||
SearchResponse: SearchResponse{
|
SearchResponse: SearchResponse{
|
||||||
Count: 2,
|
Count: 3,
|
||||||
},
|
},
|
||||||
Targets: []*Target{
|
Targets: []*Target{
|
||||||
{
|
{
|
||||||
@ -183,9 +186,8 @@ func Test_TargetPrepares(t *testing.T) {
|
|||||||
Name: "target-name1",
|
Name: "target-name1",
|
||||||
TargetType: domain.TargetTypeWebhook,
|
TargetType: domain.TargetTypeWebhook,
|
||||||
Timeout: 1 * time.Second,
|
Timeout: 1 * time.Second,
|
||||||
URL: "https://example.com",
|
Endpoint: "https://example.com",
|
||||||
Async: true,
|
InterruptOnError: true,
|
||||||
InterruptOnError: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "id-2",
|
ID: "id-2",
|
||||||
@ -197,9 +199,21 @@ func Test_TargetPrepares(t *testing.T) {
|
|||||||
Name: "target-name2",
|
Name: "target-name2",
|
||||||
TargetType: domain.TargetTypeWebhook,
|
TargetType: domain.TargetTypeWebhook,
|
||||||
Timeout: 1 * time.Second,
|
Timeout: 1 * time.Second,
|
||||||
URL: "https://example.com",
|
Endpoint: "https://example.com",
|
||||||
Async: false,
|
InterruptOnError: false,
|
||||||
InterruptOnError: true,
|
},
|
||||||
|
{
|
||||||
|
ID: "id-3",
|
||||||
|
ObjectDetails: domain.ObjectDetails{
|
||||||
|
EventDate: testNow,
|
||||||
|
ResourceOwner: "ro",
|
||||||
|
Sequence: 20211110,
|
||||||
|
},
|
||||||
|
Name: "target-name3",
|
||||||
|
TargetType: domain.TargetTypeAsync,
|
||||||
|
Timeout: 1 * time.Second,
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
InterruptOnError: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -256,7 +270,6 @@ func Test_TargetPrepares(t *testing.T) {
|
|||||||
1 * time.Second,
|
1 * time.Second,
|
||||||
"https://example.com",
|
"https://example.com",
|
||||||
true,
|
true,
|
||||||
false,
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -270,9 +283,8 @@ func Test_TargetPrepares(t *testing.T) {
|
|||||||
Name: "target-name",
|
Name: "target-name",
|
||||||
TargetType: domain.TargetTypeWebhook,
|
TargetType: domain.TargetTypeWebhook,
|
||||||
Timeout: 1 * time.Second,
|
Timeout: 1 * time.Second,
|
||||||
URL: "https://example.com",
|
Endpoint: "https://example.com",
|
||||||
Async: true,
|
InterruptOnError: true,
|
||||||
InterruptOnError: false,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
40
internal/query/targets_by_execution_id.sql
Normal file
40
internal/query/targets_by_execution_id.sql
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
WITH RECURSIVE
|
||||||
|
dissolved_execution_targets(execution_id, instance_id, position, "include", "target_id")
|
||||||
|
AS (SELECT execution_id
|
||||||
|
, instance_id
|
||||||
|
, ARRAY [position]
|
||||||
|
, "include"
|
||||||
|
, "target_id"
|
||||||
|
FROM matched_targets_and_includes
|
||||||
|
UNION ALL
|
||||||
|
SELECT e.execution_id
|
||||||
|
, p.instance_id
|
||||||
|
, e.position || p.position
|
||||||
|
, p."include"
|
||||||
|
, p."target_id"
|
||||||
|
FROM dissolved_execution_targets e
|
||||||
|
JOIN projections.executions1_targets p
|
||||||
|
ON e.instance_id = p.instance_id
|
||||||
|
AND e.include IS NOT NULL
|
||||||
|
AND e.include = p.execution_id),
|
||||||
|
matched AS (SELECT *
|
||||||
|
FROM projections.executions1
|
||||||
|
WHERE instance_id = $1
|
||||||
|
AND id = ANY($2)
|
||||||
|
ORDER BY id DESC
|
||||||
|
LIMIT 1),
|
||||||
|
matched_targets_and_includes AS (SELECT pos.*
|
||||||
|
FROM matched m
|
||||||
|
JOIN
|
||||||
|
projections.executions1_targets pos
|
||||||
|
ON m.id = pos.execution_id
|
||||||
|
AND m.instance_id = pos.instance_id
|
||||||
|
ORDER BY execution_id,
|
||||||
|
position)
|
||||||
|
select e.execution_id, e.instance_id, e.target_id, t.target_type, t.endpoint, t.timeout, t.interrupt_on_error
|
||||||
|
FROM dissolved_execution_targets e
|
||||||
|
JOIN projections.targets1 t
|
||||||
|
ON e.instance_id = t.instance_id
|
||||||
|
AND e.target_id = t.id
|
||||||
|
WHERE "include" = ''
|
||||||
|
ORDER BY position DESC;
|
47
internal/query/targets_by_execution_ids.sql
Normal file
47
internal/query/targets_by_execution_ids.sql
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
WITH RECURSIVE
|
||||||
|
dissolved_execution_targets(execution_id, instance_id, position, "include", "target_id")
|
||||||
|
AS (SELECT execution_id
|
||||||
|
, instance_id
|
||||||
|
, ARRAY [position]
|
||||||
|
, "include"
|
||||||
|
, "target_id"
|
||||||
|
FROM matched_targets_and_includes
|
||||||
|
UNION ALL
|
||||||
|
SELECT e.execution_id
|
||||||
|
, p.instance_id
|
||||||
|
, e.position || p.position
|
||||||
|
, p."include"
|
||||||
|
, p."target_id"
|
||||||
|
FROM dissolved_execution_targets e
|
||||||
|
JOIN projections.executions1_targets p
|
||||||
|
ON e.instance_id = p.instance_id
|
||||||
|
AND e.include IS NOT NULL
|
||||||
|
AND e.include = p.execution_id),
|
||||||
|
matched AS ((SELECT *
|
||||||
|
FROM projections.executions1
|
||||||
|
WHERE instance_id = $1
|
||||||
|
AND id = ANY($2)
|
||||||
|
ORDER BY id DESC
|
||||||
|
LIMIT 1)
|
||||||
|
UNION ALL
|
||||||
|
(SELECT *
|
||||||
|
FROM projections.executions1
|
||||||
|
WHERE instance_id = $1
|
||||||
|
AND id = ANY($3)
|
||||||
|
ORDER BY id DESC
|
||||||
|
LIMIT 1)),
|
||||||
|
matched_targets_and_includes AS (SELECT pos.*
|
||||||
|
FROM matched m
|
||||||
|
JOIN
|
||||||
|
projections.executions1_targets pos
|
||||||
|
ON m.id = pos.execution_id
|
||||||
|
AND m.instance_id = pos.instance_id
|
||||||
|
ORDER BY execution_id,
|
||||||
|
position)
|
||||||
|
select e.execution_id, e.instance_id, e.target_id, t.target_type, t.endpoint, t.timeout, t.interrupt_on_error
|
||||||
|
FROM dissolved_execution_targets e
|
||||||
|
JOIN projections.targets1 t
|
||||||
|
ON e.instance_id = t.instance_id
|
||||||
|
AND e.target_id = t.id
|
||||||
|
WHERE "include" = ''
|
||||||
|
ORDER BY position DESC;
|
@ -23,7 +23,10 @@ func NewAggregate(aggrID, instanceID string) *eventstore.Aggregate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ID(executionType domain.ExecutionType, value string) string {
|
func ID(executionType domain.ExecutionType, value string) string {
|
||||||
return strings.Join([]string{executionType.String(), value}, ".")
|
if strings.HasPrefix(value, "/") {
|
||||||
|
return strings.Join([]string{executionType.String(), value}, "")
|
||||||
|
}
|
||||||
|
return strings.Join([]string{executionType.String(), value}, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func IDAll(executionType domain.ExecutionType) string {
|
func IDAll(executionType domain.ExecutionType) string {
|
||||||
|
@ -4,5 +4,6 @@ import "github.com/zitadel/zitadel/internal/eventstore"
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
eventstore.RegisterFilterEventMapper(AggregateType, SetEventType, eventstore.GenericEventMapper[SetEvent])
|
eventstore.RegisterFilterEventMapper(AggregateType, SetEventType, eventstore.GenericEventMapper[SetEvent])
|
||||||
|
eventstore.RegisterFilterEventMapper(AggregateType, SetEventV2Type, eventstore.GenericEventMapper[SetEventV2])
|
||||||
eventstore.RegisterFilterEventMapper(AggregateType, RemovedEventType, eventstore.GenericEventMapper[RemovedEvent])
|
eventstore.RegisterFilterEventMapper(AggregateType, RemovedEventType, eventstore.GenericEventMapper[RemovedEvent])
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,14 @@ package execution
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
eventTypePrefix eventstore.EventType = "execution."
|
eventTypePrefix eventstore.EventType = "execution."
|
||||||
SetEventType = eventTypePrefix + "set"
|
SetEventType = eventTypePrefix + "set"
|
||||||
|
SetEventV2Type = eventTypePrefix + "v2.set"
|
||||||
RemovedEventType = eventTypePrefix + "removed"
|
RemovedEventType = eventTypePrefix + "removed"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,18 +33,39 @@ func (e *SetEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSetEvent(
|
type SetEventV2 struct {
|
||||||
|
*eventstore.BaseEvent `json:"-"`
|
||||||
|
|
||||||
|
Targets []*Target `json:"targets"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *SetEventV2) SetBaseEvent(b *eventstore.BaseEvent) {
|
||||||
|
e.BaseEvent = b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *SetEventV2) Payload() any {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *SetEventV2) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Target struct {
|
||||||
|
Type domain.ExecutionTargetType `json:"type"`
|
||||||
|
Target string `json:"target"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSetEventV2(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
aggregate *eventstore.Aggregate,
|
aggregate *eventstore.Aggregate,
|
||||||
targets []string,
|
targets []*Target,
|
||||||
includes []string,
|
) *SetEventV2 {
|
||||||
) *SetEvent {
|
return &SetEventV2{
|
||||||
return &SetEvent{
|
|
||||||
BaseEvent: eventstore.NewBaseEventForPush(
|
BaseEvent: eventstore.NewBaseEventForPush(
|
||||||
ctx, aggregate, SetEventType,
|
ctx, aggregate, SetEventV2Type,
|
||||||
),
|
),
|
||||||
Targets: targets,
|
Targets: targets,
|
||||||
Includes: includes,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,9 +20,8 @@ type AddedEvent struct {
|
|||||||
|
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
TargetType domain.TargetType `json:"targetType"`
|
TargetType domain.TargetType `json:"targetType"`
|
||||||
URL string `json:"url"`
|
Endpoint string `json:"endpoint"`
|
||||||
Timeout time.Duration `json:"timeout"`
|
Timeout time.Duration `json:"timeout"`
|
||||||
Async bool `json:"async"`
|
|
||||||
InterruptOnError bool `json:"interruptOnError"`
|
InterruptOnError bool `json:"interruptOnError"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,16 +42,15 @@ func NewAddedEvent(
|
|||||||
aggregate *eventstore.Aggregate,
|
aggregate *eventstore.Aggregate,
|
||||||
name string,
|
name string,
|
||||||
targetType domain.TargetType,
|
targetType domain.TargetType,
|
||||||
url string,
|
endpoint string,
|
||||||
timeout time.Duration,
|
timeout time.Duration,
|
||||||
async bool,
|
|
||||||
interruptOnError bool,
|
interruptOnError bool,
|
||||||
) *AddedEvent {
|
) *AddedEvent {
|
||||||
return &AddedEvent{
|
return &AddedEvent{
|
||||||
*eventstore.NewBaseEventForPush(
|
*eventstore.NewBaseEventForPush(
|
||||||
ctx, aggregate, AddedEventType,
|
ctx, aggregate, AddedEventType,
|
||||||
),
|
),
|
||||||
name, targetType, url, timeout, async, interruptOnError}
|
name, targetType, endpoint, timeout, interruptOnError}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChangedEvent struct {
|
type ChangedEvent struct {
|
||||||
@ -60,9 +58,8 @@ type ChangedEvent struct {
|
|||||||
|
|
||||||
Name *string `json:"name,omitempty"`
|
Name *string `json:"name,omitempty"`
|
||||||
TargetType *domain.TargetType `json:"targetType,omitempty"`
|
TargetType *domain.TargetType `json:"targetType,omitempty"`
|
||||||
URL *string `json:"url,omitempty"`
|
Endpoint *string `json:"endpoint,omitempty"`
|
||||||
Timeout *time.Duration `json:"timeout,omitempty"`
|
Timeout *time.Duration `json:"timeout,omitempty"`
|
||||||
Async *bool `json:"async,omitempty"`
|
|
||||||
InterruptOnError *bool `json:"interruptOnError,omitempty"`
|
InterruptOnError *bool `json:"interruptOnError,omitempty"`
|
||||||
|
|
||||||
oldName string
|
oldName string
|
||||||
@ -119,9 +116,9 @@ func ChangeTargetType(targetType domain.TargetType) func(event *ChangedEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ChangeURL(url string) func(event *ChangedEvent) {
|
func ChangeEndpoint(endpoint string) func(event *ChangedEvent) {
|
||||||
return func(e *ChangedEvent) {
|
return func(e *ChangedEvent) {
|
||||||
e.URL = &url
|
e.Endpoint = &endpoint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,12 +128,6 @@ func ChangeTimeout(timeout time.Duration) func(event *ChangedEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ChangeAsync(async bool) func(event *ChangedEvent) {
|
|
||||||
return func(e *ChangedEvent) {
|
|
||||||
e.Async = &async
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ChangeInterruptOnError(interruptOnError bool) func(event *ChangedEvent) {
|
func ChangeInterruptOnError(interruptOnError bool) func(event *ChangedEvent) {
|
||||||
return func(e *ChangedEvent) {
|
return func(e *ChangedEvent) {
|
||||||
e.InterruptOnError = &interruptOnError
|
e.InterruptOnError = &interruptOnError
|
||||||
|
@ -484,6 +484,7 @@ Errors:
|
|||||||
NotActive: Действието не е активно
|
NotActive: Действието не е активно
|
||||||
NotInactive: Действието не е неактивно
|
NotInactive: Действието не е неактивно
|
||||||
MaxAllowed: Не са разрешени допълнителни активни действия
|
MaxAllowed: Не са разрешени допълнителни активни действия
|
||||||
|
NotEnabled: Функцията „Действие“ не е активирана
|
||||||
Flow:
|
Flow:
|
||||||
FlowTypeMissing: Липсва FlowType
|
FlowTypeMissing: Липсва FlowType
|
||||||
Empty: Потокът вече е празен
|
Empty: Потокът вече е празен
|
||||||
|
@ -470,6 +470,7 @@ Errors:
|
|||||||
NotActive: Akce není aktivní
|
NotActive: Akce není aktivní
|
||||||
NotInactive: Akce není neaktivní
|
NotInactive: Akce není neaktivní
|
||||||
MaxAllowed: Není dovoleno více aktivních akcí
|
MaxAllowed: Není dovoleno více aktivních akcí
|
||||||
|
NotEnabled: Funkce "Akce" není povolena
|
||||||
Flow:
|
Flow:
|
||||||
FlowTypeMissing: Chybí typ toku
|
FlowTypeMissing: Chybí typ toku
|
||||||
Empty: Tok je již prázdný
|
Empty: Tok je již prázdný
|
||||||
|
@ -470,6 +470,7 @@ Errors:
|
|||||||
NotActive: Action ist nicht aktiv
|
NotActive: Action ist nicht aktiv
|
||||||
NotInactive: Action ist nicht inaktiv
|
NotInactive: Action ist nicht inaktiv
|
||||||
MaxAllowed: Keine weitere aktiven Actions mehr erlaubt
|
MaxAllowed: Keine weitere aktiven Actions mehr erlaubt
|
||||||
|
NotEnabled: Function "Action" ist nicht aktiviert
|
||||||
Flow:
|
Flow:
|
||||||
FlowTypeMissing: FlowType fehlt
|
FlowTypeMissing: FlowType fehlt
|
||||||
Empty: Flow ist bereits leer
|
Empty: Flow ist bereits leer
|
||||||
|
@ -470,6 +470,7 @@ Errors:
|
|||||||
NotActive: Action is not active
|
NotActive: Action is not active
|
||||||
NotInactive: Action is not inactive
|
NotInactive: Action is not inactive
|
||||||
MaxAllowed: No additional active Actions allowed
|
MaxAllowed: No additional active Actions allowed
|
||||||
|
NotEnabled: Feature "Action" is not enabled
|
||||||
Flow:
|
Flow:
|
||||||
FlowTypeMissing: FlowType missing
|
FlowTypeMissing: FlowType missing
|
||||||
Empty: Flow is already empty
|
Empty: Flow is already empty
|
||||||
|
@ -470,6 +470,7 @@ Errors:
|
|||||||
NotActive: La acción no está activa
|
NotActive: La acción no está activa
|
||||||
NotInactive: La acción no está inactiva
|
NotInactive: La acción no está inactiva
|
||||||
MaxAllowed: No hay acciones adicionales activas permitidas
|
MaxAllowed: No hay acciones adicionales activas permitidas
|
||||||
|
NotEnabled: La función "Acción" no está habilitada
|
||||||
Flow:
|
Flow:
|
||||||
FlowTypeMissing: Falta el tipo de flujo
|
FlowTypeMissing: Falta el tipo de flujo
|
||||||
Empty: El flujo ya está vacío
|
Empty: El flujo ya está vacío
|
||||||
|
@ -470,6 +470,7 @@ Errors:
|
|||||||
NotActive: L'action n'est pas active
|
NotActive: L'action n'est pas active
|
||||||
NotInactive: L'action n'est pas inactive
|
NotInactive: L'action n'est pas inactive
|
||||||
MaxAllowed: Aucune action active supplémentaire n'est autorisée
|
MaxAllowed: Aucune action active supplémentaire n'est autorisée
|
||||||
|
NotEnabled: La fonctionnalité "Action" n'est pas activée
|
||||||
Flow:
|
Flow:
|
||||||
FlowTypeMissing: FlowType missing
|
FlowTypeMissing: FlowType missing
|
||||||
Empty: Le flux est déjà vide
|
Empty: Le flux est déjà vide
|
||||||
|
@ -470,6 +470,7 @@ Errors:
|
|||||||
NotActive: L'azione non è attiva
|
NotActive: L'azione non è attiva
|
||||||
NotInactive: L'azione non è inattiva
|
NotInactive: L'azione non è inattiva
|
||||||
MaxAllowed: Non sono permesse altre azioni attive
|
MaxAllowed: Non sono permesse altre azioni attive
|
||||||
|
NotEnabled: La funzione "Azione" non è abilitata
|
||||||
Flow:
|
Flow:
|
||||||
FlowTypeMissing: FlowType mancante
|
FlowTypeMissing: FlowType mancante
|
||||||
Empty: Flow è già vuoto
|
Empty: Flow è già vuoto
|
||||||
|
@ -459,6 +459,7 @@ Errors:
|
|||||||
NotActive: アクションはアクティブではありません
|
NotActive: アクションはアクティブではありません
|
||||||
NotInactive: アクションは非アクティブではありません
|
NotInactive: アクションは非アクティブではありません
|
||||||
MaxAllowed: 追加のアクティブアクションは許可されていません
|
MaxAllowed: 追加のアクティブアクションは許可されていません
|
||||||
|
NotEnabled: 機能「アクション」が有効になっていません
|
||||||
Flow:
|
Flow:
|
||||||
FlowTypeMissing: フロータイプがありません
|
FlowTypeMissing: フロータイプがありません
|
||||||
Empty: フローはすでに空です
|
Empty: フローはすでに空です
|
||||||
|
@ -469,6 +469,7 @@ Errors:
|
|||||||
NotActive: Акцијата не е активна
|
NotActive: Акцијата не е активна
|
||||||
NotInactive: Акцијата не е неактивна
|
NotInactive: Акцијата не е неактивна
|
||||||
MaxAllowed: Не се дозволени дополнителни активни акции
|
MaxAllowed: Не се дозволени дополнителни активни акции
|
||||||
|
NotEnabled: Функцијата „Акција“ не е овозможена
|
||||||
Flow:
|
Flow:
|
||||||
FlowTypeMissing: FlowType не е наведен
|
FlowTypeMissing: FlowType не е наведен
|
||||||
Empty: Flow е веќе празен
|
Empty: Flow е веќе празен
|
||||||
|
@ -469,6 +469,7 @@ Errors:
|
|||||||
NotActive: Actie is niet actief
|
NotActive: Actie is niet actief
|
||||||
NotInactive: Actie is niet inactief
|
NotInactive: Actie is niet inactief
|
||||||
MaxAllowed: Geen extra actieve acties toegestaan
|
MaxAllowed: Geen extra actieve acties toegestaan
|
||||||
|
NotEnabled: Functie "Actie" is niet ingeschakeld
|
||||||
Flow:
|
Flow:
|
||||||
FlowTypeMissing: FlowType ontbreekt
|
FlowTypeMissing: FlowType ontbreekt
|
||||||
Empty: Flow is al leeg
|
Empty: Flow is al leeg
|
||||||
|
@ -470,6 +470,7 @@ Errors:
|
|||||||
NotActive: Działanie nie jest aktywne
|
NotActive: Działanie nie jest aktywne
|
||||||
NotInactive: Działanie nie jest dezaktywowane
|
NotInactive: Działanie nie jest dezaktywowane
|
||||||
MaxAllowed: Nie dopuszcza się dodatkowych aktywnych działań.
|
MaxAllowed: Nie dopuszcza się dodatkowych aktywnych działań.
|
||||||
|
NotEnabled: Funkcja „Akcja” nie jest włączona
|
||||||
Flow:
|
Flow:
|
||||||
FlowTypeMissing: Typ przepływu brakuje
|
FlowTypeMissing: Typ przepływu brakuje
|
||||||
Empty: Przepływ jest już pusty
|
Empty: Przepływ jest już pusty
|
||||||
|
@ -469,6 +469,7 @@ Errors:
|
|||||||
NotActive: A ação não está ativa
|
NotActive: A ação não está ativa
|
||||||
NotInactive: A ação não está inativa
|
NotInactive: A ação não está inativa
|
||||||
MaxAllowed: Não são permitidas ações adicionais ativas
|
MaxAllowed: Não são permitidas ações adicionais ativas
|
||||||
|
NotEnabled: O recurso "Ação" não está ativado
|
||||||
Flow:
|
Flow:
|
||||||
FlowTypeMissing: O tipo de fluxo está faltando
|
FlowTypeMissing: O tipo de fluxo está faltando
|
||||||
Empty: O fluxo já está vazio
|
Empty: O fluxo já está vazio
|
||||||
|
@ -463,6 +463,7 @@ Errors:
|
|||||||
NotActive: Действие не активно
|
NotActive: Действие не активно
|
||||||
NotInactive: Действие не является неактивным
|
NotInactive: Действие не является неактивным
|
||||||
MaxAllowed: Дополнительные активные действия запрещены
|
MaxAllowed: Дополнительные активные действия запрещены
|
||||||
|
NotEnabled: Функция «Действие» не включена
|
||||||
Flow:
|
Flow:
|
||||||
FlowTypeMissing: Тип процесса отсутствует
|
FlowTypeMissing: Тип процесса отсутствует
|
||||||
Empty: Процесс уже пуст
|
Empty: Процесс уже пуст
|
||||||
|
@ -470,6 +470,7 @@ Errors:
|
|||||||
NotActive: 动作不是启用状态
|
NotActive: 动作不是启用状态
|
||||||
NotInactive: 动作不是停用状态
|
NotInactive: 动作不是停用状态
|
||||||
MaxAllowed: 不允许额外的动作
|
MaxAllowed: 不允许额外的动作
|
||||||
|
NotEnabled: 未启用“操作”功能
|
||||||
Flow:
|
Flow:
|
||||||
FlowTypeMissing: 缺少身份认证流程类型
|
FlowTypeMissing: 缺少身份认证流程类型
|
||||||
Empty: 身份认证流程为空
|
Empty: 身份认证流程为空
|
||||||
|
@ -423,22 +423,26 @@ message CreateTargetRequest {
|
|||||||
option (validate.required) = true;
|
option (validate.required) = true;
|
||||||
|
|
||||||
SetRESTWebhook rest_webhook = 2;
|
SetRESTWebhook rest_webhook = 2;
|
||||||
SetRESTRequestResponse rest_request_response = 3;
|
SetRESTCall rest_call = 3;
|
||||||
|
SetRESTAsync rest_async = 4;
|
||||||
}
|
}
|
||||||
// Timeout defines the duration until ZITADEL cancels the execution.
|
// Timeout defines the duration until ZITADEL cancels the execution.
|
||||||
google.protobuf.Duration timeout = 4 [
|
google.protobuf.Duration timeout = 5 [
|
||||||
(validate.rules).duration = {gt: {seconds: 0}, required: true},
|
(validate.rules).duration = {gt: {seconds: 0}, required: true},
|
||||||
(google.api.field_behavior) = REQUIRED,
|
(google.api.field_behavior) = REQUIRED,
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
example: "\"10s\"";
|
example: "\"10s\"";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
oneof execution_type {
|
string endpoint = 6 [
|
||||||
// Set the execution to run asynchronously.
|
(validate.rules).string = {min_len: 1, max_len: 1000, uri: true},
|
||||||
bool is_async = 5;
|
(google.api.field_behavior) = REQUIRED,
|
||||||
// Define if any error stops the whole execution. By default the process continues as normal.
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
bool interrupt_on_error = 6;
|
min_length: 1,
|
||||||
|
max_length: 1000,
|
||||||
|
example: "\"https://example.com/hooks/ip_check\"";
|
||||||
}
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
message CreateTargetResponse {
|
message CreateTargetResponse {
|
||||||
@ -472,21 +476,24 @@ message UpdateTargetRequest {
|
|||||||
// or its target URL.
|
// or its target URL.
|
||||||
oneof target_type {
|
oneof target_type {
|
||||||
SetRESTWebhook rest_webhook = 3;
|
SetRESTWebhook rest_webhook = 3;
|
||||||
SetRESTRequestResponse rest_request_response = 4;
|
SetRESTCall rest_call = 4;
|
||||||
|
SetRESTAsync rest_async = 5;
|
||||||
}
|
}
|
||||||
// Optionally change the timeout, which defines the duration until ZITADEL cancels the execution.
|
// Optionally change the timeout, which defines the duration until ZITADEL cancels the execution.
|
||||||
optional google.protobuf.Duration timeout = 5 [
|
optional google.protobuf.Duration timeout = 6 [
|
||||||
(validate.rules).duration = {gt: {seconds: 0}},
|
(validate.rules).duration = {gt: {seconds: 0}},
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
example: "\"10s\"";
|
example: "\"10s\"";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
oneof execution_type {
|
|
||||||
// Set the execution to run asynchronously.
|
optional string endpoint = 7 [
|
||||||
bool is_async = 6;
|
(validate.rules).string = {max_len: 1000, uri: true},
|
||||||
// Define if any error stops the whole execution. By default the process continues as normal.
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
bool interrupt_on_error = 7;
|
max_length: 1000,
|
||||||
|
example: "\"https://example.com/hooks/ip_check\"";
|
||||||
}
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
message UpdateTargetResponse {
|
message UpdateTargetResponse {
|
||||||
@ -554,10 +561,8 @@ message GetTargetByIDResponse {
|
|||||||
message SetExecutionRequest {
|
message SetExecutionRequest {
|
||||||
// Defines the condition type and content of the condition for execution.
|
// Defines the condition type and content of the condition for execution.
|
||||||
Condition condition = 1;
|
Condition condition = 1;
|
||||||
// Defines the execution targets which are defined as a different resource, which are called in the defined conditions.
|
// Ordered list of targets/includes called during the execution.
|
||||||
repeated string targets = 2;
|
repeated zitadel.action.v3alpha.ExecutionTargetType targets = 2;
|
||||||
// Defines other executions as included with the same condition-types.
|
|
||||||
repeated string includes = 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message SetExecutionResponse {
|
message SetExecutionResponse {
|
||||||
|
@ -14,17 +14,20 @@ import "zitadel/protoc_gen_zitadel/v2/options.proto";
|
|||||||
option go_package = "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha;action";
|
option go_package = "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha;action";
|
||||||
|
|
||||||
message Execution {
|
message Execution {
|
||||||
string execution_id = 1 [
|
Condition Condition = 1;
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
example: "\"request.zitadel.session.v2beta.SessionService\"";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
// Details provide some base information (such as the last change date) of the target.
|
// Details provide some base information (such as the last change date) of the target.
|
||||||
zitadel.object.v2beta.Details details = 2;
|
zitadel.object.v2beta.Details details = 2;
|
||||||
// Targets which are called in the defined conditions.
|
// List of ordered list of targets/includes called during the execution.
|
||||||
repeated string targets = 3;
|
repeated ExecutionTargetType targets = 3;
|
||||||
// Included executions with the same condition-types.
|
}
|
||||||
repeated string includes = 4;
|
|
||||||
|
message ExecutionTargetType {
|
||||||
|
oneof type {
|
||||||
|
// Unique identifier of existing target to call.
|
||||||
|
string target = 1;
|
||||||
|
// Unique identifier of existing execution to include targets of.
|
||||||
|
Condition include = 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message Condition {
|
message Condition {
|
||||||
@ -37,7 +40,7 @@ message Condition {
|
|||||||
// Condition-type to execute on response if a request on the defined API point happens.
|
// Condition-type to execute on response if a request on the defined API point happens.
|
||||||
ResponseExecution response = 2;
|
ResponseExecution response = 2;
|
||||||
// Condition-type to execute if function is used, replaces actions v1.
|
// Condition-type to execute if function is used, replaces actions v1.
|
||||||
string function = 3;
|
FunctionExecution function = 3;
|
||||||
// Condition-type to execute if an event is created in the system.
|
// Condition-type to execute if an event is created in the system.
|
||||||
EventExecution event = 4;
|
EventExecution event = 4;
|
||||||
}
|
}
|
||||||
@ -95,6 +98,11 @@ message ResponseExecution {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Executed on the specified function
|
||||||
|
message FunctionExecution {
|
||||||
|
string name = 1 [(validate.rules).string = {min_len: 1, max_len: 1000}];
|
||||||
|
}
|
||||||
|
|
||||||
message EventExecution{
|
message EventExecution{
|
||||||
// Condition for the event execution, only one possible.
|
// Condition for the event execution, only one possible.
|
||||||
oneof condition{
|
oneof condition{
|
||||||
|
@ -43,7 +43,7 @@ message TargetQuery {
|
|||||||
|
|
||||||
message IncludeQuery {
|
message IncludeQuery {
|
||||||
// Defines the include to query for.
|
// Defines the include to query for.
|
||||||
string include = 1 [
|
Condition include = 1 [
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
description: "the id of the include"
|
description: "the id of the include"
|
||||||
example: "\"request.zitadel.session.v2beta.SessionService\"";
|
example: "\"request.zitadel.session.v2beta.SessionService\"";
|
||||||
|
@ -13,30 +13,21 @@ import "zitadel/protoc_gen_zitadel/v2/options.proto";
|
|||||||
|
|
||||||
option go_package = "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha;action";
|
option go_package = "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha;action";
|
||||||
|
|
||||||
|
// Wait for response but response body is ignored, status is checked, call is sent as post.
|
||||||
message SetRESTWebhook {
|
message SetRESTWebhook {
|
||||||
string url = 1 [
|
// Define if any error stops the whole execution. By default the process continues as normal.
|
||||||
(validate.rules).string = {min_len: 1, max_len: 1000, uri: true},
|
bool interrupt_on_error = 1;
|
||||||
(google.api.field_behavior) = REQUIRED,
|
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
min_length: 1,
|
|
||||||
max_length: 1000,
|
|
||||||
example: "\"https://example.com/hooks/ip_check\"";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message SetRESTRequestResponse {
|
// Wait for response and response body is used, status is checked, call is sent as post.
|
||||||
string url = 1 [
|
message SetRESTCall {
|
||||||
(validate.rules).string = {min_len: 1, max_len: 1000, uri: true},
|
// Define if any error stops the whole execution. By default the process continues as normal.
|
||||||
(google.api.field_behavior) = REQUIRED,
|
bool interrupt_on_error = 1;
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
min_length: 1,
|
|
||||||
max_length: 1000,
|
|
||||||
example: "\"https://example.com/hooks/ip_check\"";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call is executed in parallel to others, ZITADEL does not wait until the call is finished. The state is ignored, call is sent as post.
|
||||||
|
message SetRESTAsync {}
|
||||||
|
|
||||||
message Target {
|
message Target {
|
||||||
// ID is the read-only unique identifier of the target.
|
// ID is the read-only unique identifier of the target.
|
||||||
string target_id = 1 [
|
string target_id = 1 [
|
||||||
@ -56,18 +47,19 @@ message Target {
|
|||||||
// Defines the target type and how the response of the target is treated.
|
// Defines the target type and how the response of the target is treated.
|
||||||
oneof target_type {
|
oneof target_type {
|
||||||
SetRESTWebhook rest_webhook = 4;
|
SetRESTWebhook rest_webhook = 4;
|
||||||
SetRESTRequestResponse rest_request_response = 5;
|
SetRESTCall rest_call = 5;
|
||||||
|
SetRESTAsync rest_async = 6;
|
||||||
}
|
}
|
||||||
// Timeout defines the duration until ZITADEL cancels the execution.
|
// Timeout defines the duration until ZITADEL cancels the execution.
|
||||||
google.protobuf.Duration timeout = 6 [
|
google.protobuf.Duration timeout = 7 [
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
example: "\"10s\"";
|
example: "\"10s\"";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
oneof execution_type {
|
|
||||||
// Set the execution to run asynchronously.
|
string endpoint = 8 [
|
||||||
bool is_async = 7;
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
// Define if any error stops the whole execution. By default the process continues as normal.
|
example: "\"https://example.com/hooks/ip_check\"";
|
||||||
bool interrupt_on_error = 8;
|
|
||||||
}
|
}
|
||||||
|
];
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user